diff --git a/AGENTS.md b/AGENTS.md index 90cff4d..c22b62d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,7 +76,7 @@ Work from the issue's typed findings. `spec_pointer` is an RFC 6901 location in 3. Fix the API library before considering CLI exposure. Follow the finding's pointer and Rust item: - Missing/extra operations: add or remove the corresponding `Client` method in `client.rs`; only intentional non-OpenAPI helpers belong in `non_openapi_client_methods`. - Missing models, fields, or extra fields: update public structs/enums/type aliases and Serde names in `models.rs`. An undefined `$ref` (`missing_schema_definition`) is an upstream-spec defect, not a model to invent locally. `models.rs` uses explicit `#[serde(rename = "...")]` wire names exclusively; `rename_all` is rejected by the analyzer parser, because wire vocabulary (Postgres GUCs, SCIM URNs, region IDs, duration literals) cannot be derived from Rust identifiers by any casing rule, and explicit literals keep the code↔spec mapping verbatim and greppable. - - Optionality: use `T` for required non-nullable fields and `Option` plus `skip_serializing_if` for optional/nullable fields. Retain `#[serde(default)]` on model fields. + - Optionality and deserialization tolerance: requiredness is expressed only in the type — required non-nullable fields are `T`, optional/nullable fields are `Option` plus `skip_serializing_if`. We are strict in what we send and liberal in what we accept: the type system enforces required request fields, while every model field carries `#[serde(default)]`, unknown fields are ignored (never `deny_unknown_fields`), and enums/unions carry `Unknown` catch-alls. Deserializing a response must not fail because the API added, removed, or renamed a field; spec conformance is enforced by the analyzer and the daily drift job, not by runtime errors, and the policy itself is enforced by the `every_model_field_carries_serde_default` test in `spec_coverage_test.rs`. The residual case is a field whose *type* changes: `#[serde(default)]` only fills a missing key, so a type change still fails the response for a plain struct field (enums and `discriminated_union!` unions absorb it into their `Unknown` catch-all), so do not document the policy as covering a changed field type. `#[serde(default)]` only affects deserialization and never changes what we serialize, so it cannot weaken a request — a review-bot finding that it "masks a required field" is wrong, and stripping it reintroduces a user-facing outage vector. The one exception is a field that structurally discriminates an `untagged` union, which must stay strict: convert the union to `discriminated_union!` and default the field in the same change rather than leaving either half done. Defaulting every field of a variant also makes that variant total, so a union that discriminates a variant by the *absence* of the key must name the other variants' exclusive keys in the macro's `none unless` guard; otherwise a dropped discriminator silently retypes the payload as the absence variant and loses the keys it does not declare. If defaulting a specific field would be misleading, make it `Option` with an `optionality_exemptions` entry instead of leaving it strict. - Missing/extra enum values: update the typed enum, its Serde wire value, and its `Display` implementation. Preserve data-carrying catch-all variants. - Beta/deprecation findings: regenerate `BETA_OPERATIONS` with `python3 scripts/regenerate-beta-lists.py` and `DEPRECATED_FIELDS` with `python3 scripts/regenerate-deprecated-fields.py`; deprecated fields also need the matching `#[cfg(feature = "deprecated-fields")]` marker in `models.rs`. - Stale exemption: remove or narrow the configuration entry. Do not change comparison logic to preserve a stale exception. diff --git a/crates/clickhouse-cloud-api/src/lib.rs b/crates/clickhouse-cloud-api/src/lib.rs index 63f2626..ae22872 100644 --- a/crates/clickhouse-cloud-api/src/lib.rs +++ b/crates/clickhouse-cloud-api/src/lib.rs @@ -15,6 +15,37 @@ //! Ok(()) //! } //! ``` +//! +//! ## Tolerant deserialization +//! +//! Every field on every model carries `#[serde(default)]`, and unknown fields are +//! ignored, so a Cloud API change should not break a deployed consumer. Concretely, a +//! response that: +//! +//! * **adds** a field is accepted and the field ignored; +//! * **drops or renames** a field degrades to that field's default instead of failing +//! the whole call; +//! * carries an **unrecognized enum value or union shape** lands in that type's +//! `Unknown` catch-all, which holds the value verbatim and re-serializes losslessly. +//! +//! The residual case is a field whose *type* changes — an array that becomes a string, +//! say. `#[serde(default)]` fills in a missing key and cannot help there, so such a +//! change still fails the response for the model that holds the field. Enums and +//! discriminated unions absorb it into `Unknown`; a plain struct field does not. +//! +//! Requests stay strict through the type system: fields the API requires are `T`, not +//! `Option`, and the defaults never change what is serialized. +//! +//! The caveat is read-modify-write. A consumer that `GET`s an object, changes one field, +//! and writes the whole thing back can persist a defaulted value — an empty string, say — +//! for a field the server stopped sending. Callers doing read-modify-write should send an +//! explicit set of the fields they mean to change rather than echoing back a deserialized +//! response. `clickhousectl` itself does not round-trip: request types are separate from +//! response models and its update commands build bodies from CLI flags. +//! +//! Exposure to a silently defaulted field, and to the residual type-change case, is +//! bounded by the daily OpenAPI drift job, which compares the live spec against these +//! models and files an issue when they diverge. pub mod client; pub mod error; diff --git a/crates/clickhouse-cloud-api/src/models.rs b/crates/clickhouse-cloud-api/src/models.rs index 8d554ea..444b522 100644 --- a/crates/clickhouse-cloud-api/src/models.rs +++ b/crates/clickhouse-cloud-api/src/models.rs @@ -12,11 +12,19 @@ use serde::{Deserialize, Serialize}; /// `displayType`, `service`, `operator`) shares the same deserialization shape: /// buffer the payload as a [`serde_json::Value`], read the discriminator key, /// and route each known wire value to the matching variant via -/// [`serde_json::from_value`]. Any unrecognized or missing discriminator falls -/// through to the enum's `Unknown(serde_json::Value)` catch-all, so unknown -/// payloads round-trip losslessly instead of failing to deserialize. This -/// explicit dispatch avoids the greedy first-match misrouting that -/// `#[serde(untagged)]` derives suffer when variants share a discriminator. +/// [`serde_json::from_value`]. This explicit dispatch avoids the greedy +/// first-match misrouting that `#[serde(untagged)]` derives suffer when variants +/// share a discriminator. +/// +/// Once the payload buffers into a `Value`, deserialization cannot fail. Two +/// routes reach the enum's `Unknown(serde_json::Value)` catch-all, which holds +/// the payload verbatim so it round-trips losslessly: +/// +/// * an unrecognized discriminator value, through the final catch-all arm; +/// * a recognized discriminator whose payload does not fit the selected variant +/// — e.g. the API changes a field from an array to a string — through +/// [`crate::serde_helpers::deserialize_or_raw`]. `#[serde(default)]` covers a +/// field the API stops sending; this covers a field whose shape it changes. /// /// The macro emits **only** the `Deserialize` impl. The enum declaration, its /// derives/serde attributes, and its `Display` impl must remain literal source @@ -36,13 +44,47 @@ use serde::{Deserialize, Serialize}; /// } /// ``` /// +/// Some unions discriminate one variant by the *absence* of the key rather than +/// by a wire value of it (e.g. a ClickStack chart config carries +/// `configType: "sql"` when it is a raw-SQL config and carries no `configType` +/// at all when it is a builder config). Such a union adds a trailing `none` arm +/// naming the variant the key's absence selects, plus the keys whose presence +/// disqualifies that variant: +/// +/// ```ignore +/// discriminated_union! { +/// ClickStackLineChartConfig, "configType" { +/// "sql" => ClickStackLineRawSqlChartConfig, +/// none unless "connectionId" | "sqlTemplate" => ClickStackLineBuilderChartConfig, +/// } +/// } +/// ``` +/// +/// The `none` arm pins two semantics: +/// +/// * It deliberately conflates "key absent" and "key present but not a string": +/// both produce a `None` scrutinee, so both take the arm. +/// * The `unless` keys guard against a *dropped* discriminator. Because the +/// absence variant is total — every field either `Option` or +/// `#[serde(default)]` — it would otherwise absorb any keyless payload, +/// silently retyping a raw-SQL config as an empty builder config and +/// discarding its `connectionId`/`sqlTemplate`. Listing keys that only the +/// other variants carry routes such a payload to `Unknown` instead, where it +/// survives intact. Unknown *added* keys are not listed and stay ignored. If +/// the spec ever gives the absence variant one of the guard keys, drop that +/// key from the list. +/// +/// Without a `none` arm, an absent or non-string discriminator falls to +/// `Unknown` through the final catch-all. +/// /// New discriminated unions in this module should use this macro rather than /// hand-writing the impl. Enums whose variants need multi-level or nested /// dispatch do not fit this single-key shape and must stay hand-written. macro_rules! discriminated_union { ( $enum:ident, $key:literal { - $( $( $wire:literal )|+ => $variant:ident ),+ $(,)? + $( $( $wire:literal )|+ => $variant:ident, )+ + $( none unless $( $guard:literal )|+ => $absent:ident, )? } ) => { impl<'de> Deserialize<'de> for $enum { @@ -53,10 +95,23 @@ macro_rules! discriminated_union { let value = serde_json::Value::deserialize(deserializer)?; match value.get($key).and_then(|v| v.as_str()) { $( - $( Some($wire) )|+ => serde_json::from_value(value) - .map($enum::$variant) - .map_err(serde::de::Error::custom), + $( Some($wire) )|+ => Ok( + crate::serde_helpers::deserialize_or_raw(value) + .map($enum::$variant) + .unwrap_or_else($enum::Unknown), + ), )+ + $( + None => Ok( + if [$($guard),+].iter().any(|key| value.get(key).is_some()) { + $enum::Unknown(value) + } else { + crate::serde_helpers::deserialize_or_raw(value) + .map($enum::$absent) + .unwrap_or_else($enum::Unknown) + }, + ), + )? _ => Ok($enum::Unknown(value)), } } @@ -2903,12 +2958,18 @@ impl std::fmt::Display for ClickPipeStatePatchRequestCommand { } /// Inline enum for `ClickStackAlertChannelEmail.type`. +/// +/// The spec gives both alert-channel variants the same `enum: ["webhook", +/// "email"]`, so `#[default]` sits on `Email` rather than on the first value: +/// this field discriminates the `ClickStackAlertChannel` union, and defaulting +/// it to `webhook` would make `ClickStackAlertChannelEmail::default()` +/// deserialize back as the webhook variant. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub enum ClickStackAlertChannelEmailType { #[serde(rename = "webhook")] - #[default] Webhook, #[serde(rename = "email")] + #[default] Email, /// Catch-all for unknown or newly-added values. #[serde(untagged)] @@ -7873,7 +7934,10 @@ impl std::fmt::Display for BackupBucketProperties { } /// `ClickStackAlertChannel` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `type` field; see the `discriminated_union!` +/// invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackAlertChannel { ClickStackAlertChannelEmail(ClickStackAlertChannelEmail), @@ -7885,6 +7949,13 @@ pub enum ClickStackAlertChannel { Unknown(serde_json::Value), } +discriminated_union! { + ClickStackAlertChannel, "type" { + "email" => ClickStackAlertChannelEmail, + "webhook" => ClickStackAlertChannelWebhook, + } +} + impl std::fmt::Display for ClickStackAlertChannel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -7896,15 +7967,29 @@ impl std::fmt::Display for ClickStackAlertChannel { } /// `ClickStackBarChartConfig` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `configType` field (absent or non-string dispatches to the +/// builder variant, unless the payload carries a raw-SQL-only key); see the +/// `discriminated_union!` invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackBarChartConfig { ClickStackBarBuilderChartConfig(ClickStackBarBuilderChartConfig), ClickStackBarRawSqlChartConfig(ClickStackBarRawSqlChartConfig), /// Catch-all for unknown or newly-added values. + /// + /// Holds the raw payload as `serde_json::Value` so it round-trips + /// losslessly; its `Display` emits the payload as compact JSON. Unknown(serde_json::Value), } +discriminated_union! { + ClickStackBarChartConfig, "configType" { + "sql" => ClickStackBarRawSqlChartConfig, + none unless "connectionId" | "sqlTemplate" => ClickStackBarBuilderChartConfig, + } +} + impl std::fmt::Display for ClickStackBarChartConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -7918,15 +8003,29 @@ impl std::fmt::Display for ClickStackBarChartConfig { } /// `ClickStackCategoricalBarChartConfig` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `configType` field (absent or non-string dispatches to the +/// builder variant, unless the payload carries a raw-SQL-only key); see the +/// `discriminated_union!` invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackCategoricalBarChartConfig { ClickStackCategoricalBarBuilderChartConfig(ClickStackCategoricalBarBuilderChartConfig), ClickStackCategoricalBarRawSqlChartConfig(ClickStackCategoricalBarRawSqlChartConfig), /// Catch-all for unknown or newly-added values. + /// + /// Holds the raw payload as `serde_json::Value` so it round-trips + /// losslessly; its `Display` emits the payload as compact JSON. Unknown(serde_json::Value), } +discriminated_union! { + ClickStackCategoricalBarChartConfig, "configType" { + "sql" => ClickStackCategoricalBarRawSqlChartConfig, + none unless "connectionId" | "sqlTemplate" => ClickStackCategoricalBarBuilderChartConfig, + } +} + impl Default for ClickStackCategoricalBarChartConfig { fn default() -> Self { Self::ClickStackCategoricalBarBuilderChartConfig( @@ -7992,15 +8091,29 @@ impl std::fmt::Display for ClickStackDashboardChartSeries { } /// `ClickStackLineChartConfig` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `configType` field (absent or non-string dispatches to the +/// builder variant, unless the payload carries a raw-SQL-only key); see the +/// `discriminated_union!` invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackLineChartConfig { ClickStackLineBuilderChartConfig(ClickStackLineBuilderChartConfig), ClickStackLineRawSqlChartConfig(ClickStackLineRawSqlChartConfig), /// Catch-all for unknown or newly-added values. + /// + /// Holds the raw payload as `serde_json::Value` so it round-trips + /// losslessly; its `Display` emits the payload as compact JSON. Unknown(serde_json::Value), } +discriminated_union! { + ClickStackLineChartConfig, "configType" { + "sql" => ClickStackLineRawSqlChartConfig, + none unless "connectionId" | "sqlTemplate" => ClickStackLineBuilderChartConfig, + } +} + impl std::fmt::Display for ClickStackLineChartConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -8016,15 +8129,29 @@ impl std::fmt::Display for ClickStackLineChartConfig { } /// `ClickStackNumberChartConfig` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `configType` field (absent or non-string dispatches to the +/// builder variant, unless the payload carries a raw-SQL-only key); see the +/// `discriminated_union!` invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackNumberChartConfig { ClickStackNumberBuilderChartConfig(ClickStackNumberBuilderChartConfig), ClickStackNumberRawSqlChartConfig(ClickStackNumberRawSqlChartConfig), /// Catch-all for unknown or newly-added values. + /// + /// Holds the raw payload as `serde_json::Value` so it round-trips + /// losslessly; its `Display` emits the payload as compact JSON. Unknown(serde_json::Value), } +discriminated_union! { + ClickStackNumberChartConfig, "configType" { + "sql" => ClickStackNumberRawSqlChartConfig, + none unless "connectionId" | "sqlTemplate" => ClickStackNumberBuilderChartConfig, + } +} + impl std::fmt::Display for ClickStackNumberChartConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -8167,15 +8294,29 @@ impl std::fmt::Display for ClickStackOnClickTarget { } /// `ClickStackPieChartConfig` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `configType` field (absent or non-string dispatches to the +/// builder variant, unless the payload carries a raw-SQL-only key); see the +/// `discriminated_union!` invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackPieChartConfig { ClickStackPieBuilderChartConfig(ClickStackPieBuilderChartConfig), ClickStackPieRawSqlChartConfig(ClickStackPieRawSqlChartConfig), /// Catch-all for unknown or newly-added values. + /// + /// Holds the raw payload as `serde_json::Value` so it round-trips + /// losslessly; its `Display` emits the payload as compact JSON. Unknown(serde_json::Value), } +discriminated_union! { + ClickStackPieChartConfig, "configType" { + "sql" => ClickStackPieRawSqlChartConfig, + none unless "connectionId" | "sqlTemplate" => ClickStackPieBuilderChartConfig, + } +} + impl std::fmt::Display for ClickStackPieChartConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -8231,15 +8372,29 @@ impl std::fmt::Display for ClickStackSource { } /// `ClickStackTableChartConfig` - one of multiple variants. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// +/// Dispatched on the `configType` field (absent or non-string dispatches to the +/// builder variant, unless the payload carries a raw-SQL-only key); see the +/// `discriminated_union!` invocation below for the wire values. +#[derive(Debug, Clone, PartialEq, Serialize)] #[serde(untagged)] pub enum ClickStackTableChartConfig { ClickStackTableBuilderChartConfig(ClickStackTableBuilderChartConfig), ClickStackTableRawSqlChartConfig(ClickStackTableRawSqlChartConfig), /// Catch-all for unknown or newly-added values. + /// + /// Holds the raw payload as `serde_json::Value` so it round-trips + /// losslessly; its `Display` emits the payload as compact JSON. Unknown(serde_json::Value), } +discriminated_union! { + ClickStackTableChartConfig, "configType" { + "sql" => ClickStackTableRawSqlChartConfig, + none unless "connectionId" | "sqlTemplate" => ClickStackTableBuilderChartConfig, + } +} + impl std::fmt::Display for ClickStackTableChartConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -9028,7 +9183,7 @@ pub struct ClickPipeMongoDBPipeSettings { default )] pub pull_batch_size: Option, - #[serde(rename = "replicationMode")] + #[serde(rename = "replicationMode", default)] pub replication_mode: ClickPipeMongoDBPipeSettingsReplicationmode, #[serde( rename = "snapshotNumRowsPerPartition", @@ -9059,9 +9214,9 @@ pub struct ClickPipeMongoDBPipeSettings { /// `ClickPipeMongoDBPipeTableMapping` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickPipeMongoDBPipeTableMapping { - #[serde(rename = "sourceCollection")] + #[serde(rename = "sourceCollection", default)] pub source_collection: String, - #[serde(rename = "sourceDatabaseName")] + #[serde(rename = "sourceDatabaseName", default)] pub source_database_name: String, #[serde( rename = "tableEngine", @@ -9069,7 +9224,7 @@ pub struct ClickPipeMongoDBPipeTableMapping { default )] pub table_engine: Option, - #[serde(rename = "targetTable")] + #[serde(rename = "targetTable", default)] pub target_table: String, } @@ -9088,7 +9243,7 @@ pub struct ClickPipeMongoDBSource { default )] pub disable_tls: Option, - #[serde(rename = "readPreference")] + #[serde(rename = "readPreference", default)] pub read_preference: ClickPipeMongoDBSourceReadpreference, #[serde(skip_serializing_if = "Option::is_none", default)] pub settings: Option, @@ -9106,6 +9261,7 @@ pub struct ClickPipeMongoDBSource { pub table_mappings: Option>, #[serde(rename = "tlsHost", skip_serializing_if = "Option::is_none", default)] pub tls_host: Option, + #[serde(default)] pub uri: String, } @@ -9186,8 +9342,9 @@ pub struct ClickPipeMutateMongoDBSource { default )] pub disable_tls: Option, - #[serde(rename = "readPreference")] + #[serde(rename = "readPreference", default)] pub read_preference: ClickPipeMutateMongoDBSourceReadpreference, + #[serde(default)] pub settings: ClickPipeMongoDBPipeSettings, #[serde( rename = "skipCertVerification", @@ -9195,10 +9352,11 @@ pub struct ClickPipeMutateMongoDBSource { default )] pub skip_cert_verification: Option, - #[serde(rename = "tableMappings")] + #[serde(rename = "tableMappings", default)] pub table_mappings: Vec, #[serde(rename = "tlsHost", skip_serializing_if = "Option::is_none", default)] pub tls_host: Option, + #[serde(default)] pub uri: String, } @@ -9221,12 +9379,15 @@ pub struct ClickPipeMutateMySQLSource { default )] pub disable_tls: Option, + #[serde(default)] pub host: String, #[serde(rename = "iamRole", skip_serializing_if = "Option::is_none", default)] pub iam_role: Option, + #[serde(default)] pub port: i64, #[serde(rename = "serverId", skip_serializing_if = "Option::is_none", default)] pub server_id: Option, + #[serde(default)] pub settings: ClickPipeMySQLPipeSettings, #[serde( rename = "skipCertVerification", @@ -9234,7 +9395,7 @@ pub struct ClickPipeMutateMySQLSource { default )] pub skip_cert_verification: Option, - #[serde(rename = "tableMappings")] + #[serde(rename = "tableMappings", default)] pub table_mappings: Vec, #[serde(rename = "tlsHost", skip_serializing_if = "Option::is_none", default)] pub tls_host: Option, @@ -9318,7 +9479,7 @@ pub struct ClickPipeMySQLPipeSettings { default )] pub replication_mechanism: Option, - #[serde(rename = "replicationMode")] + #[serde(rename = "replicationMode", default)] pub replication_mode: ClickPipeMySQLPipeSettingsReplicationmode, #[serde( rename = "snapshotNumRowsPerPartition", @@ -9367,9 +9528,9 @@ pub struct ClickPipeMySQLPipeTableMapping { default )] pub sorting_keys: Option>, - #[serde(rename = "sourceSchemaName")] + #[serde(rename = "sourceSchemaName", default)] pub source_schema_name: String, - #[serde(rename = "sourceTable")] + #[serde(rename = "sourceTable", default)] pub source_table: String, #[serde( rename = "tableEngine", @@ -9377,7 +9538,7 @@ pub struct ClickPipeMySQLPipeTableMapping { default )] pub table_engine: Option, - #[serde(rename = "targetTable")] + #[serde(rename = "targetTable", default)] pub target_table: String, #[serde( rename = "useCustomSortingKey", @@ -9404,12 +9565,15 @@ pub struct ClickPipeMySQLSource { default )] pub disable_tls: Option, + #[serde(default)] pub host: String, #[serde(rename = "iamRole", skip_serializing_if = "Option::is_none", default)] pub iam_role: Option, + #[serde(default)] pub port: i64, #[serde(rename = "serverId", skip_serializing_if = "Option::is_none", default)] pub server_id: Option, + #[serde(default)] pub settings: ClickPipeMySQLPipeSettings, #[serde( rename = "skipCertVerification", @@ -9417,7 +9581,7 @@ pub struct ClickPipeMySQLSource { default )] pub skip_cert_verification: Option, - #[serde(rename = "tableMappings")] + #[serde(rename = "tableMappings", default)] pub table_mappings: Vec, #[serde(rename = "tlsHost", skip_serializing_if = "Option::is_none", default)] pub tls_host: Option, @@ -9522,9 +9686,9 @@ pub struct ClickPipePatchKinesisSource { /// `ClickPipePatchMongoDBPipeRemoveTableMapping` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickPipePatchMongoDBPipeRemoveTableMapping { - #[serde(rename = "sourceCollection")] + #[serde(rename = "sourceCollection", default)] pub source_collection: Option, - #[serde(rename = "sourceDatabaseName")] + #[serde(rename = "sourceDatabaseName", default)] pub source_database_name: Option, #[serde( rename = "tableEngine", @@ -9532,7 +9696,7 @@ pub struct ClickPipePatchMongoDBPipeRemoveTableMapping { default )] pub table_engine: Option, - #[serde(rename = "targetTable")] + #[serde(rename = "targetTable", default)] pub target_table: Option, } @@ -9598,6 +9762,7 @@ pub struct ClickPipePatchMongoDBSource { pub table_mappings_to_remove: Option>, #[serde(rename = "tlsHost", skip_serializing_if = "Option::is_none", default)] pub tls_host: Option, + #[serde(default)] pub uri: Option, } @@ -9610,9 +9775,9 @@ pub struct ClickPipePatchMySQLPipeRemoveTableMapping { default )] pub partition_key: Option, - #[serde(rename = "sourceSchemaName")] + #[serde(rename = "sourceSchemaName", default)] pub source_schema_name: Option, - #[serde(rename = "sourceTable")] + #[serde(rename = "sourceTable", default)] pub source_table: Option, #[serde( rename = "tableEngine", @@ -9620,7 +9785,7 @@ pub struct ClickPipePatchMySQLPipeRemoveTableMapping { default )] pub table_engine: Option, - #[serde(rename = "targetTable")] + #[serde(rename = "targetTable", default)] pub target_table: Option, } @@ -9666,9 +9831,11 @@ pub struct ClickPipePatchMySQLSource { default )] pub disable_tls: Option, + #[serde(default)] pub host: Option, #[serde(rename = "iamRole", skip_serializing_if = "Option::is_none", default)] pub iam_role: Option, + #[serde(default)] pub port: Option, #[serde(rename = "serverId", skip_serializing_if = "Option::is_none", default)] pub server_id: Option, @@ -9845,8 +10012,9 @@ pub struct ClickPipePatchPubSubSource { default )] pub ack_deadline: Option, + #[serde(default)] pub authentication: Option, - #[serde(rename = "serviceAccountKey")] + #[serde(rename = "serviceAccountKey", default)] pub service_account_key: Option, } @@ -10076,6 +10244,7 @@ pub struct ClickPipePostPubSubSource { default )] pub ack_deadline: Option, + #[serde(default)] pub authentication: ClickPipePostPubSubSourceAuthentication, #[serde( rename = "enableOrdering", @@ -10085,8 +10254,9 @@ pub struct ClickPipePostPubSubSource { pub enable_ordering: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub filter: Option, + #[serde(default)] pub format: ClickPipePostPubSubSourceFormat, - #[serde(rename = "projectId")] + #[serde(rename = "projectId", default)] pub project_id: String, #[serde( rename = "seekTimestamp", @@ -10094,10 +10264,11 @@ pub struct ClickPipePostPubSubSource { default )] pub seek_timestamp: Option>, - #[serde(rename = "seekType")] + #[serde(rename = "seekType", default)] pub seek_type: ClickPipePostPubSubSourceSeektype, - #[serde(rename = "serviceAccountKey")] + #[serde(rename = "serviceAccountKey", default)] pub service_account_key: ServiceAccount, + #[serde(default)] pub topic: String, } @@ -10271,6 +10442,7 @@ pub struct ClickPipePubSubSource { default )] pub ack_deadline: Option, + #[serde(default)] pub authentication: ClickPipePubSubSourceAuthentication, #[serde( rename = "enableOrdering", @@ -10280,8 +10452,9 @@ pub struct ClickPipePubSubSource { pub enable_ordering: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub filter: Option, + #[serde(default)] pub format: ClickPipePubSubSourceFormat, - #[serde(rename = "projectId")] + #[serde(rename = "projectId", default)] pub project_id: String, #[serde( rename = "seekTimestamp", @@ -10289,8 +10462,9 @@ pub struct ClickPipePubSubSource { default )] pub seek_timestamp: Option>, - #[serde(rename = "seekType")] + #[serde(rename = "seekType", default)] pub seek_type: ClickPipePubSubSourceSeektype, + #[serde(default)] pub topic: String, } @@ -10449,9 +10623,9 @@ pub struct ClickPipesCdcScalingPatchRequest { /// `ClickStackAggregatedColumn` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackAggregatedColumn { - #[serde(rename = "aggFn")] + #[serde(rename = "aggFn", default)] pub agg_fn: String, - #[serde(rename = "mvColumn")] + #[serde(rename = "mvColumn", default)] pub mv_column: String, #[serde( rename = "sourceColumn", @@ -10464,8 +10638,9 @@ pub struct ClickStackAggregatedColumn { /// `ClickStackAlertChannelEmail` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackAlertChannelEmail { - #[serde(rename = "emailRecipients")] + #[serde(rename = "emailRecipients", default)] pub email_recipients: Vec, + #[serde(default)] pub r#type: ClickStackAlertChannelEmailType, } @@ -10480,8 +10655,9 @@ pub struct ClickStackAlertChannelWebhook { default )] pub slack_channel_id: Option, + #[serde(default)] pub r#type: ClickStackAlertChannelWebhookType, - #[serde(rename = "webhookId")] + #[serde(rename = "webhookId", default)] pub webhook_id: String, #[serde( rename = "webhookService", @@ -10593,6 +10769,7 @@ pub struct ClickStackAlertSilenced { pub struct ClickStackBackgroundChart { #[serde(skip_serializing_if = "Option::is_none", default)] pub color: Option, + #[serde(default)] pub r#type: ClickStackBackgroundChartType, } @@ -10607,7 +10784,7 @@ pub struct ClickStackBarBuilderChartConfig { pub align_date_range_to_granularity: Option, #[serde(rename = "asRatio", skip_serializing_if = "Option::is_none", default)] pub as_ratio: Option, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackBarBuilderChartConfigDisplaytype, #[serde(rename = "fillNulls", skip_serializing_if = "Option::is_none", default)] pub fill_nulls: Option, @@ -10619,8 +10796,9 @@ pub struct ClickStackBarBuilderChartConfig { default )] pub number_format: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, } @@ -10633,11 +10811,11 @@ pub struct ClickStackBarRawSqlChartConfig { default )] pub align_date_range_to_granularity: Option, - #[serde(rename = "configType")] + #[serde(rename = "configType", default)] pub config_type: ClickStackBarRawSqlChartConfigConfigtype, - #[serde(rename = "connectionId")] + #[serde(rename = "connectionId", default)] pub connection_id: String, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackBarRawSqlChartConfigDisplaytype, #[serde(rename = "fillNulls", skip_serializing_if = "Option::is_none", default)] pub fill_nulls: Option, @@ -10649,23 +10827,27 @@ pub struct ClickStackBarRawSqlChartConfig { pub number_format: Option, #[serde(rename = "sourceId", skip_serializing_if = "Option::is_none", default)] pub source_id: Option, - #[serde(rename = "sqlTemplate")] + #[serde(rename = "sqlTemplate", default)] pub sql_template: String, } /// `ClickStackBetweenColorCondition` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackBetweenColorCondition { + #[serde(default)] pub color: ClickStackChartColor, #[serde(skip_serializing_if = "Option::is_none", default)] pub label: Option, + #[serde(default)] pub operator: ClickStackBetweenColorConditionOperator, + #[serde(default)] pub value: Vec, } /// `ClickStackCASLPermission` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackCASLPermission { + #[serde(default)] pub action: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub conditions: Option, @@ -10673,13 +10855,14 @@ pub struct ClickStackCASLPermission { pub integration: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub inverted: Option, + #[serde(default)] pub subject: String, } /// `ClickStackCategoricalBarBuilderChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackCategoricalBarBuilderChartConfig { - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackCategoricalBarBuilderChartConfigDisplaytype, #[serde(rename = "groupBy", skip_serializing_if = "Option::is_none", default)] pub group_by: Option, @@ -10693,19 +10876,20 @@ pub struct ClickStackCategoricalBarBuilderChartConfig { pub number_format: Option, #[serde(rename = "orderBy", skip_serializing_if = "Option::is_none", default)] pub order_by: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, } /// `ClickStackCategoricalBarRawSqlChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackCategoricalBarRawSqlChartConfig { - #[serde(rename = "configType")] + #[serde(rename = "configType", default)] pub config_type: ClickStackCategoricalBarRawSqlChartConfigConfigtype, - #[serde(rename = "connectionId")] + #[serde(rename = "connectionId", default)] pub connection_id: String, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackCategoricalBarRawSqlChartConfigDisplaytype, #[serde( rename = "numberFormat", @@ -10715,7 +10899,7 @@ pub struct ClickStackCategoricalBarRawSqlChartConfig { pub number_format: Option, #[serde(rename = "sourceId", skip_serializing_if = "Option::is_none", default)] pub source_id: Option, - #[serde(rename = "sqlTemplate")] + #[serde(rename = "sqlTemplate", default)] pub sql_template: String, } @@ -10724,6 +10908,7 @@ pub struct ClickStackCategoricalBarRawSqlChartConfig { pub struct ClickStackConnection { #[serde(rename = "createdAt", skip_serializing_if = "Option::is_none", default)] pub created_at: Option>, + #[serde(default)] pub host: String, #[serde( rename = "hyperdxSettingPrefix", @@ -10731,6 +10916,7 @@ pub struct ClickStackConnection { default )] pub hyperdx_setting_prefix: Option, + #[serde(default)] pub id: String, #[serde( rename = "isPrometheusEndpoint", @@ -10738,9 +10924,11 @@ pub struct ClickStackConnection { default )] pub is_prometheus_endpoint: Option, + #[serde(default)] pub name: String, #[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none", default)] pub updated_at: Option>, + #[serde(default)] pub username: String, } @@ -10808,6 +10996,7 @@ pub struct ClickStackCreateAlertRequest { /// `ClickStackCreateConnectionRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackCreateConnectionRequest { + #[serde(default)] pub host: String, #[serde( rename = "hyperdxSettingPrefix", @@ -10821,9 +11010,11 @@ pub struct ClickStackCreateConnectionRequest { default )] pub is_prometheus_endpoint: Option, + #[serde(default)] pub name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub password: Option, + #[serde(default)] pub username: String, } @@ -10834,6 +11025,7 @@ pub struct ClickStackCreateDashboardRequest { pub containers: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub filters: Option>, + #[serde(default)] pub name: String, #[serde( rename = "savedFilterValues", @@ -10855,6 +11047,7 @@ pub struct ClickStackCreateDashboardRequest { pub saved_query_language: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option>, + #[serde(default)] pub tiles: Vec, } @@ -10863,7 +11056,9 @@ pub struct ClickStackCreateDashboardRequest { pub struct ClickStackCreateRoleRequest { #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub name: String, + #[serde(default)] pub permissions: Vec, } @@ -10872,19 +11067,24 @@ pub struct ClickStackCreateRoleRequest { pub struct ClickStackDashboardContainer { #[serde(skip_serializing_if = "Option::is_none", default)] pub bordered: Option, + #[serde(default)] pub collapsed: bool, #[serde(skip_serializing_if = "Option::is_none", default)] pub collapsible: Option, + #[serde(default)] pub id: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub tabs: Option>, + #[serde(default)] pub title: String, } /// `ClickStackDashboardContainerTab` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackDashboardContainerTab { + #[serde(default)] pub id: String, + #[serde(default)] pub title: String, } @@ -10926,22 +11126,25 @@ pub struct ClickStackDashboardResponse { /// `ClickStackEqualityColorCondition` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackEqualityColorCondition { + #[serde(default)] pub color: ClickStackChartColor, #[serde(skip_serializing_if = "Option::is_none", default)] pub label: Option, + #[serde(default)] pub operator: ClickStackEqualityColorConditionOperator, /// A finite number or a string; the spec models this as `oneOf number|string`. + #[serde(default)] pub value: serde_json::Value, } /// `ClickStackEventPatternsChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackEventPatternsChartConfig { - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackEventPatternsChartConfigDisplaytype, #[serde(skip_serializing_if = "Option::is_none", default)] pub select: Option, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#where: Option, @@ -10962,10 +11165,13 @@ pub struct ClickStackFilter { default )] pub applies_to_source_ids: Option>, + #[serde(default)] pub expression: String, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde( rename = "sourceMetricType", @@ -10973,6 +11179,7 @@ pub struct ClickStackFilter { default )] pub source_metric_type: Option, + #[serde(default)] pub r#type: ClickStackFilterType, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#where: Option, @@ -10993,9 +11200,11 @@ pub struct ClickStackFilterInput { default )] pub applies_to_source_ids: Option>, + #[serde(default)] pub expression: String, + #[serde(default)] pub name: String, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde( rename = "sourceMetricType", @@ -11003,6 +11212,7 @@ pub struct ClickStackFilterInput { default )] pub source_metric_type: Option, + #[serde(default)] pub r#type: ClickStackFilterInputType, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#where: Option, @@ -11017,7 +11227,9 @@ pub struct ClickStackFilterInput { /// `ClickStackFilterSettingsColumn` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackFilterSettingsColumn { + #[serde(default)] pub label: String, + #[serde(default)] pub name: String, } @@ -11026,14 +11238,17 @@ pub struct ClickStackFilterSettingsColumn { pub struct ClickStackGenericWebhook { #[serde(skip_serializing_if = "Option::is_none", default)] pub body: Option, - #[serde(rename = "createdAt")] + #[serde(rename = "createdAt", default)] pub created_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, + #[serde(default)] pub service: ClickStackGenericWebhookService, - #[serde(rename = "updatedAt")] + #[serde(rename = "updatedAt", default)] pub updated_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub url: Option, @@ -11042,7 +11257,7 @@ pub struct ClickStackGenericWebhook { /// `ClickStackHeatmapChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackHeatmapChartConfig { - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackHeatmapChartConfigDisplaytype, #[serde( rename = "numberFormat", @@ -11050,8 +11265,9 @@ pub struct ClickStackHeatmapChartConfig { default )] pub number_format: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde(rename = "where", skip_serializing_if = "Option::is_none", default)] pub r#where: Option, @@ -11078,7 +11294,7 @@ pub struct ClickStackHeatmapSelectItem { default )] pub heatmap_scale_type: Option, - #[serde(rename = "valueExpression")] + #[serde(rename = "valueExpression", default)] pub value_expression: String, } @@ -11093,21 +11309,24 @@ pub struct ClickStackHighlightedAttributeExpression { default )] pub lucene_expression: Option, - #[serde(rename = "sqlExpression")] + #[serde(rename = "sqlExpression", default)] pub sql_expression: String, } /// `ClickStackIncidentIOWebhook` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackIncidentIOWebhook { - #[serde(rename = "createdAt")] + #[serde(rename = "createdAt", default)] pub created_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, + #[serde(default)] pub service: ClickStackIncidentIOWebhookService, - #[serde(rename = "updatedAt")] + #[serde(rename = "updatedAt", default)] pub updated_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub url: Option, @@ -11130,7 +11349,7 @@ pub struct ClickStackLineBuilderChartConfig { default )] pub compare_to_previous_period: Option, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackLineBuilderChartConfigDisplaytype, #[serde(rename = "fillNulls", skip_serializing_if = "Option::is_none", default)] pub fill_nulls: Option, @@ -11148,8 +11367,9 @@ pub struct ClickStackLineBuilderChartConfig { default )] pub number_format: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, } @@ -11168,11 +11388,11 @@ pub struct ClickStackLineRawSqlChartConfig { default )] pub compare_to_previous_period: Option, - #[serde(rename = "configType")] + #[serde(rename = "configType", default)] pub config_type: ClickStackLineRawSqlChartConfigConfigtype, - #[serde(rename = "connectionId")] + #[serde(rename = "connectionId", default)] pub connection_id: String, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackLineRawSqlChartConfigDisplaytype, #[serde(rename = "fillNulls", skip_serializing_if = "Option::is_none", default)] pub fill_nulls: Option, @@ -11190,7 +11410,7 @@ pub struct ClickStackLineRawSqlChartConfig { pub number_format: Option, #[serde(rename = "sourceId", skip_serializing_if = "Option::is_none", default)] pub source_id: Option, - #[serde(rename = "sqlTemplate")] + #[serde(rename = "sqlTemplate", default)] pub sql_template: String, } @@ -11203,8 +11423,9 @@ pub struct ClickStackLogSource { default )] pub body_expression: Option, + #[serde(default)] pub connection: String, - #[serde(rename = "defaultTableSelectExpression")] + #[serde(rename = "defaultTableSelectExpression", default)] pub default_table_select_expression: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub disabled: Option, @@ -11226,6 +11447,7 @@ pub struct ClickStackLogSource { default )] pub filter_settings: Option, + #[serde(default)] pub from: ClickStackSourceFrom, #[serde( rename = "highlightedRowAttributeExpressions", @@ -11249,6 +11471,7 @@ pub struct ClickStackLogSource { default )] pub implicit_column_expression: Option, + #[serde(default)] pub kind: ClickStackLogSourceKind, #[serde( rename = "knownColumnsListExpression", @@ -11274,6 +11497,7 @@ pub struct ClickStackLogSource { default )] pub metric_source_id: Option, + #[serde(default)] pub name: String, #[serde( rename = "querySettings", @@ -11307,7 +11531,7 @@ pub struct ClickStackLogSource { default )] pub span_id_expression: Option, - #[serde(rename = "timestampValueExpression")] + #[serde(rename = "timestampValueExpression", default)] pub timestamp_value_expression: String, #[serde( rename = "traceIdExpression", @@ -11344,7 +11568,7 @@ pub struct ClickStackLogSourceMetadataMaterializedViews { /// `ClickStackMarkdownChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackMarkdownChartConfig { - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackMarkdownChartConfigDisplaytype, #[serde(skip_serializing_if = "Option::is_none", default)] pub markdown: Option, @@ -11353,38 +11577,43 @@ pub struct ClickStackMarkdownChartConfig { /// `ClickStackMarkdownChartSeries` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackMarkdownChartSeries { + #[serde(default)] pub content: String, + #[serde(default)] pub r#type: ClickStackMarkdownChartSeriesType, } /// `ClickStackMaterializedView` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackMaterializedView { - #[serde(rename = "aggregatedColumns")] + #[serde(rename = "aggregatedColumns", default)] pub aggregated_columns: Vec, - #[serde(rename = "databaseName")] + #[serde(rename = "databaseName", default)] pub database_name: String, - #[serde(rename = "dimensionColumns")] + #[serde(rename = "dimensionColumns", default)] pub dimension_columns: String, #[serde(rename = "minDate", skip_serializing_if = "Option::is_none", default)] pub min_date: Option>, - #[serde(rename = "minGranularity")] + #[serde(rename = "minGranularity", default)] pub min_granularity: ClickStackMaterializedViewMingranularity, - #[serde(rename = "tableName")] + #[serde(rename = "tableName", default)] pub table_name: String, - #[serde(rename = "timestampColumn")] + #[serde(rename = "timestampColumn", default)] pub timestamp_column: String, } /// `ClickStackMetricSource` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackMetricSource { + #[serde(default)] pub connection: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub disabled: Option, + #[serde(default)] pub from: ClickStackMetricSourceFrom, #[serde(skip_serializing_if = "Option::is_none", default)] pub id: Option, + #[serde(default)] pub kind: ClickStackMetricSourceKind, #[serde( rename = "logSourceId", @@ -11392,8 +11621,9 @@ pub struct ClickStackMetricSource { default )] pub log_source_id: Option, - #[serde(rename = "metricTables")] + #[serde(rename = "metricTables", default)] pub metric_tables: ClickStackMetricTables, + #[serde(default)] pub name: String, #[serde( rename = "querySettings", @@ -11401,18 +11631,18 @@ pub struct ClickStackMetricSource { default )] pub query_settings: Option>, - #[serde(rename = "resourceAttributesExpression")] + #[serde(rename = "resourceAttributesExpression", default)] pub resource_attributes_expression: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub section: Option, - #[serde(rename = "timestampValueExpression")] + #[serde(rename = "timestampValueExpression", default)] pub timestamp_value_expression: String, } /// `ClickStackMetricSourceFrom` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackMetricSourceFrom { - #[serde(rename = "databaseName")] + #[serde(rename = "databaseName", default)] pub database_name: String, #[serde(rename = "tableName", skip_serializing_if = "Option::is_none", default)] pub table_name: Option, @@ -11450,7 +11680,7 @@ pub struct ClickStackNumberBuilderChartConfig { default )] pub color_rules: Option>, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackNumberBuilderChartConfigDisplaytype, #[serde( rename = "numberFormat", @@ -11458,15 +11688,16 @@ pub struct ClickStackNumberBuilderChartConfig { default )] pub number_format: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, } /// `ClickStackNumberChartSeries` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackNumberChartSeries { - #[serde(rename = "aggFn")] + #[serde(rename = "aggFn", default)] pub agg_fn: ClickStackNumberChartSeriesAggfn, #[serde(skip_serializing_if = "Option::is_none", default)] pub alias: Option, @@ -11492,11 +11723,13 @@ pub struct ClickStackNumberChartSeries { default )] pub number_format: Option, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, + #[serde(default)] pub r#type: ClickStackNumberChartSeriesType, + #[serde(default)] pub r#where: String, - #[serde(rename = "whereLanguage")] + #[serde(rename = "whereLanguage", default)] pub where_language: ClickStackNumberChartSeriesWherelanguage, } @@ -11528,11 +11761,11 @@ pub struct ClickStackNumberFormat { pub struct ClickStackNumberRawSqlChartConfig { #[serde(skip_serializing_if = "Option::is_none", default)] pub color: Option, - #[serde(rename = "configType")] + #[serde(rename = "configType", default)] pub config_type: ClickStackNumberRawSqlChartConfigConfigtype, - #[serde(rename = "connectionId")] + #[serde(rename = "connectionId", default)] pub connection_id: String, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackNumberRawSqlChartConfigDisplaytype, #[serde( rename = "numberFormat", @@ -11542,17 +11775,20 @@ pub struct ClickStackNumberRawSqlChartConfig { pub number_format: Option, #[serde(rename = "sourceId", skip_serializing_if = "Option::is_none", default)] pub source_id: Option, - #[serde(rename = "sqlTemplate")] + #[serde(rename = "sqlTemplate", default)] pub sql_template: String, } /// `ClickStackNumericColorCondition` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackNumericColorCondition { + #[serde(default)] pub color: ClickStackChartColor, #[serde(skip_serializing_if = "Option::is_none", default)] pub label: Option, + #[serde(default)] pub operator: ClickStackNumericColorConditionOperator, + #[serde(default)] pub value: f64, } @@ -11561,7 +11797,9 @@ pub struct ClickStackNumericColorCondition { pub struct ClickStackOnClickDashboard { #[serde(skip_serializing_if = "Option::is_none", default)] pub filters: Option>, + #[serde(default)] pub target: ClickStackOnClickTarget, + #[serde(default)] pub r#type: ClickStackOnClickDashboardType, #[serde( rename = "whereLanguage", @@ -11580,16 +11818,20 @@ pub struct ClickStackOnClickDashboard { /// `ClickStackOnClickExternal` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackOnClickExternal { + #[serde(default)] pub r#type: ClickStackOnClickExternalType, - #[serde(rename = "urlTemplate")] + #[serde(rename = "urlTemplate", default)] pub url_template: String, } /// `ClickStackOnClickFilterTemplate` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackOnClickFilterTemplate { + #[serde(default)] pub expression: String, + #[serde(default)] pub kind: ClickStackOnClickFilterTemplateKind, + #[serde(default)] pub template: String, } @@ -11598,7 +11840,9 @@ pub struct ClickStackOnClickFilterTemplate { pub struct ClickStackOnClickSearch { #[serde(skip_serializing_if = "Option::is_none", default)] pub filters: Option>, + #[serde(default)] pub target: ClickStackOnClickTarget, + #[serde(default)] pub r#type: ClickStackOnClickSearchType, #[serde( rename = "whereLanguage", @@ -11617,28 +11861,35 @@ pub struct ClickStackOnClickSearch { /// `ClickStackOnClickTargetIdVariant` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackOnClickTargetIdVariant { + #[serde(default)] pub id: String, + #[serde(default)] pub mode: ClickStackOnClickTargetIdVariantMode, } /// `ClickStackOnClickTargetTemplateVariant` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackOnClickTargetTemplateVariant { + #[serde(default)] pub mode: ClickStackOnClickTargetTemplateVariantMode, + #[serde(default)] pub template: String, } /// `ClickStackPagerDutyAPIWebhook` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackPagerDutyAPIWebhook { - #[serde(rename = "createdAt")] + #[serde(rename = "createdAt", default)] pub created_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, + #[serde(default)] pub service: ClickStackPagerDutyAPIWebhookService, - #[serde(rename = "updatedAt")] + #[serde(rename = "updatedAt", default)] pub updated_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub url: Option, @@ -11647,7 +11898,7 @@ pub struct ClickStackPagerDutyAPIWebhook { /// `ClickStackPieBuilderChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackPieBuilderChartConfig { - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackPieBuilderChartConfigDisplaytype, #[serde(rename = "groupBy", skip_serializing_if = "Option::is_none", default)] pub group_by: Option, @@ -11661,19 +11912,20 @@ pub struct ClickStackPieBuilderChartConfig { pub number_format: Option, #[serde(rename = "orderBy", skip_serializing_if = "Option::is_none", default)] pub order_by: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, } /// `ClickStackPieRawSqlChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackPieRawSqlChartConfig { - #[serde(rename = "configType")] + #[serde(rename = "configType", default)] pub config_type: ClickStackPieRawSqlChartConfigConfigtype, - #[serde(rename = "connectionId")] + #[serde(rename = "connectionId", default)] pub connection_id: String, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackPieRawSqlChartConfigDisplaytype, #[serde( rename = "numberFormat", @@ -11683,20 +11935,24 @@ pub struct ClickStackPieRawSqlChartConfig { pub number_format: Option, #[serde(rename = "sourceId", skip_serializing_if = "Option::is_none", default)] pub source_id: Option, - #[serde(rename = "sqlTemplate")] + #[serde(rename = "sqlTemplate", default)] pub sql_template: String, } /// `ClickStackPromqlSource` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackPromqlSource { + #[serde(default)] pub connection: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub disabled: Option, + #[serde(default)] pub from: ClickStackSourceFrom, #[serde(skip_serializing_if = "Option::is_none", default)] pub id: Option, + #[serde(default)] pub kind: ClickStackPromqlSourceKind, + #[serde(default)] pub name: String, #[serde( rename = "querySettings", @@ -11706,14 +11962,16 @@ pub struct ClickStackPromqlSource { pub query_settings: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub section: Option, - #[serde(rename = "timestampValueExpression")] + #[serde(rename = "timestampValueExpression", default)] pub timestamp_value_expression: String, } /// `ClickStackQuerySetting` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackQuerySetting { + #[serde(default)] pub setting: String, + #[serde(default)] pub value: String, } @@ -11724,10 +11982,13 @@ pub struct ClickStackRole { pub created_at: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub id: String, - #[serde(rename = "isPredefined")] + #[serde(rename = "isPredefined", default)] pub is_predefined: bool, + #[serde(default)] pub name: String, + #[serde(default)] pub permissions: Vec, #[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none", default)] pub updated_at: Option>, @@ -11736,6 +11997,7 @@ pub struct ClickStackRole { /// `ClickStackSavedFilterValue` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSavedFilterValue { + #[serde(default)] pub condition: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#type: Option, @@ -11748,13 +12010,15 @@ pub struct ClickStackSavedSearch { pub created_at: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub filters: Option>, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, #[serde(rename = "orderBy", skip_serializing_if = "Option::is_none", default)] pub order_by: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub select: Option, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option>, @@ -11775,6 +12039,7 @@ pub struct ClickStackSavedSearch { /// `ClickStackSavedSearchFilter` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSavedSearchFilter { + #[serde(default)] pub condition: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#type: Option, @@ -11785,12 +12050,13 @@ pub struct ClickStackSavedSearchFilter { pub struct ClickStackSavedSearchInput { #[serde(skip_serializing_if = "Option::is_none", default)] pub filters: Option>, + #[serde(default)] pub name: String, #[serde(rename = "orderBy", skip_serializing_if = "Option::is_none", default)] pub order_by: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub select: Option, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option>, @@ -11807,33 +12073,37 @@ pub struct ClickStackSavedSearchInput { /// `ClickStackSearchChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSearchChartConfig { - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackSearchChartConfigDisplaytype, + #[serde(default)] pub select: String, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#where: Option, - #[serde(rename = "whereLanguage")] + #[serde(rename = "whereLanguage", default)] pub where_language: ClickStackSearchChartConfigWherelanguage, } /// `ClickStackSearchChartSeries` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSearchChartSeries { + #[serde(default)] pub fields: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, + #[serde(default)] pub r#type: ClickStackSearchChartSeriesType, + #[serde(default)] pub r#where: String, - #[serde(rename = "whereLanguage")] + #[serde(rename = "whereLanguage", default)] pub where_language: ClickStackSearchChartSeriesWherelanguage, } /// `ClickStackSelectItem` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSelectItem { - #[serde(rename = "aggFn")] + #[serde(rename = "aggFn", default)] pub agg_fn: ClickStackSelectItemAggfn, #[serde(skip_serializing_if = "Option::is_none", default)] pub alias: Option, @@ -11882,13 +12152,17 @@ pub struct ClickStackSelectItem { /// `ClickStackSessionSource` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSessionSource { + #[serde(default)] pub connection: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub disabled: Option, + #[serde(default)] pub from: ClickStackSourceFrom, #[serde(skip_serializing_if = "Option::is_none", default)] pub id: Option, + #[serde(default)] pub kind: ClickStackSessionSourceKind, + #[serde(default)] pub name: String, #[serde( rename = "querySettings", @@ -11904,21 +12178,24 @@ pub struct ClickStackSessionSource { default )] pub timestamp_value_expression: Option, - #[serde(rename = "traceSourceId")] + #[serde(rename = "traceSourceId", default)] pub trace_source_id: String, } /// `ClickStackSlackAPIWebhook` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSlackAPIWebhook { - #[serde(rename = "createdAt")] + #[serde(rename = "createdAt", default)] pub created_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, + #[serde(default)] pub service: ClickStackSlackAPIWebhookService, - #[serde(rename = "updatedAt")] + #[serde(rename = "updatedAt", default)] pub updated_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub url: Option, @@ -11927,14 +12204,17 @@ pub struct ClickStackSlackAPIWebhook { /// `ClickStackSlackWebhook` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSlackWebhook { - #[serde(rename = "createdAt")] + #[serde(rename = "createdAt", default)] pub created_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, + #[serde(default)] pub service: ClickStackSlackWebhookService, - #[serde(rename = "updatedAt")] + #[serde(rename = "updatedAt", default)] pub updated_at: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub url: Option, @@ -11943,19 +12223,20 @@ pub struct ClickStackSlackWebhook { /// `ClickStackSourceFilterSettings` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSourceFilterSettings { + #[serde(default)] pub columns: Vec, - #[serde(rename = "databaseName")] + #[serde(rename = "databaseName", default)] pub database_name: String, - #[serde(rename = "tableName")] + #[serde(rename = "tableName", default)] pub table_name: String, } /// `ClickStackSourceFrom` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackSourceFrom { - #[serde(rename = "databaseName")] + #[serde(rename = "databaseName", default)] pub database_name: String, - #[serde(rename = "tableName")] + #[serde(rename = "tableName", default)] pub table_name: String, } @@ -11964,7 +12245,7 @@ pub struct ClickStackSourceFrom { pub struct ClickStackTableBuilderChartConfig { #[serde(rename = "asRatio", skip_serializing_if = "Option::is_none", default)] pub as_ratio: Option, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackTableBuilderChartConfigDisplaytype, #[serde(rename = "groupBy", skip_serializing_if = "Option::is_none", default)] pub group_by: Option, @@ -11986,21 +12267,22 @@ pub struct ClickStackTableBuilderChartConfig { pub on_click: Option, #[serde(rename = "orderBy", skip_serializing_if = "Option::is_none", default)] pub order_by: Option, + #[serde(default)] pub select: Vec, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, } /// `ClickStackTableChartSeries` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackTableChartSeries { - #[serde(rename = "aggFn")] + #[serde(rename = "aggFn", default)] pub agg_fn: ClickStackTableChartSeriesAggfn, #[serde(skip_serializing_if = "Option::is_none", default)] pub alias: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub field: Option, - #[serde(rename = "groupBy")] + #[serde(rename = "groupBy", default)] pub group_by: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub level: Option, @@ -12024,22 +12306,24 @@ pub struct ClickStackTableChartSeries { pub number_format: Option, #[serde(rename = "sortOrder", skip_serializing_if = "Option::is_none", default)] pub sort_order: Option, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, + #[serde(default)] pub r#type: ClickStackTableChartSeriesType, + #[serde(default)] pub r#where: String, - #[serde(rename = "whereLanguage")] + #[serde(rename = "whereLanguage", default)] pub where_language: ClickStackTableChartSeriesWherelanguage, } /// `ClickStackTableRawSqlChartConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackTableRawSqlChartConfig { - #[serde(rename = "configType")] + #[serde(rename = "configType", default)] pub config_type: ClickStackTableRawSqlChartConfigConfigtype, - #[serde(rename = "connectionId")] + #[serde(rename = "connectionId", default)] pub connection_id: String, - #[serde(rename = "displayType")] + #[serde(rename = "displayType", default)] pub display_type: ClickStackTableRawSqlChartConfigDisplaytype, #[serde( rename = "numberFormat", @@ -12051,7 +12335,7 @@ pub struct ClickStackTableRawSqlChartConfig { pub on_click: Option, #[serde(rename = "sourceId", skip_serializing_if = "Option::is_none", default)] pub source_id: Option, - #[serde(rename = "sqlTemplate")] + #[serde(rename = "sqlTemplate", default)] pub sql_template: String, } @@ -12069,17 +12353,22 @@ pub struct ClickStackTileInput { default )] pub container_id: Option, + #[serde(default)] pub h: i64, #[serde(skip_serializing_if = "Option::is_none", default)] pub id: Option, + #[serde(default)] pub name: String, #[cfg(feature = "deprecated-fields")] #[serde(skip_serializing_if = "Option::is_none", default)] pub series: Option>, #[serde(rename = "tabId", skip_serializing_if = "Option::is_none", default)] pub tab_id: Option, + #[serde(default)] pub w: i64, + #[serde(default)] pub x: i64, + #[serde(default)] pub y: i64, } @@ -12094,20 +12383,26 @@ pub struct ClickStackTileOutput { default )] pub container_id: Option, + #[serde(default)] pub h: i64, + #[serde(default)] pub id: String, + #[serde(default)] pub name: String, #[serde(rename = "tabId", skip_serializing_if = "Option::is_none", default)] pub tab_id: Option, + #[serde(default)] pub w: i64, + #[serde(default)] pub x: i64, + #[serde(default)] pub y: i64, } /// `ClickStackTimeChartSeries` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackTimeChartSeries { - #[serde(rename = "aggFn")] + #[serde(rename = "aggFn", default)] pub agg_fn: ClickStackTimeChartSeriesAggfn, #[serde(skip_serializing_if = "Option::is_none", default)] pub alias: Option, @@ -12119,7 +12414,7 @@ pub struct ClickStackTimeChartSeries { pub display_type: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub field: Option, - #[serde(rename = "groupBy")] + #[serde(rename = "groupBy", default)] pub group_by: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub level: Option, @@ -12141,25 +12436,28 @@ pub struct ClickStackTimeChartSeries { default )] pub number_format: Option, - #[serde(rename = "sourceId")] + #[serde(rename = "sourceId", default)] pub source_id: String, + #[serde(default)] pub r#type: ClickStackTimeChartSeriesType, + #[serde(default)] pub r#where: String, - #[serde(rename = "whereLanguage")] + #[serde(rename = "whereLanguage", default)] pub where_language: ClickStackTimeChartSeriesWherelanguage, } /// `ClickStackTraceSource` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackTraceSource { + #[serde(default)] pub connection: String, #[serde(rename = "defaultTableSelectExpression", default)] pub default_table_select_expression: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub disabled: Option, - #[serde(rename = "durationExpression")] + #[serde(rename = "durationExpression", default)] pub duration_expression: String, - #[serde(rename = "durationPrecision")] + #[serde(rename = "durationPrecision", default)] pub duration_precision: i64, #[serde( rename = "eventAttributesExpression", @@ -12173,6 +12471,7 @@ pub struct ClickStackTraceSource { default )] pub filter_settings: Option, + #[serde(default)] pub from: ClickStackSourceFrom, #[serde( rename = "highlightedRowAttributeExpressions", @@ -12196,6 +12495,7 @@ pub struct ClickStackTraceSource { default )] pub implicit_column_expression: Option, + #[serde(default)] pub kind: ClickStackTraceSourceKind, #[serde( rename = "knownColumnsListExpression", @@ -12227,8 +12527,9 @@ pub struct ClickStackTraceSource { default )] pub metric_source_id: Option, + #[serde(default)] pub name: String, - #[serde(rename = "parentSpanIdExpression")] + #[serde(rename = "parentSpanIdExpression", default)] pub parent_span_id_expression: String, #[serde( rename = "querySettings", @@ -12262,11 +12563,11 @@ pub struct ClickStackTraceSource { default )] pub span_events_value_expression: Option, - #[serde(rename = "spanIdExpression")] + #[serde(rename = "spanIdExpression", default)] pub span_id_expression: String, - #[serde(rename = "spanKindExpression")] + #[serde(rename = "spanKindExpression", default)] pub span_kind_expression: String, - #[serde(rename = "spanNameExpression")] + #[serde(rename = "spanNameExpression", default)] pub span_name_expression: String, #[serde( rename = "statusCodeExpression", @@ -12280,9 +12581,9 @@ pub struct ClickStackTraceSource { default )] pub status_message_expression: Option, - #[serde(rename = "timestampValueExpression")] + #[serde(rename = "timestampValueExpression", default)] pub timestamp_value_expression: String, - #[serde(rename = "traceIdExpression")] + #[serde(rename = "traceIdExpression", default)] pub trace_id_expression: String, #[serde( rename = "useTextIndexForImplicitColumn", @@ -12368,6 +12669,7 @@ pub struct ClickStackUpdateAlertRequest { /// `ClickStackUpdateConnectionRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackUpdateConnectionRequest { + #[serde(default)] pub host: String, #[serde( rename = "hyperdxSettingPrefix", @@ -12381,9 +12683,11 @@ pub struct ClickStackUpdateConnectionRequest { default )] pub is_prometheus_endpoint: Option, + #[serde(default)] pub name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub password: Option, + #[serde(default)] pub username: String, } @@ -12394,6 +12698,7 @@ pub struct ClickStackUpdateDashboardRequest { pub containers: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub filters: Option>, + #[serde(default)] pub name: String, #[serde( rename = "savedFilterValues", @@ -12415,6 +12720,7 @@ pub struct ClickStackUpdateDashboardRequest { pub saved_query_language: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option>, + #[serde(default)] pub tiles: Vec, } @@ -12425,21 +12731,27 @@ pub struct ClickStackUpdateRoleRequest { pub description: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub name: Option, + #[serde(default)] pub permissions: Vec, } /// `ClickStackValidateDashboardError` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackValidateDashboardError { + #[serde(default)] pub message: String, + #[serde(default)] pub path: String, } /// `ClickStackValidateDashboardResponse` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ClickStackValidateDashboardResponse { + #[serde(default)] pub errors: Vec, + #[serde(default)] pub normalized: Option, + #[serde(default)] pub valid: bool, } @@ -12452,6 +12764,7 @@ pub struct ClickStackWebhookInput { pub description: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub headers: Option, + #[serde(default)] pub name: String, #[serde( rename = "queryParams", @@ -12461,6 +12774,7 @@ pub struct ClickStackWebhookInput { pub query_params: Option, #[serde(default)] pub service: ClickStackWebhookInputService, + #[serde(default)] pub url: String, } @@ -12834,14 +13148,19 @@ pub struct OrganizationPrivateEndpointsPatch { /// `OrganizationQuota` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct OrganizationQuota { + #[serde(default)] pub adjustable: bool, + #[serde(default)] pub description: String, + #[serde(default)] pub name: String, - #[serde(rename = "quotaCode")] + #[serde(rename = "quotaCode", default)] pub quota_code: OrganizationQuotaQuotacode, + #[serde(default)] pub scope: OrganizationQuotaScope, #[serde(skip_serializing_if = "Option::is_none", default)] pub usage: Option, + #[serde(default)] pub value: i64, } @@ -12943,6 +13262,7 @@ pub struct PostgresServicePatchRequest { pub struct PostgresServicePostRequest { #[serde(rename = "haType", skip_serializing_if = "Option::is_none", default)] pub ha_type: Option, + #[serde(default)] pub name: PgNameProperty, #[serde( rename = "pgBouncerConfig", @@ -12958,8 +13278,11 @@ pub struct PostgresServicePostRequest { default )] pub postgres_version: Option, + #[serde(default)] pub provider: PgProvider, + #[serde(default)] pub region: PgRegion, + #[serde(default)] pub size: PgSize, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option, @@ -12968,6 +13291,7 @@ pub struct PostgresServicePostRequest { /// `PostgresServiceReadReplicaRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct PostgresServiceReadReplicaRequest { + #[serde(default)] pub name: PgNameProperty, #[serde( rename = "pgBouncerConfig", @@ -12984,6 +13308,7 @@ pub struct PostgresServiceReadReplicaRequest { /// `PostgresServiceRestoreRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct PostgresServiceRestoreRequest { + #[serde(default)] pub name: PgNameProperty, #[serde( rename = "pgBouncerConfig", @@ -12993,7 +13318,7 @@ pub struct PostgresServiceRestoreRequest { pub pg_bouncer_config: Option, #[serde(rename = "pgConfig", skip_serializing_if = "Option::is_none", default)] pub pg_config: Option, - #[serde(rename = "restoreTarget")] + #[serde(rename = "restoreTarget", default)] pub restore_target: PgPitrRestoreTargetProperty, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option, @@ -13235,9 +13560,11 @@ pub struct RBACPolicy { /// `RBACPolicyCreateRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct RBACPolicyCreateRequest { - #[serde(rename = "allowDeny")] + #[serde(rename = "allowDeny", default)] pub allow_deny: RBACPolicyCreateRequestAllowdeny, + #[serde(default)] pub permissions: Vec, + #[serde(default)] pub resources: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option, @@ -13278,6 +13605,7 @@ pub struct RBACRole { /// `ResourceTagsV1` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ResourceTagsV1 { + #[serde(default)] pub key: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub value: Option, @@ -13349,8 +13677,11 @@ pub struct ReversePrivateEndpoint { /// `RoleCreateRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct RoleCreateRequest { + #[serde(default)] pub actors: Vec, + #[serde(default)] pub name: String, + #[serde(default)] pub policies: Vec, } @@ -13551,7 +13882,7 @@ pub struct ScimEnterpriseUser { /// `ScimGroup` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimGroup { - #[serde(rename = "displayName")] + #[serde(rename = "displayName", default)] pub display_name: String, #[serde( rename = "externalId", @@ -13559,24 +13890,28 @@ pub struct ScimGroup { default )] pub external_id: Option, + #[serde(default)] pub id: uuid::Uuid, #[serde(skip_serializing_if = "Option::is_none", default)] pub members: Option>, + #[serde(default)] pub meta: ScimGroupMeta, + #[serde(default)] pub schemas: Vec, } /// `ScimGroupListResponse` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimGroupListResponse { - #[serde(rename = "Resources")] + #[serde(rename = "Resources", default)] pub resources: Vec, - #[serde(rename = "itemsPerPage")] + #[serde(rename = "itemsPerPage", default)] pub items_per_page: i64, + #[serde(default)] pub schemas: Vec, - #[serde(rename = "startIndex")] + #[serde(rename = "startIndex", default)] pub start_index: i64, - #[serde(rename = "totalResults")] + #[serde(rename = "totalResults", default)] pub total_results: i64, } @@ -13587,25 +13922,27 @@ pub struct ScimGroupMember { pub display: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#type: Option, + #[serde(default)] pub value: String, } /// `ScimGroupMeta` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimGroupMeta { + #[serde(default)] pub created: chrono::DateTime, - #[serde(rename = "lastModified")] + #[serde(rename = "lastModified", default)] pub last_modified: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub location: Option, - #[serde(rename = "resourceType")] + #[serde(rename = "resourceType", default)] pub resource_type: String, } /// `ScimGroupPostRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimGroupPostRequest { - #[serde(rename = "displayName")] + #[serde(rename = "displayName", default)] pub display_name: String, #[serde( rename = "externalId", @@ -13615,13 +13952,14 @@ pub struct ScimGroupPostRequest { pub external_id: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub members: Option>, + #[serde(default)] pub schemas: Vec, } /// `ScimGroupPutRequest` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimGroupPutRequest { - #[serde(rename = "displayName")] + #[serde(rename = "displayName", default)] pub display_name: String, #[serde( rename = "externalId", @@ -13635,34 +13973,38 @@ pub struct ScimGroupPutRequest { pub members: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub meta: Option, + #[serde(default)] pub schemas: Vec, } /// `ScimListResponse` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimListResponse { - #[serde(rename = "Resources")] + #[serde(rename = "Resources", default)] pub resources: Vec, - #[serde(rename = "itemsPerPage")] + #[serde(rename = "itemsPerPage", default)] pub items_per_page: i64, + #[serde(default)] pub schemas: Vec, - #[serde(rename = "startIndex")] + #[serde(rename = "startIndex", default)] pub start_index: i64, - #[serde(rename = "totalResults")] + #[serde(rename = "totalResults", default)] pub total_results: i64, } /// `ScimPatchOp` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimPatchOp { - #[serde(rename = "Operations")] + #[serde(rename = "Operations", default)] pub operations: Vec, + #[serde(default)] pub schemas: Vec, } /// `ScimPatchOperation` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimPatchOperation { + #[serde(default)] pub op: ScimPatchOperationOp, #[serde(skip_serializing_if = "Option::is_none", default)] pub path: Option, @@ -13673,6 +14015,7 @@ pub struct ScimPatchOperation { /// `ScimUser` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimUser { + #[serde(default)] pub active: bool, #[serde(skip_serializing_if = "Option::is_none", default)] pub addresses: Option>, @@ -13682,6 +14025,7 @@ pub struct ScimUser { default )] pub display_name: Option, + #[serde(default)] pub emails: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub entitlements: Option>, @@ -13693,12 +14037,15 @@ pub struct ScimUser { pub external_id: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub groups: Option>, + #[serde(default)] pub id: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub ims: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub locale: Option, + #[serde(default)] pub meta: ScimUserMeta, + #[serde(default)] pub name: ScimUserName, #[serde(rename = "nickName", skip_serializing_if = "Option::is_none", default)] pub nick_name: Option, @@ -13724,12 +14071,13 @@ pub struct ScimUser { pub profile_url: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub roles: Option>, + #[serde(default)] pub schemas: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub timezone: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub title: Option, - #[serde(rename = "userName")] + #[serde(rename = "userName", default)] pub user_name: String, #[serde(rename = "userType", skip_serializing_if = "Option::is_none", default)] pub user_type: Option, @@ -13769,6 +14117,7 @@ pub struct ScimUserEmail { pub primary: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub r#type: Option, + #[serde(default)] pub value: String, } @@ -13810,12 +14159,13 @@ pub struct ScimUserIm { /// `ScimUserMeta` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimUserMeta { + #[serde(default)] pub created: chrono::DateTime, - #[serde(rename = "lastModified")] + #[serde(rename = "lastModified", default)] pub last_modified: chrono::DateTime, #[serde(skip_serializing_if = "Option::is_none", default)] pub location: Option, - #[serde(rename = "resourceType")] + #[serde(rename = "resourceType", default)] pub resource_type: String, } @@ -13871,6 +14221,7 @@ pub struct ScimUserPostRequest { default )] pub display_name: Option, + #[serde(default)] pub emails: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub entitlements: Option>, @@ -13914,6 +14265,7 @@ pub struct ScimUserPostRequest { pub profile_url: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub roles: Option>, + #[serde(default)] pub schemas: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub timezone: Option, @@ -13925,7 +14277,7 @@ pub struct ScimUserPostRequest { default )] pub urn_ietf_params_scim_schemas_extension_enterprise_2_0_user: Option, - #[serde(rename = "userName")] + #[serde(rename = "userName", default)] pub user_name: String, #[serde(rename = "userType", skip_serializing_if = "Option::is_none", default)] pub user_type: Option, @@ -13950,6 +14302,7 @@ pub struct ScimUserPutRequest { default )] pub display_name: Option, + #[serde(default)] pub emails: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub entitlements: Option>, @@ -13997,6 +14350,7 @@ pub struct ScimUserPutRequest { pub profile_url: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub roles: Option>, + #[serde(default)] pub schemas: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub timezone: Option, @@ -14008,7 +14362,7 @@ pub struct ScimUserPutRequest { default )] pub urn_ietf_params_scim_schemas_extension_enterprise_2_0_user: Option, - #[serde(rename = "userName")] + #[serde(rename = "userName", default)] pub user_name: String, #[serde(rename = "userType", skip_serializing_if = "Option::is_none", default)] pub user_type: Option, @@ -14043,66 +14397,84 @@ pub struct ScimX509Certificate { /// `ScimAuthenticationScheme` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimAuthenticationScheme { + #[serde(default)] pub description: String, + #[serde(default)] pub name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub primary: Option, #[serde(rename = "specUri", skip_serializing_if = "Option::is_none", default)] pub spec_uri: Option, - #[serde(rename = "type")] + #[serde(rename = "type", default)] pub r#type: String, } /// `ScimBooleanFeature` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimBooleanFeature { + #[serde(default)] pub supported: bool, } /// `ScimResourceType` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimResourceType { + #[serde(default)] pub description: String, + #[serde(default)] pub endpoint: String, + #[serde(default)] pub id: String, + #[serde(default)] pub meta: ScimResourceTypeMeta, + #[serde(default)] pub name: String, + #[serde(default)] pub schema: String, - #[serde(rename = "schemaExtensions")] + #[serde(rename = "schemaExtensions", default)] pub schema_extensions: Vec, + #[serde(default)] pub schemas: Vec, } /// `ScimResourceTypeListResponse` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimResourceTypeListResponse { - #[serde(rename = "Resources")] + #[serde(rename = "Resources", default)] pub resources: Vec, - #[serde(rename = "itemsPerPage")] + #[serde(rename = "itemsPerPage", default)] pub items_per_page: i64, + #[serde(default)] pub schemas: Vec, - #[serde(rename = "startIndex")] + #[serde(rename = "startIndex", default)] pub start_index: i64, - #[serde(rename = "totalResults")] + #[serde(rename = "totalResults", default)] pub total_results: i64, } /// `ScimResourceTypeMeta` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimResourceTypeMeta { + #[serde(default)] pub location: String, - #[serde(rename = "resourceType")] + #[serde(rename = "resourceType", default)] pub resource_type: String, } /// `ScimSchema` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimSchema { + #[serde(default)] pub attributes: Vec, + #[serde(default)] pub description: String, + #[serde(default)] pub id: String, + #[serde(default)] pub meta: ScimSchemaMeta, + #[serde(default)] pub name: String, + #[serde(default)] pub schemas: Vec, } @@ -14117,10 +14489,13 @@ pub struct ScimSchemaAttribute { pub canonical_values: Option>, #[serde(rename = "caseExact", skip_serializing_if = "Option::is_none", default)] pub case_exact: Option, + #[serde(default)] pub description: String, - #[serde(rename = "multiValued")] + #[serde(rename = "multiValued", default)] pub multi_valued: bool, + #[serde(default)] pub mutability: String, + #[serde(default)] pub name: String, #[serde( rename = "referenceTypes", @@ -14128,7 +14503,9 @@ pub struct ScimSchemaAttribute { default )] pub reference_types: Option>, + #[serde(default)] pub required: bool, + #[serde(default)] pub returned: String, #[serde( rename = "subAttributes", @@ -14136,7 +14513,7 @@ pub struct ScimSchemaAttribute { default )] pub sub_attributes: Option>, - #[serde(rename = "type")] + #[serde(rename = "type", default)] pub r#type: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub uniqueness: Option, @@ -14145,39 +14522,44 @@ pub struct ScimSchemaAttribute { /// `ScimSchemaExtension` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimSchemaExtension { + #[serde(default)] pub required: bool, + #[serde(default)] pub schema: String, } /// `ScimSchemaListResponse` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimSchemaListResponse { - #[serde(rename = "Resources")] + #[serde(rename = "Resources", default)] pub resources: Vec, - #[serde(rename = "itemsPerPage")] + #[serde(rename = "itemsPerPage", default)] pub items_per_page: i64, + #[serde(default)] pub schemas: Vec, - #[serde(rename = "startIndex")] + #[serde(rename = "startIndex", default)] pub start_index: i64, - #[serde(rename = "totalResults")] + #[serde(rename = "totalResults", default)] pub total_results: i64, } /// `ScimSchemaMeta` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimSchemaMeta { + #[serde(default)] pub location: String, - #[serde(rename = "resourceType")] + #[serde(rename = "resourceType", default)] pub resource_type: String, } /// `ScimServiceProviderConfig` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimServiceProviderConfig { - #[serde(rename = "authenticationSchemes")] + #[serde(rename = "authenticationSchemes", default)] pub authentication_schemes: Vec, + #[serde(default)] pub bulk: ScimServiceProviderConfigBulk, - #[serde(rename = "changePassword")] + #[serde(rename = "changePassword", default)] pub change_password: ScimBooleanFeature, #[serde( rename = "documentationUri", @@ -14185,43 +14567,53 @@ pub struct ScimServiceProviderConfig { default )] pub documentation_uri: Option, + #[serde(default)] pub etag: ScimBooleanFeature, + #[serde(default)] pub filter: ScimServiceProviderConfigFilter, + #[serde(default)] pub meta: ScimServiceProviderConfigMeta, + #[serde(default)] pub patch: ScimServiceProviderConfigPatch, + #[serde(default)] pub schemas: Vec, + #[serde(default)] pub sort: ScimBooleanFeature, } /// `ScimServiceProviderConfigBulk` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimServiceProviderConfigBulk { - #[serde(rename = "maxOperations")] + #[serde(rename = "maxOperations", default)] pub max_operations: i64, - #[serde(rename = "maxPayloadSize")] + #[serde(rename = "maxPayloadSize", default)] pub max_payload_size: i64, + #[serde(default)] pub supported: bool, } /// `ScimServiceProviderConfigFilter` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimServiceProviderConfigFilter { - #[serde(rename = "maxResults")] + #[serde(rename = "maxResults", default)] pub max_results: i64, + #[serde(default)] pub supported: bool, } /// `ScimServiceProviderConfigMeta` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimServiceProviderConfigMeta { + #[serde(default)] pub location: String, - #[serde(rename = "resourceType")] + #[serde(rename = "resourceType", default)] pub resource_type: String, } /// `ScimServiceProviderConfigPatch` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimServiceProviderConfigPatch { + #[serde(default)] pub supported: bool, } @@ -15028,9 +15420,9 @@ pub struct PostgresInstanceConfig { pub struct PostgresInstanceUpdateConfigResponse { #[serde(skip_serializing_if = "Option::is_none", default)] pub message: Option, - #[serde(rename = "pgBouncerConfig")] + #[serde(rename = "pgBouncerConfig", default)] pub pg_bouncer_config: PgBouncerConfig, - #[serde(rename = "pgConfig")] + #[serde(rename = "pgConfig", default)] pub pg_config: PgConfig, } @@ -15041,6 +15433,7 @@ pub struct ApiResponse { pub status: Option, #[serde(skip_serializing_if = "Option::is_none", default, rename = "requestId")] pub request_id: Option, + #[serde(default)] pub result: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub error: Option, diff --git a/crates/clickhouse-cloud-api/src/serde_helpers.rs b/crates/clickhouse-cloud-api/src/serde_helpers.rs index 0d2e42e..d12b8cb 100644 --- a/crates/clickhouse-cloud-api/src/serde_helpers.rs +++ b/crates/clickhouse-cloud-api/src/serde_helpers.rs @@ -15,3 +15,21 @@ where { Option::>::deserialize(d).map(Option::unwrap_or_default) } + +/// Deserialize a buffered payload into `T`, handing the payload back unchanged +/// if it does not fit `T`. +/// +/// Used by the `discriminated_union!` macro in `models.rs` so a union whose +/// discriminator selects a variant the rest of the payload no longer fits +/// degrades to that union's `Unknown(serde_json::Value)` catch-all instead of +/// failing the whole response. `#[serde(default)]` covers a field the API stops +/// sending; this covers a field whose *shape* the API changes. +pub fn deserialize_or_raw(value: serde_json::Value) -> Result +where + T: serde::de::DeserializeOwned, +{ + match serde_json::from_value(value.clone()) { + Ok(parsed) => Ok(parsed), + Err(_) => Err(value), + } +} diff --git a/crates/clickhouse-cloud-api/tests/models_test.rs b/crates/clickhouse-cloud-api/tests/models_test.rs index 043a5a9..a6bfb7b 100644 --- a/crates/clickhouse-cloud-api/tests/models_test.rs +++ b/crates/clickhouse-cloud-api/tests/models_test.rs @@ -19,6 +19,53 @@ where assert!(reserialized.is_object()); } +/// Every `discriminated_union!` enum with a `Default` is reachable from a +/// response that drops a field carrying that union, so its default must be a +/// fixed point of its own `Deserialize`: it has to come back as the same +/// variant. It is not automatic — variants of one union can share the same +/// inline `enum` values for the discriminating field (both alert channels +/// declare `["webhook", "email"]`), so a variant defaulting its discriminator to +/// another variant's value would silently retype the value on the next +/// deserialize. Add new unions with a `Default` impl here. +#[test] +fn discriminated_union_defaults_round_trip_to_the_same_variant() { + macro_rules! assert_default_round_trips { + ($($union:ty),+ $(,)?) => { + $({ + let default = <$union>::default(); + let json = serde_json::to_string(&default).unwrap(); + let parsed: $union = serde_json::from_str(&json).unwrap(); + assert_eq!( + parsed, + default, + "{} default deserialized as another variant from {json}", + stringify!($union), + ); + })+ + }; + } + + assert_default_round_trips!( + BackupBucket, + BackupBucketPatchRequest, + BackupBucketPostRequest, + BackupBucketProperties, + ClickStackAlertChannel, + ClickStackBarChartConfig, + ClickStackCategoricalBarChartConfig, + ClickStackDashboardChartSeries, + ClickStackLineChartConfig, + ClickStackNumberChartConfig, + ClickStackOnClick, + ClickStackOnClickTarget, + ClickStackPieChartConfig, + ClickStackSource, + ClickStackTableChartConfig, + ClickStackTileConfig, + ClickStackWebhook, + ); +} + #[test] fn deserialize_organization() { let json = r#"{ @@ -1779,11 +1826,38 @@ fn clickstack_alert_channel_known_variants_deserialize() { } } +#[test] +fn clickstack_alert_channel_known_type_sparse_payload_defaults() { + // A recognized `type` dispatches hard to its variant, and the variant's own + // fields are tolerant: a server that drops `emailRecipients` degrades to the + // default rather than failing the response. + let json = r#"{"type":"email"}"#; + let channel: ClickStackAlertChannel = serde_json::from_str(json).unwrap(); + match channel { + ClickStackAlertChannel::ClickStackAlertChannelEmail(v) => { + assert_eq!(v.r#type, ClickStackAlertChannelEmailType::Email); + assert!(v.email_recipients.is_empty()); + } + other => panic!("expected email variant, got {other}"), + } +} + +#[test] +fn clickstack_alert_channel_missing_type_key_is_unknown() { + // This union has no arm for an absent `type` key, in deliberate contrast to + // the chart-config unions where absence means the Builder variant, so a + // payload without the discriminator lands in the lossless Unknown catch-all. + let json = r#"{"webhookId":"wh-1"}"#; + assert_unknown_variant_round_trips(json, |c: &ClickStackAlertChannel| { + matches!(c, ClickStackAlertChannel::Unknown(_)) + }); +} + #[test] fn clickstack_alert_channel_unknown_shape_round_trips() { - // A payload matching neither the email nor webhook variant must land in the - // lossless Unknown catch-all and re-serialize to the same JSON object rather - // than erroring on deserialize. + // An unrecognized `type` value must land in the lossless Unknown catch-all + // and re-serialize to the same JSON object rather than erroring on + // deserialize. let json = r#"{ "type": "future_channel", "foo": 1 @@ -1793,6 +1867,40 @@ fn clickstack_alert_channel_unknown_shape_round_trips() { }); } +#[test] +fn clickstack_alert_channel_changed_field_shape_is_unknown() { + // A recognized `type` whose payload no longer fits the variant — here + // `emailRecipients` as a string instead of an array — must not fail the + // response. `list_alerts` returns a Vec, so one such element would otherwise + // take down the whole call; instead the element lands in Unknown intact. + let json = r#"{"type":"email","emailRecipients":"a@b.c"}"#; + assert_unknown_variant_round_trips(json, |c: &ClickStackAlertChannel| { + matches!(c, ClickStackAlertChannel::Unknown(_)) + }); +} + +#[test] +fn clickstack_alert_channel_default_round_trips_to_the_same_variant() { + // Both alert-channel variants declare the same `enum: ["webhook", "email"]` + // for their discriminating `type`, so the email variant's default must name + // `email`: `channel` carries #[serde(default)], and a default that named + // `webhook` would come back from its own union as the webhook variant and + // could be PUT back as a webhook channel with no `webhookId`. + let default = ClickStackAlertChannel::default(); + assert!(matches!( + default, + ClickStackAlertChannel::ClickStackAlertChannelEmail(_) + )); + let json = serde_json::to_string(&default).unwrap(); + assert_eq!(json, r#"{"emailRecipients":[],"type":"email"}"#); + let round_tripped: ClickStackAlertChannel = serde_json::from_str(&json).unwrap(); + assert_eq!(round_tripped, default); + + // A response that drops `channel` entirely lands on that same default. + let response: ClickStackAlertResponse = serde_json::from_str(r#"{"name":"my-alert"}"#).unwrap(); + assert_eq!(response.channel, default); +} + #[test] fn deserialize_clickstack_log_source_with_metadata_materialized_views() { let json = r#"{ @@ -2789,9 +2897,9 @@ fn clickstack_tile_config_markdown_variant() { #[test] fn clickstack_tile_config_heatmap_variant() { - // Untagged-enum dispatch must reach the new ClickStackHeatmapChartConfig - // arm. The discriminator is `displayType: "heatmap"` plus the heatmap- - // specific `select` shape with `valueExpression`. + // `displayType: "heatmap"` is the only discriminator that reaches the + // ClickStackHeatmapChartConfig arm; the heatmap-specific `select` shape with + // `valueExpression` is then parsed by that variant, not used to select it. let json = r#"{ "displayType": "heatmap", "sourceId": "src-1", @@ -2823,19 +2931,119 @@ fn clickstack_tile_config_unknown_display_type_round_trips() { } #[test] -fn clickstack_tile_config_known_display_type_novel_shape_falls_to_sub_union_unknown() { - // A known `displayType` whose body matches neither the builder nor the raw - // SQL shape must land in the sub-union's Unknown(Value) rather than error. +fn clickstack_tile_config_line_novel_shape_defaults_to_builder() { + // A known `displayType` carrying no `configType` dispatches to the builder + // variant: the novel member is ignored and the builder's strict fields fall + // back to their serde defaults instead of failing the response. let json = r#"{"displayType":"line","somethingNew":true}"#; let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); match cfg { - ClickStackTileConfig::ClickStackLineChartConfig(ClickStackLineChartConfig::Unknown(v)) => { - assert_eq!(v, serde_json::from_str::(json).unwrap()); + ClickStackTileConfig::ClickStackLineChartConfig( + ClickStackLineChartConfig::ClickStackLineBuilderChartConfig(b), + ) => { + assert_eq!(b.source_id, ""); + assert!(b.select.is_empty()); + } + other => panic!("expected line builder variant, got {other}"), + } +} + +#[test] +fn clickstack_tile_config_line_minimal_body_defaults_to_builder() { + // The bare discriminator with no `configType` key at all: key absence is the + // builder discriminator, and every builder field defaults. + let json = r#"{"displayType":"line"}"#; + let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); + match cfg { + ClickStackTileConfig::ClickStackLineChartConfig( + ClickStackLineChartConfig::ClickStackLineBuilderChartConfig(b), + ) => { + assert_eq!(b, ClickStackLineBuilderChartConfig::default()); + } + other => panic!("expected line builder variant, got {other}"), + } +} + +#[test] +fn clickstack_tile_config_non_string_config_type_defaults_to_builder() { + // A non-string `configType` is deliberately conflated with an absent one: + // both dispatch to the builder variant rather than to Unknown. + let json = r#"{"displayType":"line","configType":123,"sourceId":"src-1"}"#; + let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); + match cfg { + ClickStackTileConfig::ClickStackLineChartConfig( + ClickStackLineChartConfig::ClickStackLineBuilderChartConfig(b), + ) => { + assert_eq!(b.source_id, "src-1"); } - other => panic!("expected line sub-union Unknown(Value), got {other}"), + other => panic!("expected line builder variant, got {other}"), } } +#[test] +fn clickstack_tile_config_line_unrecognized_config_type_falls_to_sub_union_unknown() { + // An unrecognized *string* `configType` reaches the line sub-union's + // Unknown(Value); it round-trips losslessly. + let json = r#"{"displayType":"line","configType":"future"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackLineChartConfig(ClickStackLineChartConfig::Unknown(_)) + ) + }); +} + +#[test] +fn clickstack_tile_config_raw_sql_body_without_config_type_falls_to_sub_union_unknown() { + // `configType` is spec-required on the Raw SQL configs, so a server that + // stops sending it must not silently retype the tile: the builder variant is + // total and would otherwise absorb the body and drop `connectionId` and + // `sqlTemplate`. The `unless` guard routes it to Unknown, which keeps the + // payload verbatim. + let json = r#"{"displayType":"line","connectionId":"conn-1","sqlTemplate":"SELECT 1"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackLineChartConfig(ClickStackLineChartConfig::Unknown(_)) + ) + }); + + // Either guard key on its own disqualifies the builder variant, and the + // guard is wired on every chart-config sub-union, not just the line one. + let json = r#"{"displayType":"number","connectionId":"conn-1"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackNumberChartConfig( + ClickStackNumberChartConfig::Unknown(_) + ) + ) + }); +} + +#[test] +fn clickstack_tile_config_changed_field_shape_falls_to_sub_union_unknown() { + // `#[serde(default)]` only covers a field the API stops sending. A field + // whose *shape* changes cannot deserialize into the dispatched variant, so + // the union hands the payload to its lossless Unknown catch-all rather than + // failing the whole dashboard response. + let builder = r#"{"displayType":"line","sourceId":123}"#; + assert_unknown_variant_round_trips(builder, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackLineChartConfig(ClickStackLineChartConfig::Unknown(_)) + ) + }); + + let raw_sql = r#"{"displayType":"line","configType":"sql","sqlTemplate":123}"#; + assert_unknown_variant_round_trips(raw_sql, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackLineChartConfig(ClickStackLineChartConfig::Unknown(_)) + ) + }); +} + #[test] fn clickstack_tile_config_line_raw_sql_variant() { // The Raw SQL line config (configType "sql") dispatches to the line @@ -2976,89 +3184,159 @@ fn clickstack_tile_config_pie_raw_sql_variant() { } #[test] -fn clickstack_tile_config_categorical_bar_novel_shape_falls_to_sub_union_unknown() { - // A "bar" body matching neither the categorical bar builder nor raw SQL shape - // lands in the categorical bar sub-union's Unknown(Value) and round-trips. +fn clickstack_tile_config_categorical_bar_novel_shape_defaults_to_builder() { + // A "bar" body with no `configType` dispatches to the categorical bar builder + // variant, whose strict fields default rather than failing the response. let json = r#"{"displayType":"bar","somethingNew":true}"#; let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); - match &cfg { + match cfg { ClickStackTileConfig::ClickStackCategoricalBarChartConfig( - ClickStackCategoricalBarChartConfig::Unknown(v), + ClickStackCategoricalBarChartConfig::ClickStackCategoricalBarBuilderChartConfig(b), ) => { - assert_eq!(*v, serde_json::from_str::(json).unwrap()); + assert_eq!(b.source_id, ""); + assert!(b.select.is_empty()); } - other => panic!("expected categorical bar sub-union Unknown(Value), got {other}"), + other => panic!("expected categorical bar builder variant, got {other}"), } - let expected: serde_json::Value = serde_json::from_str(json).unwrap(); - assert_eq!(serde_json::to_value(&cfg).unwrap(), expected); } #[test] -fn clickstack_tile_config_stacked_bar_novel_shape_falls_to_sub_union_unknown() { - // A "stacked_bar" body matching neither the bar builder nor raw SQL shape - // lands in the bar sub-union's Unknown(Value) and round-trips. +fn clickstack_tile_config_categorical_bar_unrecognized_config_type_falls_to_sub_union_unknown() { + // An unrecognized *string* `configType` reaches the categorical bar + // sub-union's Unknown(Value); it round-trips losslessly. + let json = r#"{"displayType":"bar","configType":"future"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackCategoricalBarChartConfig( + ClickStackCategoricalBarChartConfig::Unknown(_) + ) + ) + }); +} + +#[test] +fn clickstack_tile_config_stacked_bar_novel_shape_defaults_to_builder() { + // A "stacked_bar" body with no `configType` dispatches to the bar builder + // variant, whose strict fields default rather than failing the response. let json = r#"{"displayType":"stacked_bar","somethingNew":true}"#; let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); - match &cfg { - ClickStackTileConfig::ClickStackBarChartConfig(ClickStackBarChartConfig::Unknown(v)) => { - assert_eq!(*v, serde_json::from_str::(json).unwrap()); + match cfg { + ClickStackTileConfig::ClickStackBarChartConfig( + ClickStackBarChartConfig::ClickStackBarBuilderChartConfig(b), + ) => { + assert_eq!(b.source_id, ""); + assert!(b.select.is_empty()); } - other => panic!("expected bar sub-union Unknown(Value), got {other}"), + other => panic!("expected bar builder variant, got {other}"), } - let expected: serde_json::Value = serde_json::from_str(json).unwrap(); - assert_eq!(serde_json::to_value(&cfg).unwrap(), expected); } #[test] -fn clickstack_tile_config_table_novel_shape_falls_to_sub_union_unknown() { - // A "table" body matching neither the table builder nor raw SQL shape lands - // in the table sub-union's Unknown(Value) and round-trips. +fn clickstack_tile_config_stacked_bar_unrecognized_config_type_falls_to_sub_union_unknown() { + // An unrecognized *string* `configType` reaches the bar sub-union's + // Unknown(Value); it round-trips losslessly. + let json = r#"{"displayType":"stacked_bar","configType":"future"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackBarChartConfig(ClickStackBarChartConfig::Unknown(_)) + ) + }); +} + +#[test] +fn clickstack_tile_config_table_novel_shape_defaults_to_builder() { + // A "table" body with no `configType` dispatches to the table builder + // variant, whose strict fields default rather than failing the response. let json = r#"{"displayType":"table","somethingNew":true}"#; let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); - match &cfg { - ClickStackTileConfig::ClickStackTableChartConfig(ClickStackTableChartConfig::Unknown( - v, - )) => { - assert_eq!(*v, serde_json::from_str::(json).unwrap()); + match cfg { + ClickStackTileConfig::ClickStackTableChartConfig( + ClickStackTableChartConfig::ClickStackTableBuilderChartConfig(b), + ) => { + assert_eq!(b.source_id, ""); + assert!(b.select.is_empty()); } - other => panic!("expected table sub-union Unknown(Value), got {other}"), + other => panic!("expected table builder variant, got {other}"), } - let expected: serde_json::Value = serde_json::from_str(json).unwrap(); - assert_eq!(serde_json::to_value(&cfg).unwrap(), expected); } #[test] -fn clickstack_tile_config_number_novel_shape_falls_to_sub_union_unknown() { - // A "number" body matching neither the number builder nor raw SQL shape lands - // in the number sub-union's Unknown(Value) and round-trips. +fn clickstack_tile_config_table_unrecognized_config_type_falls_to_sub_union_unknown() { + // An unrecognized *string* `configType` reaches the table sub-union's + // Unknown(Value); it round-trips losslessly. + let json = r#"{"displayType":"table","configType":"future"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackTableChartConfig(ClickStackTableChartConfig::Unknown( + _ + )) + ) + }); +} + +#[test] +fn clickstack_tile_config_number_novel_shape_defaults_to_builder() { + // A "number" body with no `configType` dispatches to the number builder + // variant, whose strict fields default rather than failing the response. let json = r#"{"displayType":"number","somethingNew":true}"#; let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); - match &cfg { + match cfg { ClickStackTileConfig::ClickStackNumberChartConfig( - ClickStackNumberChartConfig::Unknown(v), + ClickStackNumberChartConfig::ClickStackNumberBuilderChartConfig(b), ) => { - assert_eq!(*v, serde_json::from_str::(json).unwrap()); + assert_eq!(b.source_id, ""); + assert!(b.select.is_empty()); } - other => panic!("expected number sub-union Unknown(Value), got {other}"), + other => panic!("expected number builder variant, got {other}"), } - let expected: serde_json::Value = serde_json::from_str(json).unwrap(); - assert_eq!(serde_json::to_value(&cfg).unwrap(), expected); } #[test] -fn clickstack_tile_config_pie_novel_shape_falls_to_sub_union_unknown() { - // A "pie" body matching neither the pie builder nor raw SQL shape lands in - // the pie sub-union's Unknown(Value) and round-trips. +fn clickstack_tile_config_number_unrecognized_config_type_falls_to_sub_union_unknown() { + // An unrecognized *string* `configType` reaches the number sub-union's + // Unknown(Value); it round-trips losslessly. + let json = r#"{"displayType":"number","configType":"future"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackNumberChartConfig( + ClickStackNumberChartConfig::Unknown(_) + ) + ) + }); +} + +#[test] +fn clickstack_tile_config_pie_novel_shape_defaults_to_builder() { + // A "pie" body with no `configType` dispatches to the pie builder variant, + // whose strict fields default rather than failing the response. let json = r#"{"displayType":"pie","somethingNew":true}"#; let cfg: ClickStackTileConfig = serde_json::from_str(json).unwrap(); - match &cfg { - ClickStackTileConfig::ClickStackPieChartConfig(ClickStackPieChartConfig::Unknown(v)) => { - assert_eq!(*v, serde_json::from_str::(json).unwrap()); + match cfg { + ClickStackTileConfig::ClickStackPieChartConfig( + ClickStackPieChartConfig::ClickStackPieBuilderChartConfig(b), + ) => { + assert_eq!(b.source_id, ""); + assert!(b.select.is_empty()); } - other => panic!("expected pie sub-union Unknown(Value), got {other}"), + other => panic!("expected pie builder variant, got {other}"), } - let expected: serde_json::Value = serde_json::from_str(json).unwrap(); - assert_eq!(serde_json::to_value(&cfg).unwrap(), expected); +} + +#[test] +fn clickstack_tile_config_pie_unrecognized_config_type_falls_to_sub_union_unknown() { + // An unrecognized *string* `configType` reaches the pie sub-union's + // Unknown(Value); it round-trips losslessly. + let json = r#"{"displayType":"pie","configType":"future"}"#; + assert_unknown_variant_round_trips(json, |cfg: &ClickStackTileConfig| { + matches!( + cfg, + ClickStackTileConfig::ClickStackPieChartConfig(ClickStackPieChartConfig::Unknown(_)) + ) + }); } #[test] @@ -3789,3 +4067,76 @@ fn deserialize_clickstack_dashboard_chart_series_unknown_type_round_trip() { let expected: serde_json::Value = serde_json::from_str(json).unwrap(); assert_eq!(serde_json::to_value(&series).unwrap(), expected); } + +// =========================================================================== +// Tolerant response deserialization (issue #312) +// =========================================================================== + +#[test] +fn scim_user_tolerates_dropped_response_fields() { + // Every field of every model carries `serde(default)`, so a spec-required + // response field the server stops sending degrades to that field's default + // rather than failing the whole payload. + let json = r#"{"id":"user-1"}"#; + let user: ScimUser = serde_json::from_str(json).unwrap(); + assert_eq!(user.id, "user-1"); + assert_eq!(user.user_name, ""); + assert!(!user.active); + assert!(user.emails.is_empty()); + assert!(user.schemas.is_empty()); + assert_eq!(user.name, ScimUserName::default()); + assert_eq!(user.meta, ScimUserMeta::default()); + + // Serialization is unaffected: wire names and `None` omission are unchanged. + let value = serde_json::to_value(&user).unwrap(); + assert_eq!(value["id"], "user-1"); + assert_eq!(value["userName"], ""); + assert_eq!(value["active"], false); + assert_eq!(value["emails"], serde_json::json!([])); + assert_eq!(value["name"]["familyName"], ""); + assert_eq!(value["meta"]["resourceType"], ""); + assert!(value.get("displayName").is_none()); +} + +#[test] +fn clickpipe_post_pubsub_source_tolerates_dropped_response_fields() { + // `topic`, `projectId` and `serviceAccountKey` are all spec-required, but a + // sparse response still deserializes with their defaults. + let json = r#"{"seekType":"earliest"}"#; + let src: ClickPipePostPubSubSource = serde_json::from_str(json).unwrap(); + assert_eq!(src.seek_type, ClickPipePostPubSubSourceSeektype::Earliest); + assert_eq!(src.topic, ""); + assert_eq!(src.project_id, ""); + assert_eq!(src.service_account_key, ServiceAccount::default()); + assert_eq!( + src.authentication, + ClickPipePostPubSubSourceAuthentication::default() + ); + assert_eq!(src.format, ClickPipePostPubSubSourceFormat::default()); + assert_eq!(src.ack_deadline, None); + + let value = serde_json::to_value(&src).unwrap(); + assert_eq!(value["seekType"], "earliest"); + assert_eq!(value["topic"], ""); + assert_eq!(value["projectId"], ""); + assert!(value.get("ackDeadline").is_none()); +} + +#[test] +fn organization_quota_tolerates_dropped_response_fields() { + let json = r#"{"name":"maxServices","value":10}"#; + let quota: OrganizationQuota = serde_json::from_str(json).unwrap(); + assert_eq!(quota.name, "maxServices"); + assert_eq!(quota.value, 10); + assert_eq!(quota.description, ""); + assert!(!quota.adjustable); + assert_eq!(quota.quota_code, OrganizationQuotaQuotacode::default()); + assert_eq!(quota.scope, OrganizationQuotaScope::default()); + assert_eq!(quota.usage, None); + + let value = serde_json::to_value("a).unwrap(); + assert_eq!(value["name"], "maxServices"); + assert_eq!(value["value"], 10); + assert_eq!(value["adjustable"], false); + assert!(value.get("usage").is_none()); +} diff --git a/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs b/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs index e101244..b1a9390 100644 --- a/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs +++ b/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs @@ -32,6 +32,23 @@ fn vendored_openapi_snapshot_matches_rust_api() { ); } +/// Enforces the tolerant-response deserialization policy documented in AGENTS.md: every +/// public model field carries a field-level `#[serde(default)]`, so a response field the +/// server stops sending degrades to that field's default instead of failing the whole +/// response. There is deliberately no exception list — requests stay strict through the +/// type system (required fields are `T`, not `Option`), not through missing defaults. +#[test] +fn every_model_field_carries_serde_default() { + let offenders = clickhouse_openapi_analyzer::model_fields_missing_serde_default(MODELS_RS) + .expect("models.rs must parse"); + assert!( + offenders.is_empty(), + "these model fields lack #[serde(default)], violating the tolerant-deserialization \ + policy in AGENTS.md:\n{}", + offenders.join("\n") + ); +} + #[tokio::test] #[ignore = "hits the live published ClickHouse OpenAPI spec"] async fn live_openapi_spec_matches_rust_api() { diff --git a/crates/clickhouse-openapi-analyzer/src/lib.rs b/crates/clickhouse-openapi-analyzer/src/lib.rs index 7846574..fc2e3ed 100644 --- a/crates/clickhouse-openapi-analyzer/src/lib.rs +++ b/crates/clickhouse-openapi-analyzer/src/lib.rs @@ -52,3 +52,15 @@ pub fn analyze( OpenApiInventory::build(&snapshot, config).map_err(AnalyzeError::SnapshotInventory)?; Ok(compare::compare(&rust, &spec, &snapshot, config)) } + +/// Lists every public model struct field in `models_rs` that lacks a field-level +/// `#[serde(default)]`, as `StructName.rust_field_name`. +/// +/// This backs the tolerant-deserialization policy test in `clickhouse-cloud-api` (see +/// AGENTS.md); it is intentionally not a drift `FindingKind`, because it compares Rust +/// source against a repository policy rather than against the OpenAPI spec. The parsing +/// stays behind this narrow function so `syn` never enters the `clickhouse-cloud-api` +/// dependency graph. +pub fn model_fields_missing_serde_default(models_rs: &str) -> Result, AnalyzeError> { + rust_inventory::model_fields_missing_serde_default(models_rs).map_err(AnalyzeError::RustSource) +} diff --git a/crates/clickhouse-openapi-analyzer/src/rust_inventory.rs b/crates/clickhouse-openapi-analyzer/src/rust_inventory.rs index 1850ef1..2eceafd 100644 --- a/crates/clickhouse-openapi-analyzer/src/rust_inventory.rs +++ b/crates/clickhouse-openapi-analyzer/src/rust_inventory.rs @@ -89,6 +89,7 @@ pub(crate) struct FieldInfo { pub(crate) rust_name: String, pub(crate) rust_type: TypeNode, pub(crate) deprecated_marker: bool, + pub(crate) serde_default: bool, } #[derive(Debug, Clone)] @@ -212,6 +213,7 @@ impl RustInventory { rust_name, rust_type: TypeNode::from_syn(&field.ty), deprecated_marker: has_deprecated_cfg(&field.attrs)?, + serde_default: options.default, }, ); } @@ -344,11 +346,32 @@ impl RustInventory { } } +/// Lists every public model struct field that lacks a field-level `#[serde(default)]`, +/// as `StructName.rust_field_name`, sorted by struct name then wire field name. +/// +/// Both `T` and `Option` fields are reported, including `cfg`-gated deprecated-marker +/// fields. Only the field-level attribute counts: a container-level `#[serde(default)]` is +/// not honored, because `models.rs` has none and relying on it would hide per-field gaps. +pub(crate) fn model_fields_missing_serde_default(models: &str) -> syn::Result> { + let inventory = RustInventory::parse("", models, "")?; + Ok(inventory + .structs + .iter() + .flat_map(|(struct_name, info)| { + info.fields + .values() + .filter(|field| !field.serde_default) + .map(move |field| format!("{struct_name}.{}", field.rust_name)) + }) + .collect()) +} + #[derive(Default)] struct SerdeOptions { rename: Option, untagged: bool, other: bool, + default: bool, } fn serde_options(attributes: &[Attribute]) -> syn::Result { @@ -381,6 +404,13 @@ fn serde_options(attributes: &[Attribute]) -> syn::Result { options.untagged = true; } else if meta.path.is_ident("other") { options.other = true; + } else if meta.path.is_ident("default") { + // Both `default` and `default = "path"` opt the field into tolerant + // deserialization, so this must precede the generic `key = value` arm below. + options.default = true; + if meta.input.peek(syn::Token![=]) { + let _: Expr = meta.value()?.parse()?; + } } else if meta.input.peek(syn::Token![=]) { let _: Expr = meta.value()?.parse()?; } @@ -523,6 +553,71 @@ mod tests { assert!(inventory.metadata.beta_operations.contains("list_widgets")); } + #[test] + fn tracks_serde_default_in_all_attribute_forms() { + let models = r#" + pub struct Widget { + #[serde(default)] + pub bare: String, + #[serde(rename = "renamedWithDefault", default)] + pub renamed: String, + #[serde(default = "some::path")] + pub with_path: String, + pub plain: String, + } + "#; + + let inventory = RustInventory::parse("", models, "").unwrap(); + let fields = &inventory.structs["Widget"].fields; + assert!(fields["bare"].serde_default); + assert!(fields["renamedWithDefault"].serde_default); + assert!(fields["with_path"].serde_default); + assert!(!fields["plain"].serde_default); + } + + #[test] + fn model_fields_missing_serde_default_lists_offenders_sorted() { + let models = r#" + pub struct Widget { + #[serde(default)] + pub name: String, + pub description: Option, + pub r#type: String, + #[serde(rename = "createdAt", default)] + pub created_at: String, + } + pub struct Gadget { + pub id: Option, + } + pub enum State { Ready } + "#; + + assert_eq!( + model_fields_missing_serde_default(models).unwrap(), + vec![ + "Gadget.id".to_string(), + "Widget.description".to_string(), + "Widget.type".to_string(), + ] + ); + } + + #[test] + fn model_fields_missing_serde_default_reports_deprecated_gated_fields() { + let models = r#" + pub struct Widget { + #[cfg(feature = "deprecated-fields")] + #[serde(rename = "legacyName")] + pub legacy_name: Option, + } + "#; + + assert_eq!( + model_fields_missing_serde_default(models).unwrap(), + vec!["Widget.legacy_name".to_string()] + ); + } + #[test] fn serde_other_is_not_a_wire_value() { let models = r#"