From 270722af5456cec9a2032386f9de5cb5a73f7f6b Mon Sep 17 00:00:00 2001 From: sdairs Date: Fri, 5 Jun 2026 13:36:45 +0100 Subject: [PATCH 1/2] Detect struct fields removed from the spec but left in models.rs (#237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAPI drift detection was one-directional for fields: it caught spec properties missing from models.rs and optionality flips, but not the reverse — a field removed from the live spec yet still present as a struct field. A model that is a superset of the spec passed every field check (this is how the stale `storageSize` fields in #235/#236 slipped through). Add the code→spec mirror of the existing missing-field check, matching the missing/extra split already used for client methods: - spec_coverage_test.rs: `assert_no_extra_struct_fields` + `struct_fields_have_no_extras_vs_{spec,live_spec}` tests, plus an `EXTRA_FIELD_EXEMPTIONS` allowlist (analogous to OPTIONALITY_EXEMPTIONS / NON_OPENAPI_CLIENT_METHODS) with stale-entry detection. Schemas with no/empty `properties` are skipped so composition/marker schemas don't flag every field. - check-openapi-drift.py: `check_extra_fields` reverse pass + `parse_extra_field_exemptions` (reads the same constant), surfaced as a new "Extra Struct Fields" report section and summary row. - AGENTS.md: document the now-bidirectional field coverage and the EXTRA_FIELD_EXEMPTIONS allowlist. Detection only — nothing is remediated and the allowlist is empty, so the real findings it surfaces are reported rather than hidden. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 11 +- .../tests/spec_coverage_test.rs | 104 ++++++++++++++++++ scripts/check-openapi-drift.py | 89 +++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 88e4495..7f4888f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,15 +82,24 @@ In `models.rs`, required non-nullable fields use bare types (`T`), optional/null **Tooling:** - `scripts/resolve-field-requirements.py` — resolves required/optional for every schema field, outputs a JSON manifest. Handles both conventions + PATCH + nullable. -- `scripts/check-openapi-drift.py` — daily CI drift check; reports missing/extra methods, missing schemas/fields, and field-level optionality mismatches against the live spec. +- `scripts/check-openapi-drift.py` — daily CI drift check; reports missing/extra methods, missing/extra struct fields, missing schemas, and field-level optionality mismatches against the live spec. - `spec_coverage_test.rs::field_optionality_matches_spec` — asserts every field's `Option` vs `T` matches the snapshot. +Field coverage is **bidirectional**, mirroring the missing/extra split used for client methods: + +- `struct_fields_cover_every_spec_property` (spec → code) — every spec property has a matching struct field; catches fields *added* to the spec. +- `struct_fields_have_no_extras_vs_spec` (code → spec) — every struct field maps to a spec property; catches fields *removed* from the spec but left behind in `models.rs` (a superset model would otherwise pass every other field check). Schemas with no/empty `properties` are skipped, so composition/marker schemas don't flag every field. The drift script's "Extra Struct Fields" section reports the same finding. + Field optionality is maintained by hand. When the drift check or test flags a mismatch, edit `models.rs` directly to flip the field (`T` ↔ `Option`) and adjust the `#[serde(skip_serializing_if = "Option::is_none")]` attribute to match. **Optionality exemptions:** Sometimes the spec marks a field as required but the API rejects empty/default values, meaning the field is effectively optional. These fields are kept as `Option` in `models.rs` and listed in the `OPTIONALITY_EXEMPTIONS` constant in `spec_coverage_test.rs`. The test logs each exemption and fails if any become stale (spec was fixed upstream). When adding a new exemption, add a `("RustStructName", "specFieldName")` entry with a comment explaining the API behavior. +**Extra-field exemptions:** + +A struct field that intentionally has no spec property (a code-only/computed field, or a standard attribute the upstream spec omits) goes in the `EXTRA_FIELD_EXEMPTIONS` constant in `spec_coverage_test.rs`, analogous to `OPTIONALITY_EXEMPTIONS` and to `NON_OPENAPI_CLIENT_METHODS` for methods. `struct_fields_have_no_extras_vs_spec` fails on a stale entry (one that no longer corresponds to an actual extra field), and `check-openapi-drift.py` parses the same list so the report and test stay in sync. The list is empty by default — only add an entry for a *deliberate* addition, not to silence a field that should be removed. + ##### Deprecated field hiding Fields the spec marks `deprecated: true` — on both response schemas (e.g. `Service.tier`, `ApiKey.roles`) and request schemas (e.g. `ServicePostRequest.tier`, `InvitationPostRequest.role`) — are removed from the struct entirely so consumers, including the CLI, can't even reference a field the API has deprecated. Each carries `#[cfg(feature = "deprecated-fields")]` in `models.rs`: absent from the struct by default, present only when the `deprecated-fields` Cargo feature is on. On a **response** struct that means reading it is a compile error and it never appears in output (deserializing a payload that still contains it just ignores the extra key — no schema uses `deny_unknown_fields`). On a **request** struct it means callers can't set it and `skip_serializing_if` keeps it off the wire entirely. diff --git a/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs b/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs index 6ae788b..39fc6ee 100644 --- a/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs +++ b/crates/clickhouse-cloud-api/tests/spec_coverage_test.rs @@ -281,6 +281,18 @@ async fn struct_fields_cover_every_live_spec_property() { assert_field_coverage(&spec); } +#[test] +fn struct_fields_have_no_extras_vs_spec() { + assert_no_extra_struct_fields(&serde_json::from_str(SPEC_JSON).unwrap()); +} + +#[tokio::test] +#[ignore = "hits the live published ClickHouse OpenAPI spec"] +async fn struct_fields_have_no_extras_vs_live_spec() { + let spec = load_live_spec().await; + assert_no_extra_struct_fields(&spec); +} + /// Fields where our `Option` vs `T` intentionally disagrees with the spec. /// /// The spec sometimes marks fields as required when the API actually treats them @@ -541,6 +553,98 @@ fn assert_field_coverage(spec: &Value) { ); } +/// Struct fields we deliberately keep in `models.rs` even though the mapped +/// spec schema has no such property. Analogous to `NON_OPENAPI_CLIENT_METHODS` +/// (intentional client methods with no spec operation): a code-only field we +/// add on purpose — a response-only/computed field, or a standard attribute the +/// upstream spec omits. Each entry is `("RustStructName", "specFieldName")`. +/// +/// Empty by design. Every extra field the detector surfaces today is a real +/// drift finding (a field removed upstream but left in `models.rs`), not an +/// intentional addition — so nothing is exempted. The +/// `struct_fields_have_no_extras_vs_spec` test fails on a stale entry (one that +/// no longer corresponds to an actual extra field) so this list can't rot. +const EXTRA_FIELD_EXEMPTIONS: &[(&str, &str)] = &[]; + +/// Assert that no field in a Rust struct is absent from its mapped OpenAPI +/// schema. The mirror of `assert_field_coverage`: that catches spec properties +/// missing from structs (spec → code); this catches struct fields missing from +/// the spec (code → spec), e.g. a field removed from the schema upstream but +/// left behind in `models.rs`. Intentional code-only fields are listed in +/// `EXTRA_FIELD_EXEMPTIONS`. +/// +/// Schemas with no `properties` (or an empty `properties` object) are skipped, +/// matching `assert_field_coverage` — composition/marker schemas carry their +/// fields elsewhere and would otherwise flag every struct field as extra. +fn assert_no_extra_struct_fields(spec: &Value) { + let schemas = spec["components"]["schemas"].as_object().unwrap(); + let model_fields = parse_model_fields(MODELS_RS); + + let exemptions: BTreeSet<(&str, &str)> = EXTRA_FIELD_EXEMPTIONS.iter().copied().collect(); + let mut exemptions_hit: BTreeSet<(&str, &str)> = BTreeSet::new(); + let mut extras = Vec::new(); + + for (spec_name, schema) in schemas { + let rust_name = pascalize_identifier(spec_name); + let fields = match model_fields.get(&rust_name) { + Some(f) => f, + None => continue, // Schema not in models — covered by other tests + }; + + let props = match schema.get("properties").and_then(Value::as_object) { + Some(p) if !p.is_empty() => p, + _ => continue, + }; + + for spec_field in fields.keys() { + if props.contains_key(spec_field.as_str()) { + continue; + } + + if exemptions + .iter() + .any(|(s, f)| *s == rust_name && *f == spec_field.as_str()) + { + exemptions_hit.insert( + *exemptions + .iter() + .find(|(s, f)| *s == rust_name && *f == spec_field.as_str()) + .unwrap(), + ); + eprintln!( + "NOTE: {}.{} extra-field exempted — see EXTRA_FIELD_EXEMPTIONS", + rust_name, spec_field + ); + continue; + } + + extras.push(format!("{}.{}", rust_name, spec_field)); + } + } + + // Detect stale exemptions — entries that no longer correspond to an extra. + let stale: Vec<_> = exemptions + .difference(&exemptions_hit) + .map(|(s, f)| format!("({}, {})", s, f)) + .collect(); + assert!( + stale.is_empty(), + "Stale EXTRA_FIELD_EXEMPTIONS (struct field now matches the spec or was removed):\n{}", + stale.join("\n") + ); + + extras.sort(); + assert!( + extras.is_empty(), + "Struct fields with no matching spec property ({} total):\n{}\n\ + A field listed here was removed from (or never existed in) its OpenAPI \ + schema but still lives in models.rs. Remove it, or — if it's an \ + intentional code-only field — add it to EXTRA_FIELD_EXEMPTIONS.", + extras.len(), + extras.join("\n") + ); +} + /// Schemas where the spec's `required` array lists only newly-added fields; /// older fields on the same schema still rely on the description heuristic. /// For these we union `required[]` with the description heuristic instead of diff --git a/scripts/check-openapi-drift.py b/scripts/check-openapi-drift.py index 1f151ab..c97926d 100755 --- a/scripts/check-openapi-drift.py +++ b/scripts/check-openapi-drift.py @@ -213,6 +213,28 @@ def parse_optionality_exemptions() -> set[tuple[str, str]]: return set(re.findall(r'\("([^"]+)",\s*"([^"]+)"\)', match.group(1))) +def parse_extra_field_exemptions() -> set[tuple[str, str]]: + """Parse `EXTRA_FIELD_EXEMPTIONS` from spec_coverage_test.rs. + + Mirror of `parse_optionality_exemptions`: these (struct, field) pairs are + fields we intentionally keep in `models.rs` even though the mapped spec + schema has no such property (code-only/computed fields, or standard + attributes the upstream spec omits). Re-use the same list so the drift + report does not re-flag them and stays in sync with the test. + """ + if not SPEC_COVERAGE_TEST_RS.exists(): + return set() + source = SPEC_COVERAGE_TEST_RS.read_text() + match = re.search( + r"const\s+EXTRA_FIELD_EXEMPTIONS\s*:\s*&\[\(&str,\s*&str\)\]\s*=\s*&\[(.*?)\];", + source, + re.DOTALL, + ) + if not match: + return set() + return set(re.findall(r'\("([^"]+)",\s*"([^"]+)"\)', match.group(1))) + + def model_type_names() -> set[str]: """Extract all pub struct/enum/type names from models.rs.""" source = MODELS_RS.read_text() @@ -476,6 +498,48 @@ def check_missing_fields( return missing +def check_extra_fields( + spec: dict, + model_fields: dict[str, dict[str, bool]], + exemptions: set[tuple[str, str]] | None = None, +) -> list[dict]: + """Find Rust struct fields that have no corresponding spec property. + + The reverse of `check_missing_fields`: catches fields removed from (or never + present in) the OpenAPI schema but still lingering in `models.rs`. Schemas + with no/empty `properties` are skipped (composition/marker schemas carry + their fields elsewhere), matching the Rust test. + + Returns list of: [{schema, spec_name, field}] + """ + exemptions = exemptions or set() + extra = [] + schemas = spec.get("components", {}).get("schemas", {}) + + for spec_name, schema in schemas.items(): + pascal_name = pascalize(spec_name) + fields = model_fields.get(pascal_name) + if fields is None: + continue + + props = schema.get("properties") + if not props: + continue + + for field_name in fields: + if field_name in props: + continue + if (pascal_name, field_name) in exemptions: + continue + extra.append({ + "schema": pascal_name, + "spec_name": spec_name, + "field": field_name, + }) + + return extra + + # --------------------------------------------------------------------------- # GitHub helpers # --------------------------------------------------------------------------- @@ -531,6 +595,7 @@ def build_issue_body( all_spec_schemas: dict[str, dict], field_mismatches: list[dict] | None = None, missing_fields: list[dict] | None = None, + extra_fields: list[dict] | None = None, snapshot_staleness: dict | None = None, beta_status_changes: dict | None = None, deprecation_changes: dict | None = None, @@ -561,6 +626,7 @@ def build_issue_body( f"| Extra client methods (not in spec) | {len(extra_ops)} |", f"| Missing model types | {len(missing_types)} |", f"| Missing struct fields | {len(missing_fields or [])} |", + f"| Extra struct fields (not in spec) | {len(extra_fields or [])} |", f"| Field optionality mismatches | {len(field_mismatches or [])} |", f"| Beta status changes | {beta_total} |", f"| Deprecated output field changes | {dep_total} |", @@ -661,6 +727,24 @@ def build_issue_body( lines.append(f"| `{m['schema']}` | `{m['field']}` |") lines.append("") + # ---- Extra struct fields ---- + if extra_fields: + lines += [ + "## Extra Struct Fields", + "", + "These fields exist in a Rust struct but have no corresponding property", + "in the mapped OpenAPI schema. The field was likely removed from the spec", + "upstream while the struct field was left behind in `models.rs`. Remove the", + "field, or — if it is an intentional code-only field — add it to", + "`EXTRA_FIELD_EXEMPTIONS` in `spec_coverage_test.rs`.", + "", + "| Schema | Field |", + "|--------|-------|", + ] + for m in sorted(extra_fields, key=lambda m: (m["schema"], m["field"])): + lines.append(f"| `{m['schema']}` | `{m['field']}` |") + lines.append("") + # ---- Field optionality mismatches ---- if field_mismatches: lines += [ @@ -846,6 +930,8 @@ def main(): optionality_exemptions = parse_optionality_exemptions() field_mismatches = check_field_optionality(live_spec, model_fields, optionality_exemptions) missing_fields = check_missing_fields(live_spec, model_fields) + extra_field_exemptions = parse_extra_field_exemptions() + extra_fields = check_extra_fields(live_spec, model_fields, extra_field_exemptions) # Check committed snapshot staleness snapshot_staleness = check_snapshot_staleness(live_spec) @@ -877,6 +963,7 @@ def main(): + len(missing_types) + len(field_mismatches) + len(missing_fields) + + len(extra_fields) + beta_total + dep_total + snap_total @@ -890,6 +977,7 @@ def main(): print(f"Extra methods: {len(extra_op_names)}", file=sys.stderr) print(f"Missing types: {len(missing_types)}", file=sys.stderr) print(f"Missing fields: {len(missing_fields)}", file=sys.stderr) + print(f"Extra fields: {len(extra_fields)}", file=sys.stderr) print(f"Field mismatches:{len(field_mismatches)}", file=sys.stderr) print(f"Beta changes: {beta_total}", file=sys.stderr) print(f"Deprecation chg: {dep_total}", file=sys.stderr) @@ -906,6 +994,7 @@ def main(): live_schemas, field_mismatches, missing_fields, + extra_fields, snapshot_staleness, beta_status_changes, deprecation_changes, From 676d5b98c960e0eb2e107d71b7be023ed91f901f Mon Sep 17 00:00:00 2001 From: sdairs Date: Fri, 5 Jun 2026 14:13:27 +0100 Subject: [PATCH 2/2] Remediate extra-struct-field drift findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring models.rs in line with the vendored OpenAPI snapshot, clearing the 5 findings surfaced by the new struct_fields_have_no_extras_vs_spec detector (#237). EXTRA_FIELD_EXEMPTIONS stays empty — these are fixed, not exempted. - PostgresServicePatchRequest: drop name/provider/region/postgresVersion; the live spec only allows size/haType/tags. The `postgres update` CLI surface loses the matching --name/--region/--provider/--pg-version flags (same class of stale field as the storageSize cleanup in #235/#236). - ScimEnterpriseManager: drop the code-only $ref attribute absent from the ClickHouse spec. Updates the affected library and CLI tests plus the README update example. struct_fields_have_no_extras_vs_spec now passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 1 - crates/clickhouse-cloud-api/src/models.rs | 10 ------ .../clickhouse-cloud-api/tests/client_test.rs | 8 ++--- crates/clickhousectl/src/cloud/cli.rs | 2 +- crates/clickhousectl/src/cloud/postgres.rs | 34 +++---------------- crates/clickhousectl/src/main.rs | 8 ----- 6 files changed, 10 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 119c701..be92690 100644 --- a/README.md +++ b/README.md @@ -499,7 +499,6 @@ clickhousectl cloud postgres create \ # Update metadata (all flags optional) clickhousectl cloud postgres update \ - --name renamed \ --size m7i.4xlarge \ --add-tag env=prod --remove-tag legacy diff --git a/crates/clickhouse-cloud-api/src/models.rs b/crates/clickhouse-cloud-api/src/models.rs index 8cbe15b..1c69c2f 100644 --- a/crates/clickhouse-cloud-api/src/models.rs +++ b/crates/clickhouse-cloud-api/src/models.rs @@ -10521,14 +10521,6 @@ pub struct PostgresServicePatchRequest { #[serde(rename = "haType", skip_serializing_if = "Option::is_none", default)] pub ha_type: Option, #[serde(skip_serializing_if = "Option::is_none", default)] - pub name: Option, - #[serde(rename = "postgresVersion", skip_serializing_if = "Option::is_none", default)] - pub postgres_version: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub provider: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub region: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] pub size: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub tags: Option, @@ -10818,8 +10810,6 @@ pub struct ScalingSchedulePostRequest { /// `ScimEnterpriseManager` from the ClickHouse Cloud API. #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct ScimEnterpriseManager { - #[serde(rename = "$ref", default)] - pub r#ref: String, #[serde(rename = "displayName", default)] pub display_name: String, #[serde(default)] diff --git a/crates/clickhouse-cloud-api/tests/client_test.rs b/crates/clickhouse-cloud-api/tests/client_test.rs index cafec0c..0ad3ca7 100644 --- a/crates/clickhouse-cloud-api/tests/client_test.rs +++ b/crates/clickhouse-cloud-api/tests/client_test.rs @@ -2052,16 +2052,16 @@ async fn update_postgres_service() { Mock::given(method("PATCH")) .and(path("/v1/organizations/org-1/postgres/pg-1")) - .and(body_partial_json(serde_json::json!({"name": "pg-renamed"}))) + .and(body_partial_json(serde_json::json!({"size": "c6gd.medium"}))) .respond_with(ok_json(serde_json::json!({ "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", - "name": "pg-renamed" + "name": "pg-1" }))) .mount(&s) .await; let body = PostgresServicePatchRequest { - name: Some("pg-renamed".to_string()), + size: Some(PgSize::C6gd_medium), ..Default::default() }; let resp = c @@ -2069,7 +2069,7 @@ async fn update_postgres_service() { .await .unwrap(); let pg = resp.result.unwrap(); - assert_eq!(pg.name, "pg-renamed"); + assert_eq!(pg.name, "pg-1"); } #[tokio::test] diff --git a/crates/clickhousectl/src/cloud/cli.rs b/crates/clickhousectl/src/cloud/cli.rs index 85ee720..1e5645c 100644 --- a/crates/clickhousectl/src/cloud/cli.rs +++ b/crates/clickhousectl/src/cloud/cli.rs @@ -2673,7 +2673,7 @@ mod tests { // Postgres writes assert_write(&["clickhousectl", "cloud", "postgres", "create", "--name", "pg", "--region", "us-east-1", "--size", "m7i.2xlarge"], true); - assert_write(&["clickhousectl", "cloud", "postgres", "update", "pg-1", "--name", "renamed"], true); + assert_write(&["clickhousectl", "cloud", "postgres", "update", "pg-1", "--size", "c6gd.large"], true); assert_write(&["clickhousectl", "cloud", "postgres", "delete", "pg-1"], true); assert_write(&["clickhousectl", "cloud", "postgres", "config", "replace", "pg-1", "--file", "/tmp/c.json"], true); assert_write(&["clickhousectl", "cloud", "postgres", "config", "patch", "pg-1", "--set", "max_connections=500"], true); diff --git a/crates/clickhousectl/src/cloud/postgres.rs b/crates/clickhousectl/src/cloud/postgres.rs index 19506be..886d717 100644 --- a/crates/clickhousectl/src/cloud/postgres.rs +++ b/crates/clickhousectl/src/cloud/postgres.rs @@ -72,15 +72,7 @@ pub enum PostgresCommands { Update { postgres_id: String, #[arg(long)] - name: Option, - #[arg(long)] - region: Option, - #[arg(long)] size: Option, - #[arg(long)] - provider: Option, - #[arg(long, value_parser = clap::builder::PossibleValuesParser::new(KNOWN_PG_VERSIONS))] - pg_version: Option, #[arg(long, value_parser = clap::builder::PossibleValuesParser::new(KNOWN_PG_HA_TYPES))] ha_type: Option, /// Add a tag (repeatable), e.g. --add-tag env=prod @@ -436,11 +428,7 @@ pub struct PostgresCreateOptions<'a> { } pub struct PostgresUpdateOptions<'a> { - pub name: Option<&'a str>, - pub region: Option<&'a str>, pub size: Option<&'a str>, - pub provider: Option<&'a str>, - pub pg_version: Option<&'a str>, pub ha_type: Option<&'a str>, pub add_tag: &'a [String], pub remove_tag: &'a [String], @@ -630,15 +618,7 @@ pub async fn postgres_update( ) -> Result<(), Box> { let org_id = resolve_org_id(client, opts.org_id).await?; - let provider = opts - .provider - .map(|v| parse_serde_enum::(v, "provider", KNOWN_PG_PROVIDERS)) - .transpose()?; let size = opts.size.map(parse_pg_size).transpose()?; - let pg_version = opts - .pg_version - .map(|v| parse_serde_enum::(v, "pg-version", KNOWN_PG_VERSIONS)) - .transpose()?; let ha_type = opts .ha_type .map(|v| parse_serde_enum::(v, "ha-type", KNOWN_PG_HA_TYPES)) @@ -659,11 +639,7 @@ pub async fn postgres_update( }; let req = PostgresServicePatchRequest { - name: opts.name.map(|s| s.to_string()), - provider, - region: opts.region.map(|s| s.to_string()), size, - postgres_version: pg_version, ha_type, tags, }; @@ -1135,19 +1111,19 @@ mod tests { fn parses_postgres_update_tag_diff_flags() { let cmd = parse_postgres(&[ "clickhousectl", "cloud", "postgres", "update", "pg-1", - "--name", "renamed", + "--size", "c6gd.large", "--add-tag", "env=prod", "--add-tag", "team=data", "--remove-tag", "old", ]); let PostgresCommands::Update { - postgres_id, name, add_tag, remove_tag, .. + postgres_id, size, add_tag, remove_tag, .. } = cmd else { panic!("expected update"); }; assert_eq!(postgres_id, "pg-1"); - assert_eq!(name.as_deref(), Some("renamed")); + assert_eq!(size.as_deref(), Some("c6gd.large")); assert_eq!(add_tag, vec!["env=prod", "team=data"]); assert_eq!(remove_tag, vec!["old"]); } @@ -1155,11 +1131,11 @@ mod tests { #[test] fn parses_postgres_update_no_fields() { let cmd = parse_postgres(&["clickhousectl", "cloud", "postgres", "update", "pg-1"]); - let PostgresCommands::Update { postgres_id, name, .. } = cmd else { + let PostgresCommands::Update { postgres_id, size, .. } = cmd else { panic!("expected update"); }; assert_eq!(postgres_id, "pg-1"); - assert!(name.is_none()); + assert!(size.is_none()); } #[test] diff --git a/crates/clickhousectl/src/main.rs b/crates/clickhousectl/src/main.rs index af9e2e8..3513d12 100644 --- a/crates/clickhousectl/src/main.rs +++ b/crates/clickhousectl/src/main.rs @@ -1066,22 +1066,14 @@ async fn run_postgres( } PostgresCommands::Update { postgres_id, - name, - region, size, - provider, - pg_version, ha_type, add_tag, remove_tag, org_id, } => { let opts = PostgresUpdateOptions { - name: name.as_deref(), - region: region.as_deref(), size: size.as_deref(), - provider: provider.as_deref(), - pg_version: pg_version.as_deref(), ha_type: ha_type.as_deref(), add_tag: &add_tag, remove_tag: &remove_tag,