fix(models): accept null for optional string fields #41

Merged
David merged 1 commit from fix/project-list-null-deserialize into main 2026-05-16 19:25:43 +02:00
Owner

Summary

yt project list was failing on any YouTrack instance that has a project with a missing description (or a user with no email on file, etc.):

Error: list projects

Caused by:
    0: decode response from .../api/admin/projects?fields=id,shortName,name,description,archived,leader(id,login,fullName,email,banned): invalid type: null, expected a string at line 1 column 429

YouTrack returns "description": null rather than omitting the field, and plain #[serde(default)] only handles the missing-field path, not the explicit-null path.

Fix

Adds a null_or_missing_to_default helper that wraps Option::<T>::deserialize(...).map(Option::unwrap_or_default). Applied to the string fields most likely to be null on the wire: Project::name, Project::description, every string on User, and BundleValue::name / description. The missing-field path keeps working because the helper still respects #[serde(default)].

Verified

yt project list against the affected instance now renders the full table instead of erroring. The PMC project (the one with the null description) appears with an empty description cell.

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- --deny warnings
  • cargo test --all-targets (218 passed; was 215)
  • New tests: project-with-null-description, project-with-missing-description (pins the existing path), user-with-null-fullName-and-email
  • Manual: ./target/release/yt project list against the previously-failing instance renders correctly
## Summary `yt project list` was failing on any YouTrack instance that has a project with a missing description (or a user with no email on file, etc.): ``` Error: list projects Caused by: 0: decode response from .../api/admin/projects?fields=id,shortName,name,description,archived,leader(id,login,fullName,email,banned): invalid type: null, expected a string at line 1 column 429 ``` YouTrack returns `"description": null` rather than omitting the field, and plain `#[serde(default)]` only handles the missing-field path, not the explicit-null path. ### Fix Adds a `null_or_missing_to_default` helper that wraps `Option::<T>::deserialize(...).map(Option::unwrap_or_default)`. Applied to the string fields most likely to be null on the wire: `Project::name`, `Project::description`, every string on `User`, and `BundleValue::name` / `description`. The missing-field path keeps working because the helper still respects `#[serde(default)]`. ### Verified `yt project list` against the affected instance now renders the full table instead of erroring. The PMC project (the one with the null description) appears with an empty description cell. ## Test plan - [x] `cargo fmt --all` - [x] `cargo clippy --all-targets --all-features -- --deny warnings` - [x] `cargo test --all-targets` (218 passed; was 215) - [x] New tests: project-with-null-description, project-with-missing-description (pins the existing path), user-with-null-fullName-and-email - [x] Manual: `./target/release/yt project list` against the previously-failing instance renders correctly
fix(models): accept null for optional string fields
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 15s
Create release / Create release from merged PR (pull_request) Has been skipped
a0cba65470
`yt project list` fails against any YouTrack instance that has a project with a missing description, a user with no email on file, or any other optional string the server materializes as `null` rather than omitting. The default `#[serde(default)]` only handles the "field missing" case; an explicit `null` triggers `invalid type: null, expected a string` and the entire response is rejected. Reported in the wild against an instance with a project whose `description` was null.

Adds a `null_or_missing_to_default` helper that wraps `Option::<T>::deserialize(...).map(Option::unwrap_or_default)`. Applied to the string fields most likely to be null on the wire: `Project::name`, `Project::description`, `User::id`, `User::login`, `User::full_name`, `User::email`, `BundleValue::name`, `BundleValue::description`. The missing-field path keeps working because the helper still respects `#[serde(default)]`.

Three regression tests pin the new behavior: a project body with `"description":null`, the existing missing-field path, and a user body with both `fullName` and `email` set to null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
David merged commit b68b3b1dfc into main 2026-05-16 19:25:43 +02:00
David deleted branch fix/project-list-null-deserialize 2026-05-16 19:25:43 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/youtrack-cli!41
No description provided.