A LibraryOptions boolean a client omits is false, where upstream's constructor makes it true #226

Closed
opened 2026-09-15 23:30:09 +02:00 by Claude · 1 comment
Collaborator

A client that leaves a LibraryOptions boolean out gets false, where upstream's constructor sets several of them to true.

Both sides

upstream/MediaBrowser.Model/Configuration/LibraryOptions.cs initialises several booleans to true in its constructor, EnablePhotos among them. A JSON body that omits one keeps the constructor's value.

Ours deserialises into the generated DTO (crates/ephyra-dto/src/generated/models.rs), whose booleans default to false. By the time a handler sees the value, a missing field and an explicit false look the same.

What a client sees

Since #209, EnablePhotos is honoured. A client that creates or updates a library with a LibraryOptions object lacking EnablePhotos silently turns pictures off in a home-video library. The same applies to every other option upstream defaults to true.

Shape of the fix

This belongs at the generator, not in each handler. Where upstream's model has an initialiser, the generated default should match it. Read the initialisers at the pin, carry them into cargo xtask gen-enums (or whatever generates models.rs), and regenerate. A route test that posts a library without the field and reads the options back is the one that can fail.

Found reviewing #209.

A client that leaves a `LibraryOptions` boolean out gets `false`, where upstream's constructor sets several of them to `true`. ## Both sides `upstream/MediaBrowser.Model/Configuration/LibraryOptions.cs` initialises several booleans to `true` in its constructor, `EnablePhotos` among them. A JSON body that omits one keeps the constructor's value. Ours deserialises into the generated DTO (`crates/ephyra-dto/src/generated/models.rs`), whose booleans default to `false`. By the time a handler sees the value, a missing field and an explicit `false` look the same. ## What a client sees Since #209, `EnablePhotos` is honoured. A client that creates or updates a library with a `LibraryOptions` object lacking `EnablePhotos` silently turns pictures off in a home-video library. The same applies to every other option upstream defaults to `true`. ## Shape of the fix This belongs at the generator, not in each handler. Where upstream's model has an initialiser, the generated default should match it. Read the initialisers at the pin, carry them into `cargo xtask gen-enums` (or whatever generates `models.rs`), and regenerate. A route test that posts a library without the field and reads the options back is the one that can fail. Found reviewing #209.
Author
Collaborator

Fixed at the generator, and landed on main at 76f368b8.

A new xtask/src/initializers.rs reads upstream's own C# declarations per schema — both property initialisers and constructor assignments — and gen_dto emits a serde default for each boolean that starts at true. models.rs is regenerated, never hand-edited, and cargo xtask gen-dto --check passes against the pinned checkout.

65 fields across about twenty schemas, including all six LibraryOptions booleans, all seventeen of UserPolicy, and fields of ServerConfiguration, EncodingOptions, NetworkConfiguration and MediaSourceInfo. Two same-named classes that disagree are a hard error; one declaring none of the schema's booleans is not the model and is skipped, which is what tells UserConfiguration apart from its mapping class.

A route test posts LibraryOptions without EnablePhotos and asserts pictures stay on.

On the policy route, checked while reviewing. The seventeen that changed are exactly the ones upstream's UserPolicy() sets. IsAdministrator, IsDisabled, EnableContentDeletion and the rest carry no initialiser and still arrive false, so a partial body cannot grant administration or deletion. Upstream's exposure is the same, not narrower: its constructor runs, then only the present properties are set, and UpdateUserPolicy applies the result wholesale. Before this change we were stricter than upstream on a partial body, which was the bug.

Scope: non-nullable booleans only, which the module's documentation now says. The generated Default still yields false for these fields, which is filed as #232 along with two latent limits in the reader.

Fixed at the generator, and landed on `main` at `76f368b8`. A new `xtask/src/initializers.rs` reads upstream's own C# declarations per schema — both property initialisers and constructor assignments — and `gen_dto` emits a serde default for each boolean that starts at `true`. `models.rs` is regenerated, never hand-edited, and `cargo xtask gen-dto --check` passes against the pinned checkout. 65 fields across about twenty schemas, including all six `LibraryOptions` booleans, all seventeen of `UserPolicy`, and fields of `ServerConfiguration`, `EncodingOptions`, `NetworkConfiguration` and `MediaSourceInfo`. Two same-named classes that disagree are a hard error; one declaring none of the schema's booleans is not the model and is skipped, which is what tells `UserConfiguration` apart from its mapping class. A route test posts `LibraryOptions` without `EnablePhotos` and asserts pictures stay on. **On the policy route, checked while reviewing.** The seventeen that changed are exactly the ones upstream's `UserPolicy()` sets. `IsAdministrator`, `IsDisabled`, `EnableContentDeletion` and the rest carry no initialiser and still arrive `false`, so a partial body cannot grant administration or deletion. Upstream's exposure is the same, not narrower: its constructor runs, then only the present properties are set, and `UpdateUserPolicy` applies the result wholesale. Before this change we were stricter than upstream on a partial body, which was the bug. Scope: non-nullable booleans only, which the module's documentation now says. The generated `Default` still yields `false` for these fields, which is filed as #232 along with two latent limits in the reader.
Sign in to join this conversation.
No description provided.