crabidy/quality/fyyd-provider.md

4.3 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

  • Validated against the real api.fyyd.de on 2026-07-23. All four endpoints match the fyyd/src/api.rs DTOs exactly: responses are wrapped in data; a podcast carries numeric id + title; /podcast/episodes returns a single object with title and an episodes array; each episode carries id (int), title, enclosure (a direct audio URL, e.g. an .mp3), duration (int seconds), and podcast_id (int); /episode?episode_id= returns one such object. No DTO change was needed.
  • End-to-end audio playthrough of an enclosure and a W capture to /crabidy still want a manual smoke test on the running server (the resolve path and the capture reqwest client both follow the enclosure, including podtrac/redirect URLs).