# Simkl Crate β Roadmap
Issues are grouped by severity and topic, derived from a cross-review of the source against
`docs/simkl.apib`.
---
## π΄ Critical β Broken at Runtime
### URL Construction
- [x] **Pervasive missing `/` separator** (`search.rs`, `lib.rs`, `pin.rs`, `sync.rs`,
`calendar.rs`, `user.rs`)
Fixed: prefixed every path segment with `"/"` in all standalone `get_*_request()`
functions to match the convention established by `SimklRequest::build_url()`.
- [x] **Wrong domain in `get_monthly_request`** (`calendar.rs`)
Fixed: now uses the new `CALENDAR_BASE_URL = "https://data.simkl.in/calendar/"` constant
instead of `API_URL`.
- [x] **Wrong OAuth authorize domain in `get_auth_url`** (`lib.rs`)
Fixed: now uses `OAUTH_URL` (`https://simkl.com/oauth/authorize`) and appends the query
string directly, rather than starting from `API_URL`.
- [x] **`get_search_request` writes the literal string `"type"` in the path**
(`search.rs`)
Fixed: path is now `/search/{type}` with the `type` parameter interpolated; `None` type
falls back to `/search/`.
### Image URLs
- [x] **`get_avatar_url` uses `"episodes/"` instead of `"avatars/"`** (`images.rs:113`)
Fixed: path prefix changed to `"avatars/"`.
### API Endpoints
- [x] **`EpisodesRequest` has the wrong path structure** (`request.rs:301-305`)
Fixed: endpoint is now `/tv/episodes/{show_id}`; `season` moved from the URL path to a
query parameter, matching the spec.
### Unimplemented Stubs
- [x] **`get_rating()` panics unconditionally** (`lib.rs:351`)
Removed: the stub had no parameters, wrong return type, and no implementation. Replaced
with a `// TODO` comment pointing to the Ratings API roadmap item.
- [x] **`FindRandomPayload::to_url_param()` panics unconditionally** (`search.rs:181`)
Fixed: removed `todo!()`, implemented `&type=` serialization (mapping `MediaType::Show`
to spec value `"tv"`). Genre serialization left as a `TODO` comment pending idiomatic
genre enum naming (see Low section).
---
## π High β Tests Fail / Correctness
### Failing Tests
- [x] **`test_avatar_urls`** (`images.rs:256-278`)
Fixed: all assertions updated to use `avatars/` prefix; `AvatarSize::Big` corrected to
`_256` and `AvatarSize::VeryBig` corrected to `_512`, both matching the spec.
- [x] **`test_episode_urls` β `M112x63` case** (`images.rs:246-252`)
Fixed: assertion corrected from `_s.webp` to `_m.webp` per spec (`episodes | _m | 112Γ63`).
- [x] **`test_id_request` wrong expected URL** (`search.rs:225-235`)
Fixed: assertion updated to `"https://api.simkl.com/search/id?β¦"` as part of the URL
construction fix.
### Duplicated Types
- [ ] **`SimklError` is defined in both `lib.rs` and `error.rs`** (`lib.rs:22-56`,
`error.rs:6-40`)
Identical variants, identical `Display` / `From` impls. Remove the copy in `lib.rs`; the
canonical definition with the `Result<T>` alias belongs in `error.rs`.
---
## π‘ Medium β Missing Coverage / API Mismatch
### Missing API Modules
- [x] **Ratings API** (`/ratings`, `/ratings/{type}`)
Added `ratings.rs`: `RatingsById` and `RatingsByWatchlist` request structs (both implement
`SimklRequest`), `RatingsResponse` / `WatchlistRatingsItem` response types, `WatchlistType`
enum, `fields` constants module. 8 unit tests + 1 doc-test.
- [x] **Redirect API** (`/redirect`)
Added `redirect.rs`: `RedirectRequest` builder (implements `SimklRequest`), `RedirectTarget`
enum (`Simkl`, `Trailer`, `Twitter`, `Watched`). All 16 ID fields present, including new
`traktslug` and `letterboxd`. 8 unit tests + 1 doc-test.
- [x] **Checkin API** (`/checkin`)
Added `checkin.rs`: `CheckinPayload` (movie/show constructors), `CheckinEpisode`,
`ShowCheckinItem`, `CheckinResponse`, `CheckinConflict`. 4 unit tests.
### Incomplete Sync Module
- [x] **Sync POST endpoints have URL stubs but no payload types** (`sync.rs`)
Added full payload types: `WatchedSeason`, `WatchedEpisode`, `MovieHistoryItem`,
`ShowHistoryItem`, `AnimeHistoryItem`, `SyncHistoryPayload`, `MovieRatingItem`,
`ShowRatingItem`, `AnimeRatingItem`, `SyncRatingPayload`, `MovieListItem`, `ShowListItem`,
`AnimeListItem`, `SyncListPayload`. Response types: `SyncAddedCounts`, `SyncNotFound`,
`SyncResult`. 8 unit tests.
### Missing ID Fields
- [x] **`IdLookup` is missing `traktslug` and `letterboxd`** (`search.rs:15-37`)
Added `traktslug: Option<String>` and `letterboxd: Option<String>` to `IdLookup`; URL
builder `get_search_by_id_request` now appends `&traktslug=` / `&letterboxd=` when set.
### Data Model Mismatches
- [x] **`PaginatedResponse<T>` doesn't match the API** (`pagination.rs:59-73`)
Added `SimklResponse::pagination_info()` (`response.rs`) that parses the four
`X-Pagination-*` response headers and returns `Option<PaginationInfo>`. The
`PaginatedResponse<T>` wrapper is kept as an optional in-memory helper; the doc comment
now clarifies that pagination comes from headers. 4 unit tests.
- [x] **`Extended::default()` sets `full = true`** (`lib.rs:83-96`)
Changed to `#[derive(Default)]` β all `bool` fields default to `false`, which is the
neutral "no extra data" mode. Callers opt in by setting `full = true` or individual flags.
---
## π’ Low β Code Quality / Style
### Naming
- [x] **Non-idiomatic enum variants in genre enums** (`anime.rs`, `movie.rs`, `show.rs`)
Removed `#[allow(non_camel_case_types)]`. All variants renamed to PascalCase
(`MartialArts`, `SciFi`, `SliceOfLife`, `AwardsShow`, `ScienceFiction`, etc.).
Added `as_str()` methods returning the kebab-case API values (`"sci-fi"`,
`"slice-of-life"`, etc.) and `Display` impls. Genre URL param now implemented in
`FindRandomPayload::to_url_param()`. Tests added for all three enums.
- [x] **`MovieGenre::Family` is PascalCase; all other variants are lowercase** (`movie.rs:13`)
Fixed: all variants are now PascalCase consistently. `Family` maps to API string `"family"`.
- [x] **`FanartSize::Mobile950x540` has wrong dimensions in its name** (`images.rs:36`)
Renamed to `Mobile960x540`. CDN suffix `_mobile` is unchanged.
### Error Handling
- [x] **Error messages are in French** (`error.rs:19-24`)
`"Erreur JSON"` β `"JSON error"`, `"ParamΓ¨tres invalides"` β `"invalid parameters"`,
`"Erreur de parsing"` β `"parse error"`. Doc comments on variants also translated.
- [x] **`MediaType::from_str` returns `Err(())`** (`lib.rs`)
Added `pub struct ParseMediaTypeError(pub String)` with `Display` + `std::error::Error`.
`FromStr::Err` changed from `()` to `ParseMediaTypeError`; error carries the unknown string.
### Field Visibility
- [x] **`User::joined_at` is private while all other `User` fields are public** (`user.rs`)
Made `pub`.
- [x] **`WatchedLastWeek`, `Count`, `TvOrAnime::total_mins` fields are all private** (`user.rs`)
All fields made `pub`.
- [x] **`Rank`, `Ratings` in `lib.rs` have private fields**
All fields in `Rank` and `Ratings` made `pub`.
### Redundant / Dead Code
- [x] **`get_extended_parameter`: redundant `.collect()`** (`lib.rs`)
`selected.into_iter().collect::<Vec<&str>>().join(",")` β `selected.join(",")`.
- [x] **`RateLimiter` is never used** (`rate_limit.rs`)
Added module-level doc comment with usage example, documenting it as an exported helper
for callers. The doc-test also runs as a test.
- [x] **Webp support for avatars contradicts the spec** (`images.rs`)
Added doc comment on `get_avatar_url` explaining the `webp` parameter is a deliberate
client-side extension via the `wsrv.nl` proxy; pass `false` to stay spec-compliant.
- [x] **Commented-out dead code removed**
- `show.rs` β removed the 47-line commented-out `Show` impl block
- `search.rs` β removed the commented-out `extended` snippet in `get_search_request`
- `pagination.rs` β removed commented-out offset support throughout