crabidy/quality/fyyd-provider.md

3.9 KiB

Quality gates — fyyd provider

LLM-verified gates for architecture/fyyd-provider.md. Automatic tests live in fyyd/src/lib.rs (provider logic against a fake Fyyd) and crabidy-server/src/settings.rs (the provider toggle).

Correctness / semantics

  • Tree shape matches D3: /fyydsearch (creatable) + hot; /fyyd/search/<term> and /fyyd/hot list podcasts as children (not tracks); /fyyd/<branch>/<podcast> lists episodes as tracks; /fyyd/<branch>/<podcast>/<episode> is the track leaf. Verified by root_lists_search_and_hot, search_terms_list_podcasts_as_children, a_podcast_lists_its_episodes_as_tracks, hot_lists_featured_podcasts.
  • A podcast listing node is not itself queueable; each podcast child is queueable and downloadable (queue/capture a whole podcast). An episode list node is queueable and downloadable. is_track_path is true only for episode leaves. Verified by the tests above and tracks_resolve_streams_and_metadata.
  • Search terms are creatable/renamable/deletable exactly like tidal/youtube (in-memory, dedup, implicit recreation on stale paths), and only /fyyd/search is creatable. Verified by search_terms_list_podcasts_as_children, search_terms_rename_and_delete, foreign_and_malformed_paths_are_rejected.
  • Playback resolves an episode to its enclosure URL; a track's artist is the podcast title and provider_item_id is the fyyd episode id. Verified by tracks_resolve_streams_and_metadata.
  • The download blessing is applied centrally (node downloadable when queueable or track-bearing; children mirror queueability), so W captures work with no capture-side change.

Robustness (hard rules)

  • No panic on any input: malformed/foreign paths, empty create/rename input, a missing enclosure, and every backend failure return a typed ProviderError, never a panic. Verified by foreign_and_malformed_paths_are_rejected, a_missing_enclosure_is_an_error_not_a_panic, backend_failures_are_typed_never_panics.
  • Every external call is bounded by a timeout (call_timeout_secs, default 30) set on the reqwest client; every listing is capped (search_results, hot_count, episodes_per_podcast). D5.
  • fyyd responses decode defensively: the data envelope is unwrapped, DTO fields use #[serde(default)], entries without a usable id are dropped, and non-positive durations degrade to None. Failures are typed FetchErrorProviderError::FetchError.
  • No secrets: fyyd's public API uses no credentials, so nothing secret is logged; only public URLs/ids appear in traces.

Integration / operability

  • fyyd is a registered provider: in ALL_PROVIDERS, ProviderToggles (all() + provider_toggles()), and the default crabidy-server.toml. Disabling it in the providers list drops the /fyyd subtree. Covered by the existing settings tests (which iterate ALL_PROVIDERS).
  • Init is non-fatal: a client that fails to build disables /fyyd with a warning and leaves every other provider and the server running (D1). A missing fyyd.toml is normal (no credentials needed).
  • The orchestrator routes every ProviderClient method for /fyyd paths to the fyyd client (owns-check + fyyd_provider()), and get_lib_root lists fyyd only when the client is mounted. No proto change and no new ProviderCommand were needed.

Live validation (deferred, needs network)

  • Against the real api.fyyd.de: a search returns podcasts, a podcast lists episodes with non-empty enclosure URLs, and an enclosure plays through the audio player. If a field name differs from the documented shape, the fix is confined to fyyd/src/api.rs DTOs (design risk noted in the architecture doc). This gate cannot run in the offline unit suite.