mirror of
https://github.com/openai/codex.git
synced 2026-11-01 19:33:35 +00:00
I did not wait for CI on https://github.com/openai/codex/pull/10980 because it was blocking an alpha release, but apparently it broken the Windows build.
150 lines
5.3 KiB
Rust
150 lines
5.3 KiB
Rust
use crate::config::ConfigToml;
|
|
use crate::config::types::RawMcpServerConfig;
|
|
use crate::features::FEATURES;
|
|
use schemars::r#gen::SchemaGenerator;
|
|
use schemars::r#gen::SchemaSettings;
|
|
use schemars::schema::InstanceType;
|
|
use schemars::schema::ObjectValidation;
|
|
use schemars::schema::RootSchema;
|
|
use schemars::schema::Schema;
|
|
use schemars::schema::SchemaObject;
|
|
use serde_json::Map;
|
|
use serde_json::Value;
|
|
use std::path::Path;
|
|
|
|
/// Schema for the `[features]` map with known + legacy keys only.
|
|
pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
|
let mut object = SchemaObject {
|
|
instance_type: Some(InstanceType::Object.into()),
|
|
..Default::default()
|
|
};
|
|
|
|
let mut validation = ObjectValidation::default();
|
|
for feature in FEATURES {
|
|
validation
|
|
.properties
|
|
.insert(feature.key.to_string(), schema_gen.subschema_for::<bool>());
|
|
}
|
|
for legacy_key in crate::features::legacy_feature_keys() {
|
|
validation
|
|
.properties
|
|
.insert(legacy_key.to_string(), schema_gen.subschema_for::<bool>());
|
|
}
|
|
validation.additional_properties = Some(Box::new(Schema::Bool(false)));
|
|
object.object = Some(Box::new(validation));
|
|
|
|
Schema::Object(object)
|
|
}
|
|
|
|
/// Schema for the `[mcp_servers]` map using the raw input shape.
|
|
pub(crate) fn mcp_servers_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
|
let mut object = SchemaObject {
|
|
instance_type: Some(InstanceType::Object.into()),
|
|
..Default::default()
|
|
};
|
|
|
|
let validation = ObjectValidation {
|
|
additional_properties: Some(Box::new(schema_gen.subschema_for::<RawMcpServerConfig>())),
|
|
..Default::default()
|
|
};
|
|
object.object = Some(Box::new(validation));
|
|
|
|
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::<ConfigToml>()
|
|
}
|
|
|
|
/// Canonicalize a JSON value by sorting its keys.
|
|
fn canonicalize(value: &Value) -> Value {
|
|
match value {
|
|
Value::Array(items) => Value::Array(items.iter().map(canonicalize).collect()),
|
|
Value::Object(map) => {
|
|
let mut entries: Vec<_> = map.iter().collect();
|
|
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
|
let mut sorted = Map::with_capacity(map.len());
|
|
for (key, child) in entries {
|
|
sorted.insert(key.clone(), canonicalize(child));
|
|
}
|
|
Value::Object(sorted)
|
|
}
|
|
_ => value.clone(),
|
|
}
|
|
}
|
|
|
|
/// Render the config schema as pretty-printed JSON.
|
|
pub fn config_schema_json() -> anyhow::Result<Vec<u8>> {
|
|
let schema = config_schema();
|
|
let value = serde_json::to_value(schema)?;
|
|
let value = canonicalize(&value);
|
|
let json = serde_json::to_vec_pretty(&value)?;
|
|
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::canonicalize;
|
|
use super::config_schema_json;
|
|
use super::write_config_schema;
|
|
|
|
use pretty_assertions::assert_eq;
|
|
use similar::TextDiff;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn config_schema_matches_fixture() {
|
|
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");
|
|
let schema_json = config_schema_json().expect("serialize config schema");
|
|
let schema_value: serde_json::Value =
|
|
serde_json::from_slice(&schema_json).expect("decode schema json");
|
|
let fixture_value = canonicalize(&fixture_value);
|
|
let schema_value = canonicalize(&schema_value);
|
|
if fixture_value != schema_value {
|
|
let expected =
|
|
serde_json::to_string_pretty(&fixture_value).expect("serialize fixture json");
|
|
let actual =
|
|
serde_json::to_string_pretty(&schema_value).expect("serialize schema json");
|
|
let diff = TextDiff::from_lines(&expected, &actual)
|
|
.unified_diff()
|
|
.header("fixture", "generated")
|
|
.to_string();
|
|
panic!(
|
|
"Current schema for `config.toml` doesn't match the fixture. \
|
|
Run `just write-config-schema` to overwrite with your changes.\n\n{diff}"
|
|
);
|
|
}
|
|
|
|
// Make sure the version in the repo matches exactly: https://github.com/openai/codex/pull/10977.
|
|
let tmp = TempDir::new().expect("create temp dir");
|
|
let tmp_path = tmp.path().join("config.schema.json");
|
|
write_config_schema(&tmp_path).expect("write config schema to temp path");
|
|
let tmp_contents =
|
|
std::fs::read_to_string(&tmp_path).expect("read back config schema from temp path");
|
|
#[cfg(windows)]
|
|
let fixture = fixture.replace("\r\n", "\n");
|
|
|
|
assert_eq!(
|
|
fixture, tmp_contents,
|
|
"fixture should match exactly with generated schema"
|
|
);
|
|
}
|
|
}
|