Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` 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<T>` 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<T>` 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.
Expand Down
31 changes: 31 additions & 0 deletions crates/clickhouse-cloud-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`, 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;
Expand Down
Loading