From 31125c4f9e3f47b59ccb215eb89f47e4b193dadb Mon Sep 17 00:00:00 2001 From: Sayan Sisodiya Date: Fri, 9 Jan 2026 18:59:53 -0800 Subject: [PATCH] mv regen to bin, add seperate ci step, compare actual json, docs --- .github/workflows/rust-ci.yml | 4 ++ AGENTS.md | 1 + codex-rs/core/Cargo.toml | 4 ++ {docs => codex-rs/core}/config.schema.json | 0 codex-rs/core/src/bin/config_schema.rs | 25 +++++++ codex-rs/core/src/config/SCHEMA.md | 5 +- codex-rs/core/src/config/mod.rs | 2 +- codex-rs/core/src/config/schema.rs | 82 ++++++++++------------ docs/config.md | 2 +- justfile | 2 +- 10 files changed, 77 insertions(+), 50 deletions(-) rename {docs => codex-rs/core}/config.schema.json (100%) create mode 100644 codex-rs/core/src/bin/config_schema.rs diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 1af0bf2f4a..a2546aa5e6 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -66,6 +66,10 @@ jobs: run: cargo fmt -- --config imports_granularity=Item --check - name: Verify codegen for mcp-types run: ./mcp-types/check_lib_rs.py + - name: Verify config schema fixture + run: | + cargo run -p codex-core --bin codex-write-config-schema + git diff --exit-code core/config.schema.json cargo_shear: name: cargo shear diff --git a/AGENTS.md b/AGENTS.md index 5c0a6db637..9c14089e5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,7 @@ In the codex-rs folder where the rust code lives: - Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls - When writing tests, prefer comparing the equality of entire objects over fields one by one. - When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable. +- If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`. Run `just fmt` (in `codex-rs` directory) automatically after making Rust code changes; do not ask for approval to run it. Before finalizing a change to `codex-rs`, run `just fix -p ` (in `codex-rs` directory) to fix any linter issues in the code. Prefer scoping with `-p` to avoid slow workspace‑wide Clippy builds; only run `just fix` without `-p` if you changed shared crates. Additionally, run the tests: diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 860ecce35b..c4da196f67 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -9,6 +9,10 @@ doctest = false name = "codex_core" path = "src/lib.rs" +[[bin]] +name = "codex-write-config-schema" +path = "src/bin/config_schema.rs" + [lints] workspace = true diff --git a/docs/config.schema.json b/codex-rs/core/config.schema.json similarity index 100% rename from docs/config.schema.json rename to codex-rs/core/config.schema.json diff --git a/codex-rs/core/src/bin/config_schema.rs b/codex-rs/core/src/bin/config_schema.rs new file mode 100644 index 0000000000..cf2e6b88d4 --- /dev/null +++ b/codex-rs/core/src/bin/config_schema.rs @@ -0,0 +1,25 @@ +use anyhow::Context; +use anyhow::Result; +use std::env; +use std::path::PathBuf; + +/// Generate the JSON Schema for `config.toml` and write it to `config.schema.json`. +fn main() -> Result<()> { + let mut args = env::args().skip(1); + let mut out_path = None; + + while let Some(arg) = args.next() { + match arg.as_str() { + "--out" | "-o" => { + let value = args.next().context("expected a path after --out/-o")?; + out_path = Some(PathBuf::from(value)); + } + _ => anyhow::bail!("unknown argument: {arg}"), + } + } + + let out_path = out_path + .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config.schema.json")); + codex_core::config::schema::write_config_schema(&out_path)?; + Ok(()) +} diff --git a/codex-rs/core/src/config/SCHEMA.md b/codex-rs/core/src/config/SCHEMA.md index 848b2b8421..101c57b363 100644 --- a/codex-rs/core/src/config/SCHEMA.md +++ b/codex-rs/core/src/config/SCHEMA.md @@ -1,12 +1,11 @@ # Config JSON Schema We generate a JSON Schema for `~/.codex/config.toml` from the `ConfigToml` type -and commit it at `docs/config.schema.json` for editor integration. +and commit it at `codex-rs/core/config.schema.json` for editor integration. When you change any fields included in `ConfigToml` (or nested config types), -regenerate the schema and update the fixture: +regenerate the schema: ``` just write-config-schema -cargo test -p codex-core config_schema_matches_fixture ``` diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 49575db9b6..b8a2f700a2 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -684,7 +684,7 @@ pub fn set_default_oss_provider(codex_home: &Path, provider: &str) -> std::io::R } /// Base config deserialized from ~/.codex/config.toml. -// The JSON Schema in `docs/config.schema.json` is generated from this struct. +// The JSON Schema in `codex-rs/core/config.schema.json` is generated from this struct. // If you add, remove, or rename fields, regenerate the schema via // `just write-config-schema` and update the fixture test. #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)] diff --git a/codex-rs/core/src/config/schema.rs b/codex-rs/core/src/config/schema.rs index dad2cd8951..fa31a0786c 100644 --- a/codex-rs/core/src/config/schema.rs +++ b/codex-rs/core/src/config/schema.rs @@ -1,39 +1,15 @@ -#[cfg(test)] use crate::config::ConfigToml; use crate::config::types::RawMcpServerConfig; use crate::features::FEATURES; use schemars::r#gen::SchemaGenerator; -#[cfg(test)] use schemars::r#gen::SchemaSettings; use schemars::schema::InstanceType; use schemars::schema::ObjectValidation; -#[cfg(test)] use schemars::schema::RootSchema; use schemars::schema::Schema; use schemars::schema::SchemaObject; -#[cfg(test)] use std::path::Path; -/// Build the config schema used by the fixture test. -#[cfg(test)] -pub(crate) fn config_schema() -> RootSchema { - SchemaSettings::draft07() - .with(|settings| { - settings.option_add_null_type = false; - }) - .into_generator() - .into_root_schema_for::() -} - -/// Write the config schema fixture to disk. -#[cfg(test)] -pub(crate) fn write_config_schema(out_path: &Path) -> anyhow::Result<()> { - let schema = config_schema(); - let json = serde_json::to_vec_pretty(&schema)?; - std::fs::write(out_path, json)?; - Ok(()) -} - /// Schema for the `[features]` map with known + legacy keys only. pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema { let mut object = SchemaObject { @@ -74,32 +50,50 @@ pub(crate) fn mcp_servers_schema(schema_gen: &mut SchemaGenerator) -> Schema { Schema::Object(object) } +/// Build the config schema for `config.toml`. +pub fn config_schema() -> RootSchema { + SchemaSettings::draft07() + .with(|settings| { + settings.option_add_null_type = false; + }) + .into_generator() + .into_root_schema_for::() +} + +/// Render the config schema as pretty-printed JSON. +pub fn config_schema_json() -> anyhow::Result> { + let schema = config_schema(); + let json = serde_json::to_vec_pretty(&schema)?; + Ok(json) +} + +/// Write the config schema fixture to disk. +pub fn write_config_schema(out_path: &Path) -> anyhow::Result<()> { + let json = config_schema_json()?; + std::fs::write(out_path, json)?; + Ok(()) +} + #[cfg(test)] mod tests { - use super::*; - use pretty_assertions::assert_eq; + use super::config_schema_json; + use similar::TextDiff; #[test] fn config_schema_matches_fixture() { - let schema = config_schema(); - let schema_value = serde_json::to_value(schema).expect("serialize config schema"); - let fixture_path = codex_utils_cargo_bin::find_resource!("../../docs/config.schema.json") + let fixture_path = codex_utils_cargo_bin::find_resource!("config.schema.json") .expect("resolve config schema fixture path"); let fixture = std::fs::read_to_string(fixture_path).expect("read config schema fixture"); - let fixture_value: serde_json::Value = - serde_json::from_str(&fixture).expect("parse config schema fixture"); - assert_eq!( - fixture_value, schema_value, - "Current schema for `config.toml` doesn't match the fixture. Run `just write-config-schema` to overwrite with your changes." - ); - } - - /// Overwrite the config schema fixture with the current schema. - #[test] - #[ignore] - fn write_config_schema_fixture() { - let fixture_path = codex_utils_cargo_bin::find_resource!("../../docs/config.schema.json") - .expect("resolve config schema fixture path"); - write_config_schema(&fixture_path).expect("write config schema fixture"); + let schema_json = config_schema_json().expect("serialize config schema"); + let schema_str = String::from_utf8(schema_json).expect("decode schema json"); + if fixture != schema_str { + let diff = TextDiff::from_lines(&fixture, &schema_str) + .unified_diff() + .to_string(); + let short = diff.lines().take(50).collect::>().join("\n"); + panic!( + "Current schema for `config.toml` doesn't match the fixture. Run `just write-config-schema` to overwrite with your changes.\n\n{short}" + ); + } } } diff --git a/docs/config.md b/docs/config.md index 934f3954f2..0b5fecf6e7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -20,4 +20,4 @@ Codex can run a notification hook when the agent finishes a turn. See the config ## JSON Schema -The generated JSON Schema for `config.toml` lives at `docs/config.schema.json`. +The generated JSON Schema for `config.toml` lives at `codex-rs/core/config.schema.json`. diff --git a/justfile b/justfile index 2d4c60d5a8..64f470ca7a 100644 --- a/justfile +++ b/justfile @@ -59,4 +59,4 @@ mcp-server-run *args: # Regenerate the json schema for config.toml from the current config types. write-config-schema: - cargo test -p codex-core write_config_schema_fixture -- --ignored + cargo run -p codex-core --bin codex-write-config-schema