pluginengine01 / crates /bex-wire /src /builders.rs
krystv's picture
Upload 107 files
3374e90 verified
//! Builder functions that create FlatBuffer bytes from plain Rust data structs.
//!
//! Every public function takes a `Vec<...Data>` or a single `...Data` value and
//! returns `Vec<u8>` — a self-contained FlatBuffer payload that can be sent over
//! the wire.
use crate::data::*;
use crate::bex_all_generated::bex::wire as wire;
// ===========================================================================
// Internal helpers — build leaf types first (bottom-up)
// ===========================================================================
/// Build a FlatBuffer `Image` from `ImageData`.
fn build_image<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &ImageData,
) -> flatbuffers::WIPOffset<wire::Image<'a>> {
let url = if !data.url.is_empty() {
Some(fbb.create_string(&data.url))
} else {
None
};
let layout = if !data.layout.is_empty() {
Some(fbb.create_string(&data.layout))
} else {
None
};
let blurhash = data.blurhash.as_ref().map(|s| fbb.create_string(s));
wire::Image::create(
fbb,
&wire::ImageArgs {
url,
layout,
width: data.width,
height: data.height,
blurhash,
},
)
}
/// Build a FlatBuffer `ImageSet` from `ImageSetData`.
fn build_image_set<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &ImageSetData,
) -> flatbuffers::WIPOffset<wire::ImageSet<'a>> {
let low = data.low.as_ref().map(|d| build_image(fbb, d));
let medium = data.medium.as_ref().map(|d| build_image(fbb, d));
let high = data.high.as_ref().map(|d| build_image(fbb, d));
let backdrop = data.backdrop.as_ref().map(|d| build_image(fbb, d));
let logo = data.logo.as_ref().map(|d| build_image(fbb, d));
wire::ImageSet::create(
fbb,
&wire::ImageSetArgs {
low,
medium,
high,
backdrop,
logo,
},
)
}
/// Build a FlatBuffer `LinkedId` from `LinkedIdData`.
fn build_linked_id<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &LinkedIdData,
) -> flatbuffers::WIPOffset<wire::LinkedId<'a>> {
let source = if !data.source.is_empty() {
Some(fbb.create_string(&data.source))
} else {
None
};
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
wire::LinkedId::create(fbb, &wire::LinkedIdArgs { source, id })
}
/// Build a FlatBuffer `Attr` from `AttrData`.
fn build_attr<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &AttrData,
) -> flatbuffers::WIPOffset<wire::Attr<'a>> {
let key = if !data.key.is_empty() {
Some(fbb.create_string(&data.key))
} else {
None
};
let value = if !data.value.is_empty() {
Some(fbb.create_string(&data.value))
} else {
None
};
wire::Attr::create(fbb, &wire::AttrArgs { key, value })
}
/// Build a vector of `Attr` offsets.
fn build_attr_vec<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
items: &[AttrData],
) -> flatbuffers::WIPOffset<
flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<wire::Attr<'a>>>,
> {
let offsets: Vec<_> = items.iter().map(|d| build_attr(fbb, d)).collect();
fbb.create_vector(&offsets)
}
/// Build a vector of string offsets.
fn build_string_vec<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
items: &[String],
) -> flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<&'a str>>>
{
let offsets: Vec<_> = items.iter().map(|s| fbb.create_string(s)).collect();
fbb.create_vector(&offsets)
}
/// Build a vector of `LinkedId` offsets.
fn build_linked_id_vec<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
items: &[LinkedIdData],
) -> flatbuffers::WIPOffset<
flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<wire::LinkedId<'a>>>,
> {
let offsets: Vec<_> = items.iter().map(|d| build_linked_id(fbb, d)).collect();
fbb.create_vector(&offsets)
}
// ---------------------------------------------------------------------------
// Media-level helpers
// ---------------------------------------------------------------------------
/// Build a FlatBuffer `MediaCard` from `MediaCardData`.
fn build_media_card<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &MediaCardData,
) -> flatbuffers::WIPOffset<wire::MediaCard<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let title = if !data.title.is_empty() {
Some(fbb.create_string(&data.title))
} else {
None
};
let images = data.images.as_ref().map(|d| build_image_set(fbb, d));
let original_title = data.original_title.as_ref().map(|s| fbb.create_string(s));
let tagline = data.tagline.as_ref().map(|s| fbb.create_string(s));
let year = data.year.as_ref().map(|s| fbb.create_string(s));
let genres = if !data.genres.is_empty() {
Some(build_string_vec(fbb, &data.genres))
} else {
None
};
let content_rating = data.content_rating.as_ref().map(|s| fbb.create_string(s));
let url = data.url.as_ref().map(|s| fbb.create_string(s));
let ids = if !data.ids.is_empty() {
Some(build_linked_id_vec(fbb, &data.ids))
} else {
None
};
let extra = if !data.extra.is_empty() {
Some(build_attr_vec(fbb, &data.extra))
} else {
None
};
wire::MediaCard::create(
fbb,
&wire::MediaCardArgs {
id,
title,
kind: wire::MediaKind(data.kind as i8),
images,
original_title,
tagline,
year,
score: data.score,
genres,
status: wire::Status(data.status as i8),
content_rating,
url,
ids,
extra,
},
)
}
/// Build a FlatBuffer `CategoryLink` from `CategoryLinkData`.
fn build_category_link<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &CategoryLinkData,
) -> flatbuffers::WIPOffset<wire::CategoryLink<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let title = if !data.title.is_empty() {
Some(fbb.create_string(&data.title))
} else {
None
};
let subtitle = data.subtitle.as_ref().map(|s| fbb.create_string(s));
let image = data.image.as_ref().map(|d| build_image(fbb, d));
wire::CategoryLink::create(
fbb,
&wire::CategoryLinkArgs {
id,
title,
subtitle,
image,
},
)
}
/// Build a FlatBuffer `Episode` from `EpisodeData`.
fn build_episode<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &EpisodeData,
) -> flatbuffers::WIPOffset<wire::Episode<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let title = if !data.title.is_empty() {
Some(fbb.create_string(&data.title))
} else {
None
};
let images = data.images.as_ref().map(|d| build_image_set(fbb, d));
let description = data.description.as_ref().map(|s| fbb.create_string(s));
let released = data.released.as_ref().map(|s| fbb.create_string(s));
let url = data.url.as_ref().map(|s| fbb.create_string(s));
let tags = if !data.tags.is_empty() {
Some(build_string_vec(fbb, &data.tags))
} else {
None
};
let extra = if !data.extra.is_empty() {
Some(build_attr_vec(fbb, &data.extra))
} else {
None
};
wire::Episode::create(
fbb,
&wire::EpisodeArgs {
id,
title,
number: data.number,
season: data.season,
images,
description,
released,
score: data.score,
url,
tags,
extra,
},
)
}
/// Build a FlatBuffer `Season` from `SeasonData`.
fn build_season<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &SeasonData,
) -> flatbuffers::WIPOffset<wire::Season<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let title = if !data.title.is_empty() {
Some(fbb.create_string(&data.title))
} else {
None
};
let episodes = if !data.episodes.is_empty() {
let offsets: Vec<_> = data.episodes.iter().map(|d| build_episode(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
wire::Season::create(
fbb,
&wire::SeasonArgs {
id,
title,
number: data.number,
year: data.year,
episodes,
},
)
}
/// Build a FlatBuffer `Person` from `PersonData`.
fn build_person<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &PersonData,
) -> flatbuffers::WIPOffset<wire::Person<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let name = if !data.name.is_empty() {
Some(fbb.create_string(&data.name))
} else {
None
};
let image = data.image.as_ref().map(|d| build_image_set(fbb, d));
let role = data.role.as_ref().map(|s| fbb.create_string(s));
let url = data.url.as_ref().map(|s| fbb.create_string(s));
wire::Person::create(
fbb,
&wire::PersonArgs {
id,
name,
image,
role,
url,
},
)
}
/// Build a FlatBuffer `MediaInfo` from `MediaInfoData`.
fn build_media_info<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &MediaInfoData,
) -> flatbuffers::WIPOffset<wire::MediaInfo<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let title = if !data.title.is_empty() {
Some(fbb.create_string(&data.title))
} else {
None
};
let images = data.images.as_ref().map(|d| build_image_set(fbb, d));
let original_title = data.original_title.as_ref().map(|s| fbb.create_string(s));
let description = data.description.as_ref().map(|s| fbb.create_string(s));
let year = data.year.as_ref().map(|s| fbb.create_string(s));
let release_date = data.release_date.as_ref().map(|s| fbb.create_string(s));
let genres = if !data.genres.is_empty() {
Some(build_string_vec(fbb, &data.genres))
} else {
None
};
let tags = if !data.tags.is_empty() {
Some(build_string_vec(fbb, &data.tags))
} else {
None
};
let content_rating = data.content_rating.as_ref().map(|s| fbb.create_string(s));
let seasons = if !data.seasons.is_empty() {
let offsets: Vec<_> = data.seasons.iter().map(|d| build_season(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let cast = if !data.cast.is_empty() {
let offsets: Vec<_> = data.cast.iter().map(|d| build_person(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let crew = if !data.crew.is_empty() {
let offsets: Vec<_> = data.crew.iter().map(|d| build_person(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let trailer_url = data.trailer_url.as_ref().map(|s| fbb.create_string(s));
let ids = if !data.ids.is_empty() {
Some(build_linked_id_vec(fbb, &data.ids))
} else {
None
};
let studio = data.studio.as_ref().map(|s| fbb.create_string(s));
let country = data.country.as_ref().map(|s| fbb.create_string(s));
let language = data.language.as_ref().map(|s| fbb.create_string(s));
let url = data.url.as_ref().map(|s| fbb.create_string(s));
let extra = if !data.extra.is_empty() {
Some(build_attr_vec(fbb, &data.extra))
} else {
None
};
wire::MediaInfo::create(
fbb,
&wire::MediaInfoArgs {
id,
title,
kind: wire::MediaKind(data.kind as i8),
images,
original_title,
description,
score: data.score,
scored_by: data.scored_by,
year,
release_date,
genres,
tags,
status: wire::Status(data.status as i8),
content_rating,
seasons,
cast,
crew,
runtime_minutes: data.runtime_minutes,
trailer_url,
ids,
studio,
country,
language,
url,
extra,
},
)
}
/// Build a FlatBuffer `HomeSection` from `HomeSectionData`.
fn build_home_section<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &HomeSectionData,
) -> flatbuffers::WIPOffset<wire::HomeSection<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let title = if !data.title.is_empty() {
Some(fbb.create_string(&data.title))
} else {
None
};
let subtitle = data.subtitle.as_ref().map(|s| fbb.create_string(s));
let items = if !data.items.is_empty() {
let offsets: Vec<_> = data.items.iter().map(|d| build_media_card(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let next_page = data.next_page.as_ref().map(|s| fbb.create_string(s));
let layout = data.layout.as_ref().map(|s| fbb.create_string(s));
let categories = if !data.categories.is_empty() {
let offsets: Vec<_> = data
.categories
.iter()
.map(|d| build_category_link(fbb, d))
.collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let extra = if !data.extra.is_empty() {
Some(build_attr_vec(fbb, &data.extra))
} else {
None
};
wire::HomeSection::create(
fbb,
&wire::HomeSectionArgs {
id,
title,
subtitle,
items,
next_page,
layout,
show_rank: data.show_rank,
categories,
extra,
},
)
}
// ---------------------------------------------------------------------------
// Stream-level helpers
// ---------------------------------------------------------------------------
/// Build a FlatBuffer `VideoResolution` from `VideoResolutionData`.
fn build_video_resolution<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &VideoResolutionData,
) -> flatbuffers::WIPOffset<wire::VideoResolution<'a>> {
let label = data.label.as_ref().map(|s| fbb.create_string(s));
wire::VideoResolution::create(
fbb,
&wire::VideoResolutionArgs {
width: data.width,
height: data.height,
hdr: data.hdr,
label,
},
)
}
/// Build a FlatBuffer `VideoTrack` from `VideoTrackData`.
fn build_video_track<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &VideoTrackData,
) -> flatbuffers::WIPOffset<wire::VideoTrack<'a>> {
let resolution = data.resolution.as_ref().map(|d| build_video_resolution(fbb, d));
let url = if !data.url.is_empty() {
Some(fbb.create_string(&data.url))
} else {
None
};
let mime_type = data.mime_type.as_ref().map(|s| fbb.create_string(s));
let codecs = data.codecs.as_ref().map(|s| fbb.create_string(s));
wire::VideoTrack::create(
fbb,
&wire::VideoTrackArgs {
resolution,
url,
mime_type,
bitrate: data.bitrate,
codecs,
},
)
}
/// Build a FlatBuffer `SubtitleTrack` from `SubtitleTrackData`.
fn build_subtitle_track<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &SubtitleTrackData,
) -> flatbuffers::WIPOffset<wire::SubtitleTrack<'a>> {
let label = data.label.as_ref().map(|s| fbb.create_string(s));
let url = if !data.url.is_empty() {
Some(fbb.create_string(&data.url))
} else {
None
};
let language = data.language.as_ref().map(|s| fbb.create_string(s));
let format = data.format.as_ref().map(|s| fbb.create_string(s));
wire::SubtitleTrack::create(
fbb,
&wire::SubtitleTrackArgs {
label,
url,
language,
format,
},
)
}
/// Build a FlatBuffer `Server` from `ServerData`.
fn build_server<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &ServerData,
) -> flatbuffers::WIPOffset<wire::Server<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let label = data.label.as_ref().map(|s| fbb.create_string(s));
let url = data.url.as_ref().map(|s| fbb.create_string(s));
let extra = if !data.extra.is_empty() {
Some(build_attr_vec(fbb, &data.extra))
} else {
None
};
wire::Server::create(
fbb,
&wire::ServerArgs {
id,
label,
url,
priority: data.priority,
extra,
},
)
}
/// Build a FlatBuffer `StreamSource` from `StreamSourceData`.
fn build_stream_source<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
data: &StreamSourceData,
) -> flatbuffers::WIPOffset<wire::StreamSource<'a>> {
let id = if !data.id.is_empty() {
Some(fbb.create_string(&data.id))
} else {
None
};
let label = data.label.as_ref().map(|s| fbb.create_string(s));
let manifest_url = data.manifest_url.as_ref().map(|s| fbb.create_string(s));
let videos = if !data.videos.is_empty() {
let offsets: Vec<_> = data.videos.iter().map(|d| build_video_track(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let subtitles = if !data.subtitles.is_empty() {
let offsets: Vec<_> = data
.subtitles
.iter()
.map(|d| build_subtitle_track(fbb, d))
.collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let headers = if !data.headers.is_empty() {
Some(build_attr_vec(fbb, &data.headers))
} else {
None
};
let extra = if !data.extra.is_empty() {
Some(build_attr_vec(fbb, &data.extra))
} else {
None
};
wire::StreamSource::create(
fbb,
&wire::StreamSourceArgs {
id,
label,
format: wire::StreamFormat(data.format as i8),
manifest_url,
videos,
subtitles,
headers,
extra,
},
)
}
/// Build a FlatBuffer `PagedResult` from media card items and optional next page.
fn build_paged_result<'a>(
fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
items: &[MediaCardData],
categories: &[CategoryLinkData],
next_page: Option<&str>,
) -> flatbuffers::WIPOffset<wire::PagedResult<'a>> {
let items_vec = if !items.is_empty() {
let offsets: Vec<_> = items.iter().map(|d| build_media_card(fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let categories_vec = if !categories.is_empty() {
let offsets: Vec<_> = categories
.iter()
.map(|d| build_category_link(fbb, d))
.collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let next_page_fb = next_page.map(|s| fbb.create_string(s));
wire::PagedResult::create(
fbb,
&wire::PagedResultArgs {
items: items_vec,
categories: categories_vec,
next_page: next_page_fb,
},
)
}
// ===========================================================================
// Public API — top-level builder functions
// ===========================================================================
/// Build a `HomeResult` FlatBuffer payload from a list of home sections.
pub fn build_home_result(sections: Vec<HomeSectionData>) -> Vec<u8> {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let sections_vec = if !sections.is_empty() {
let offsets: Vec<_> = sections
.iter()
.map(|d| build_home_section(&mut fbb, d))
.collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let root = wire::HomeResult::create(
&mut fbb,
&wire::HomeResultArgs {
sections: sections_vec,
},
);
fbb.finish(root, None);
fbb.finished_data().to_vec()
}
/// Build a `SearchResult` FlatBuffer payload from media card items.
pub fn build_search_result(items: Vec<MediaCardData>, next_page: Option<String>) -> Vec<u8> {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let paged = build_paged_result(&mut fbb, &items, &[], next_page.as_deref());
let root = wire::SearchResult::create(
&mut fbb,
&wire::SearchResultArgs {
result: Some(paged),
},
);
fbb.finish(root, None);
fbb.finished_data().to_vec()
}
/// Build a `InfoResult` FlatBuffer payload from a `MediaInfoData`.
pub fn build_info_result(info: MediaInfoData) -> Vec<u8> {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let info_fb = build_media_info(&mut fbb, &info);
let root = wire::InfoResult::create(
&mut fbb,
&wire::InfoResultArgs {
info: Some(info_fb),
},
);
fbb.finish(root, None);
fbb.finished_data().to_vec()
}
/// Build a `ServersResult` FlatBuffer payload from a list of servers.
pub fn build_servers_result(servers: Vec<ServerData>) -> Vec<u8> {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let servers_vec = if !servers.is_empty() {
let offsets: Vec<_> = servers.iter().map(|d| build_server(&mut fbb, d)).collect();
Some(fbb.create_vector(&offsets))
} else {
None
};
let root = wire::ServersResult::create(
&mut fbb,
&wire::ServersResultArgs {
servers: servers_vec,
},
);
fbb.finish(root, None);
fbb.finished_data().to_vec()
}
/// Build a `StreamResult` FlatBuffer payload from a `StreamSourceData`.
pub fn build_stream_result(source: StreamSourceData) -> Vec<u8> {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let source_fb = build_stream_source(&mut fbb, &source);
let root = wire::StreamResult::create(
&mut fbb,
&wire::StreamResultArgs {
source: Some(source_fb),
},
);
fbb.finish(root, None);
fbb.finished_data().to_vec()
}
/// Build an `ErrorInfo` FlatBuffer payload.
pub fn build_error_info(code: &str, message: &str, plugin_id: &str, request_id: u64) -> Vec<u8> {
let mut fbb = flatbuffers::FlatBufferBuilder::new();
let code_fb = if !code.is_empty() {
Some(fbb.create_string(code))
} else {
None
};
let message_fb = if !message.is_empty() {
Some(fbb.create_string(message))
} else {
None
};
let plugin_id_fb = if !plugin_id.is_empty() {
Some(fbb.create_string(plugin_id))
} else {
None
};
let root = wire::ErrorInfo::create(
&mut fbb,
&wire::ErrorInfoArgs {
code: code_fb,
message: message_fb,
plugin_id: plugin_id_fb,
request_id,
},
);
fbb.finish(root, None);
fbb.finished_data().to_vec()
}
// ===========================================================================
// Tests
// ===========================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_error_info() {
let buf = build_error_info("NOT_FOUND", "item missing", "plugin-1", 42);
let parsed = flatbuffers::root::<wire::ErrorInfo>(&buf).unwrap();
assert_eq!(parsed.code().unwrap(), "NOT_FOUND");
assert_eq!(parsed.message().unwrap(), "item missing");
assert_eq!(parsed.plugin_id().unwrap(), "plugin-1");
assert_eq!(parsed.request_id(), 42);
}
#[test]
fn test_build_home_result_empty() {
let buf = build_home_result(vec![]);
let parsed = wire::root_as_home_result(&buf).unwrap();
assert!(parsed.sections().is_none());
}
#[test]
fn test_build_home_result_with_section() {
let section = HomeSectionData {
id: "sec-1".into(),
title: "Trending".into(),
show_rank: true,
items: vec![MediaCardData {
id: "mc-1".into(),
title: "Test Movie".into(),
kind: 0,
score: 85,
..Default::default()
}],
..Default::default()
};
let buf = build_home_result(vec![section]);
let parsed = wire::root_as_home_result(&buf).unwrap();
let sections = parsed.sections().unwrap();
assert_eq!(sections.len(), 1);
let s = sections.get(0);
assert_eq!(s.id().unwrap(), "sec-1");
assert_eq!(s.title().unwrap(), "Trending");
assert!(s.show_rank());
let items = s.items().unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items.get(0).id().unwrap(), "mc-1");
assert_eq!(items.get(0).title().unwrap(), "Test Movie");
assert_eq!(items.get(0).score(), 85);
}
#[test]
fn test_build_search_result() {
let buf = build_search_result(
vec![MediaCardData {
id: "s-1".into(),
title: "Search Hit".into(),
kind: 1,
..Default::default()
}],
Some("page2".into()),
);
let parsed = flatbuffers::root::<wire::SearchResult>(&buf).unwrap();
let paged = parsed.result().unwrap();
assert_eq!(paged.next_page().unwrap(), "page2");
let items = paged.items().unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items.get(0).id().unwrap(), "s-1");
}
#[test]
fn test_build_servers_result() {
let buf = build_servers_result(vec![ServerData {
id: "svr-1".into(),
label: Some("HD Server".into()),
priority: 1,
..Default::default()
}]);
let parsed = flatbuffers::root::<wire::ServersResult>(&buf).unwrap();
let servers = parsed.servers().unwrap();
assert_eq!(servers.len(), 1);
assert_eq!(servers.get(0).id().unwrap(), "svr-1");
assert_eq!(servers.get(0).label().unwrap(), "HD Server");
assert_eq!(servers.get(0).priority(), 1);
}
#[test]
fn test_build_stream_result() {
let buf = build_stream_result(StreamSourceData {
id: "src-1".into(),
label: Some("Main".into()),
format: 0,
manifest_url: Some("https://example.com/manifest.m3u8".into()),
videos: vec![VideoTrackData {
url: "https://example.com/video.mp4".into(),
bitrate: 5000,
..Default::default()
}],
subtitles: vec![SubtitleTrackData {
url: "https://example.com/sub.vtt".into(),
language: Some("en".into()),
..Default::default()
}],
..Default::default()
});
let parsed = flatbuffers::root::<wire::StreamResult>(&buf).unwrap();
let source = parsed.source().unwrap();
assert_eq!(source.id().unwrap(), "src-1");
assert_eq!(source.format(), wire::StreamFormat::Hls);
assert_eq!(
source.manifest_url().unwrap(),
"https://example.com/manifest.m3u8"
);
let videos = source.videos().unwrap();
assert_eq!(videos.len(), 1);
assert_eq!(videos.get(0).bitrate(), 5000);
let subs = source.subtitles().unwrap();
assert_eq!(subs.len(), 1);
assert_eq!(subs.get(0).language().unwrap(), "en");
}
#[test]
fn test_build_info_result() {
let buf = build_info_result(MediaInfoData {
id: "info-1".into(),
title: "Detailed Info".into(),
kind: 0,
description: Some("A great movie".into()),
seasons: vec![SeasonData {
id: "s1".into(),
title: "Season 1".into(),
number: 1.0,
year: 2024,
episodes: vec![EpisodeData {
id: "ep1".into(),
title: "Pilot".into(),
number: 1.0,
season: 1.0,
..Default::default()
}],
}],
..Default::default()
});
let parsed = flatbuffers::root::<wire::InfoResult>(&buf).unwrap();
let info = parsed.info().unwrap();
assert_eq!(info.id().unwrap(), "info-1");
assert_eq!(info.description().unwrap(), "A great movie");
let seasons = info.seasons().unwrap();
assert_eq!(seasons.len(), 1);
let s = seasons.get(0);
assert_eq!(s.title().unwrap(), "Season 1");
let episodes = s.episodes().unwrap();
assert_eq!(episodes.len(), 1);
assert_eq!(episodes.get(0).title().unwrap(), "Pilot");
}
#[test]
fn test_build_with_images_and_ids() {
let buf = build_search_result(
vec![MediaCardData {
id: "img-1".into(),
title: "With Images".into(),
kind: 0,
images: Some(ImageSetData {
low: Some(ImageData {
url: "https://img.low".into(),
layout: "portrait".into(),
width: 200,
height: 300,
blurhash: Some("LEHV6nWB2yk8pyo0".into()),
}),
..Default::default()
}),
ids: vec![LinkedIdData {
source: "imdb".into(),
id: "tt1234567".into(),
}],
extra: vec![AttrData {
key: "rating".into(),
value: "R".into(),
}],
..Default::default()
}],
None,
);
let parsed = flatbuffers::root::<wire::SearchResult>(&buf).unwrap();
let card = parsed.result().unwrap().items().unwrap().get(0);
assert_eq!(card.id().unwrap(), "img-1");
let images = card.images().unwrap();
let low = images.low().unwrap();
assert_eq!(low.url().unwrap(), "https://img.low");
assert_eq!(low.layout().unwrap(), "portrait");
assert_eq!(low.width(), 200);
assert_eq!(low.height(), 300);
assert_eq!(low.blurhash().unwrap(), "LEHV6nWB2yk8pyo0");
let ids = card.ids().unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids.get(0).source().unwrap(), "imdb");
assert_eq!(ids.get(0).id().unwrap(), "tt1234567");
let extra = card.extra().unwrap();
assert_eq!(extra.len(), 1);
assert_eq!(extra.get(0).key().unwrap(), "rating");
assert_eq!(extra.get(0).value().unwrap(), "R");
}
}