diff --git a/CHANGELOG.md b/CHANGELOG.md index 42aca19..2a06fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- Fix serialization of optional values in request and response types ([#19](https://github.com/stjude-rust-labs/tes/pull/19)). + ## 0.8.0 - 06-04-2025 ### Changed diff --git a/examples/task-list-all.rs b/examples/task-list-all.rs index b57dc23..b7c3daa 100644 --- a/examples/task-list-all.rs +++ b/examples/task-list-all.rs @@ -32,7 +32,7 @@ async fn list_all_tasks(client: &Client) -> Result<()> { loop { let response = client .list_tasks(Some(&ListTasksParams { - view: View::Full, + view: Some(View::Full), page_token: last_token, ..Default::default() })) diff --git a/src/v1/client.rs b/src/v1/client.rs index 746707f..d3a4f74 100644 --- a/src/v1/client.rs +++ b/src/v1/client.rs @@ -169,7 +169,7 @@ impl Client { None => "tasks".to_string(), }; - match params.map(|p| p.view).unwrap_or_default() { + match params.and_then(|p| p.view).unwrap_or_default() { View::Minimal => { let results = self.get::>(url).await?; diff --git a/src/v1/types/requests.rs b/src/v1/types/requests.rs index 3579a27..e661eda 100644 --- a/src/v1/types/requests.rs +++ b/src/v1/types/requests.rs @@ -47,18 +47,23 @@ pub struct ListTasksParams { /// The filter for task name (prefixed). /// /// If unspecified, no task name filtering is done. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub name_prefix: Option, /// The filter for task state. /// /// If unspecified, no task state filtering is done. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub state: Option, /// The filter for task tag keys. /// /// This is zipped with `tag_values`. /// /// If empty, no task tags filtering is done. - #[cfg_attr(feature = "serde", serde(rename = "tag_key", default))] - pub tag_keys: Vec, + #[cfg_attr( + feature = "serde", + serde(rename = "tag_key", default, skip_serializing_if = "Option::is_none") + )] + pub tag_keys: Option>, /// The filter for task tag values. /// /// This is zipped with `tag_keys`. @@ -66,11 +71,15 @@ pub struct ListTasksParams { /// If the value is empty, it matches all values. /// /// It is an error if more values are supplied than keys. - #[cfg_attr(feature = "serde", serde(rename = "tag_value", default))] - pub tag_values: Vec, + #[cfg_attr( + feature = "serde", + serde(rename = "tag_value", default, skip_serializing_if = "Option::is_none") + )] + pub tag_values: Option>, /// The number of tasks to return in one page. /// /// Must be less than 2048. Defaults to 256. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub page_size: Option, /// The page token to retrieve the next page of results. /// @@ -78,10 +87,11 @@ pub struct ListTasksParams { /// /// The value can be found in the `next_page_token`` field of the last /// returned result of `list_tasks`. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub page_token: Option, /// The view of the returned task(s). - #[cfg_attr(feature = "serde", serde(default))] - pub view: View, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub view: Option, } /// Represents the request body of the `CreateTask` endpoint. diff --git a/src/v1/types/responses/service_info.rs b/src/v1/types/responses/service_info.rs index a8b93d0..f10df0d 100644 --- a/src/v1/types/responses/service_info.rs +++ b/src/v1/types/responses/service_info.rs @@ -65,25 +65,31 @@ pub struct ServiceInfo { ty: ServiceType, /// An optional description of the service. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] description: Option, /// The organization running the service. organization: Organization, /// An optional contact URL. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] contact_url: Option, /// An optional documentation URL. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] documentation_url: Option, /// Timestamp when the service was first available. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] created_at: Option>, /// Timestamp when the service was last updated. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] updated_at: Option>, /// An optional string describing the environment that the service is /// running within. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] environment: Option, /// The version of the service. @@ -91,7 +97,18 @@ pub struct ServiceInfo { /// Lists some, but not necessarily all, storage locations supported by the /// service. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] storage: Option>, + + /// Lists all resource.backend_parameters keys supported by the service. + #[cfg_attr( + feature = "serde", + serde( + rename = "tesResources_backend_parameters", + skip_serializing_if = "Option::is_none" + ) + )] + backend_parameters: Option>, } impl ServiceInfo { @@ -256,12 +273,13 @@ mod tests { String::from("file:///path/to/local/funnel-storage"), String::from("s3://ohsu-compbio-funnel/storage"), ]), + backend_parameters: Some(vec!["foo".into(), "bar".into()]), }; let serialized = serde_json::to_string(&info).unwrap(); assert_eq!( serialized, - r#"{"id":"org.ga4gh.myservice","name":"My Server","type":{"group":"org.ga4gh","artifact":"tes","version":"1.0.0"},"description":"A description","organization":{"name":"My Organization","url":"https://example.com/"},"contactUrl":"mailto:foo@bar.com","documentationUrl":"https://docs.myservice.example.com/","createdAt":"2024-09-07T20:27:35.345673Z","updatedAt":"2024-09-07T20:27:35.345673Z","environment":"test","version":"1.5.0","storage":["file:///path/to/local/funnel-storage","s3://ohsu-compbio-funnel/storage"]}"# + r#"{"id":"org.ga4gh.myservice","name":"My Server","type":{"group":"org.ga4gh","artifact":"tes","version":"1.0.0"},"description":"A description","organization":{"name":"My Organization","url":"https://example.com/"},"contactUrl":"mailto:foo@bar.com","documentationUrl":"https://docs.myservice.example.com/","createdAt":"2024-09-07T20:27:35.345673Z","updatedAt":"2024-09-07T20:27:35.345673Z","environment":"test","version":"1.5.0","storage":["file:///path/to/local/funnel-storage","s3://ohsu-compbio-funnel/storage"],"tesResources_backend_parameters":["foo","bar"]}"# ); let deserialized: ServiceInfo = serde_json::from_str(&serialized).unwrap(); diff --git a/src/v1/types/responses/service_info/builder.rs b/src/v1/types/responses/service_info/builder.rs index edd0f00..a0cd416 100644 --- a/src/v1/types/responses/service_info/builder.rs +++ b/src/v1/types/responses/service_info/builder.rs @@ -77,6 +77,9 @@ pub struct Builder { /// /// This does not necessarily have to list _all_ storage locations. storage: Option>, + + /// Lists all resource.backend_parameters keys supported by the service. + backend_parameters: Option>, } impl Builder { @@ -215,6 +218,17 @@ impl Builder { self } + /// Sets all resource.backend_parameters keys supported by the service. + /// + /// # Notes + /// + /// This silently overrides any previously set supported backend parameters + /// for the service. + pub fn backend_parameters(mut self, value: impl Into>) -> Self { + self.backend_parameters = Some(value.into()); + self + } + /// Consumes `self` and attempts to builde a [`ServiceInfo`]. pub fn try_build(self) -> Result { let id = self.id.ok_or(Error::Missing("id"))?; @@ -249,6 +263,7 @@ impl Builder { environment: self.environment, version, storage: self.storage, + backend_parameters: self.backend_parameters, }) } } diff --git a/src/v1/types/task.rs b/src/v1/types/task.rs index 6efecda..bb506d7 100644 --- a/src/v1/types/task.rs +++ b/src/v1/types/task.rs @@ -208,18 +208,23 @@ pub struct Executor { pub command: Vec, /// The working directory. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub workdir: Option, /// The path from which to pipe the standard input stream. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub stdin: Option, /// The path to pipe the standard output stream to. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub stdout: Option, /// The path to pipe the standard error stream to. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub stderr: Option, /// The environment variables. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub env: Option>, /// Default behavior of running an array of executors is that execution @@ -227,5 +232,6 @@ pub struct Executor { /// /// If `ignore_error` is `true`, then the runner will record error exit /// codes, but will continue on to the next executor. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub ignore_error: Option, }