fix(cli): serialize the resolved locator so config view --resolved omits no field

config view --resolved hand-listed locator fields and dropped an embedded graph's
region/endpoint/policy_file/selected — exactly the fields one debugs an S3 endpoint or
per-graph policy with. Derive Serialize on GraphLocator (internally tagged `kind:`) and
serialize+prune in print_resolved_locator, so every field is reported and a new field
can't be forgotten by hand.
This commit is contained in:
Ragnor Comerford 2026-06-05 12:38:29 +02:00
parent 938248dfcf
commit 34c0af8469
No known key found for this signature in database
3 changed files with 21 additions and 63 deletions

View file

@ -898,68 +898,19 @@ fn print_config_origins(provenance: &Provenance, json: bool) -> Result<()> {
}
}
/// Print a resolved [`GraphLocator`] (embedded vs remote) in human or JSON form.
/// Print a resolved [`GraphLocator`] (embedded vs remote). Serializes the typed
/// locator (internally tagged `kind:`) and prunes null/empty values, so every
/// field — including an Embedded graph's `region`/`endpoint`/`policy_file` — is
/// reported and no field can be forgotten by hand.
fn print_resolved_locator(locator: &GraphLocator, json: bool) -> Result<()> {
match locator {
GraphLocator::Embedded {
uri,
branch,
snapshot,
graph_id,
..
} => {
if json {
print_json(&serde_json::json!({
"kind": "embedded",
"uri": uri,
"graph_id": graph_id,
"branch": branch,
"snapshot": snapshot,
}))
} else {
println!("embedded");
println!(" uri: {uri}");
println!(" graph_id: {graph_id}");
if let Some(branch) = branch {
println!(" branch: {branch}");
}
if let Some(snapshot) = snapshot {
println!(" snapshot: {snapshot}");
}
Ok(())
}
}
GraphLocator::Remote {
endpoint,
server,
graph_id,
branch,
snapshot,
} => {
if json {
print_json(&serde_json::json!({
"kind": "remote",
"endpoint": endpoint,
"server": server,
"graph_id": graph_id,
"branch": branch,
"snapshot": snapshot,
}))
} else {
println!("remote");
println!(" endpoint: {endpoint}");
println!(" server: {server}");
println!(" graph_id: {graph_id}");
if let Some(branch) = branch {
println!(" branch: {branch}");
}
if let Some(snapshot) = snapshot {
println!(" snapshot: {snapshot}");
}
Ok(())
}
}
let mut value = serde_yaml::to_value(locator)?;
prune_empty(&mut value);
if json {
println!("{}", serde_json::to_string_pretty(&value)?);
} else {
print!("{}", serde_yaml::to_string(&value)?);
}
Ok(())
}
#[derive(Debug, Clone)]

View file

@ -336,7 +336,7 @@ fn config_view_resolved_prints_embedded_and_remote_locators() {
write_config(
&config,
"version: 1\nservers:\n prod:\n endpoint: https://prod.example\n\
graphs:\n local:\n storage: ./l.omni\n staging:\n server: prod\n graph_id: prod\n",
graphs:\n local:\n storage:\n uri: ./l.omni\n region: eu-west-1\n endpoint: https://minio.local\n staging:\n server: prod\n graph_id: prod\n",
);
let embedded = parse_stdout_json(&output_success(
@ -351,6 +351,10 @@ fn config_view_resolved_prints_embedded_and_remote_locators() {
));
assert_eq!(embedded["kind"], "embedded");
assert!(embedded["uri"].as_str().unwrap().ends_with("l.omni"));
// The serialized locator exposes every field — incl. the storage block's
// region/endpoint, which the old hand-listed printer dropped.
assert_eq!(embedded["region"], "eu-west-1");
assert_eq!(embedded["endpoint"], "https://minio.local");
let remote = parse_stdout_json(&output_success(
cli()

View file

@ -169,8 +169,11 @@ pub struct ServerEntry {
}
/// A resolved graph address (RFC-002 §1.1/§2) — replaces scheme-sniffing on a
/// `uri` string with a typed embedded-XOR-remote locator.
#[derive(Debug, Clone)]
/// `uri` string with a typed embedded-XOR-remote locator. `Serialize` is
/// internally tagged (`kind: embedded|remote`) so `config view --resolved` dumps
/// every field without hand-listing — a new field can't be silently omitted.
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum GraphLocator {
Embedded {
/// Object-store URI, resolved against the config `base_dir`.