//! 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` — 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> { 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> { 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> { 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> { 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>>, > { 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>> { 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>>, > { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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) -> Vec { 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, next_page: Option) -> Vec { 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 { 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) -> Vec { 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 { 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 { 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::(&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::(&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::(&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::(&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::(&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::(&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"); } }