diff --git a/crates/workspace-api/Cargo.toml b/crates/workspace-api/Cargo.toml index 8af554f8..a052ffe0 100644 --- a/crates/workspace-api/Cargo.toml +++ b/crates/workspace-api/Cargo.toml @@ -15,3 +15,7 @@ ts-rs = { version = "12.0.1", optional = true } [dev-dependencies] serde_json.workspace = true + +[[example]] +name = "generate_workdir_api_types" +required-features = ["typescript"] diff --git a/crates/workspace-api/src/lib.rs b/crates/workspace-api/src/lib.rs index dc0baf6b..8170db95 100644 --- a/crates/workspace-api/src/lib.rs +++ b/crates/workspace-api/src/lib.rs @@ -172,30 +172,30 @@ pub struct WorkingDirectoryOccupancy { pub struct WorkingDirectorySummary { pub working_directory_id: String, pub repository_id: String, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub creation_selector: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub creation_ref: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub creation_tree: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub current_selector: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub current_ref: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub current_tree: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "typescript", ts(optional, type = "number | null"))] pub observed_at_epoch_seconds: Option, pub materializer_kind: WorkingDirectoryMaterializerKind, #[serde(default, skip_serializing_if = "Option::is_none")] pub cleanup_target: Option, pub status: WorkingDirectoryStatusKind, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub cleanliness: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub primary_worker_id: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub occupied_by: Option, } @@ -783,6 +783,48 @@ mod tests { assert!(serde_json::from_value::(incomplete).is_err()); } + #[test] + fn workdir_summary_omits_absent_optional_fields_on_the_wire() { + let value = serde_json::to_value(WorkingDirectorySummary { + working_directory_id: "workdir-1".into(), + repository_id: "main".into(), + creation_selector: None, + creation_ref: None, + creation_tree: None, + current_selector: None, + current_ref: None, + current_tree: None, + observed_at_epoch_seconds: None, + materializer_kind: WorkingDirectoryMaterializerKind::RuntimeGitCache, + cleanup_target: None, + status: WorkingDirectoryStatusKind::Active, + cleanliness: None, + primary_worker_id: None, + occupied_by: None, + }) + .expect("serialize Workdir summary"); + let object = value.as_object().expect("Workdir summary object"); + + for key in [ + "creation_selector", + "creation_ref", + "creation_tree", + "current_selector", + "current_ref", + "current_tree", + "observed_at_epoch_seconds", + "cleanup_target", + "cleanliness", + "primary_worker_id", + "occupied_by", + ] { + assert!( + !object.contains_key(key), + "absent field {key} must be omitted" + ); + } + } + #[test] fn workdir_response_rejects_stale_occupancy_shape() { let stale = serde_json::json!({