-
-
Notifications
You must be signed in to change notification settings - Fork 26
Support boolean schemas in OpenAPI 3.1 properties and composition keywords #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3946ddf
67b7670
cd787cd
bde0fc9
66445b6
ef30bb9
f6b35ab
83992af
75e9061
7b48c4b
e40aeb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,7 +124,7 @@ | |
| /// | ||
| /// See <https://json-schema.org/draft/2020-12/json-schema-core#name-allof>. | ||
| #[serde(rename = "allOf", default, skip_serializing_if = "Vec::is_empty")] | ||
| pub all_of: Vec<ObjectOrReference<ObjectSchema>>, | ||
| pub all_of: Vec<Schema>, | ||
|
|
||
| /// An instance validates successfully against this keyword if it validates successfully against | ||
| /// at least one schema defined by this keyword's value. | ||
|
|
@@ -134,7 +134,7 @@ | |
| /// | ||
| /// See <https://json-schema.org/draft/2020-12/json-schema-core#name-anyof>. | ||
| #[serde(rename = "anyOf", default, skip_serializing_if = "Vec::is_empty")] | ||
| pub any_of: Vec<ObjectOrReference<ObjectSchema>>, | ||
| pub any_of: Vec<Schema>, | ||
|
|
||
| /// An instance validates successfully against this keyword if it validates successfully against | ||
| /// exactly one schema defined by this keyword's value. | ||
|
|
@@ -144,7 +144,7 @@ | |
| /// | ||
| /// See <https://json-schema.org/draft/2020-12/json-schema-core#name-oneof>. | ||
| #[serde(rename = "oneOf", default, skip_serializing_if = "Vec::is_empty")] | ||
| pub one_of: Vec<ObjectOrReference<ObjectSchema>>, | ||
| pub one_of: Vec<Schema>, | ||
|
|
||
| // TODO: missing fields | ||
| // - not | ||
|
|
@@ -180,7 +180,7 @@ | |
| /// | ||
| /// See <https://json-schema.org/draft/2020-12/json-schema-core#name-prefixitems>. | ||
| #[serde(rename = "prefixItems", default, skip_serializing_if = "Vec::is_empty")] | ||
| pub prefix_items: Vec<ObjectOrReference<ObjectSchema>>, | ||
| pub prefix_items: Vec<Schema>, | ||
|
|
||
| // TODO: missing fields | ||
| // - contains | ||
|
|
@@ -199,7 +199,7 @@ | |
| /// | ||
| /// See <https://json-schema.org/draft/2020-12/json-schema-core#name-properties>. | ||
| #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] | ||
| pub properties: BTreeMap<String, ObjectOrReference<ObjectSchema>>, | ||
| pub properties: BTreeMap<String, Schema>, | ||
|
|
||
| /// Schema for additional object properties. | ||
| /// | ||
|
|
@@ -596,6 +596,25 @@ | |
| Object(Box<ObjectOrReference<ObjectSchema>>), | ||
| } | ||
|
|
||
| impl Schema { | ||
| /// Resolves the schema (if needed) from the given `spec` and returns an `ObjectSchema`. | ||
| /// | ||
| /// For boolean schemas, this returns an empty `ObjectSchema` since boolean schemas | ||
| /// don't have a traditional structure to resolve into. Note that this loses the boolean | ||
| /// validation semantics: | ||
| /// - `true` (accept all) becomes an empty schema with no constraints | ||
| /// - `false` (reject all) becomes an empty schema with no constraints | ||
| /// | ||
| /// Callers should check if the schema is a boolean schema and handle it specially | ||
| /// if boolean validation semantics need to be preserved. | ||
| pub fn resolve(&self, spec: &Spec) -> Result<ObjectSchema, RefError> { | ||
| match self { | ||
| Schema::Boolean(_) => Ok(ObjectSchema::default()), | ||
| Schema::Object(obj_ref) => obj_ref.resolve(spec), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Considers any value that is present as `Some`, including `null`. | ||
| fn distinguish_missing_and_null<'de, T, D>(de: D) -> Result<Option<T>, D::Error> | ||
| where | ||
|
|
@@ -690,16 +709,22 @@ | |
|
|
||
| assert_matches!( | ||
| &schema.prefix_items[0], | ||
| ObjectOrReference::Object(ObjectSchema { | ||
| schema_type: Some(TypeSet::Single(Type::String)), | ||
| .. | ||
| }), | ||
| Schema::Object(obj) if matches!( | ||
| &**obj, | ||
| ObjectOrReference::Object(ObjectSchema { | ||
| schema_type: Some(TypeSet::Single(Type::String)), | ||
| .. | ||
| }) | ||
| ), | ||
| "First prefixItems element should be inline string schema", | ||
| ); | ||
|
|
||
| assert_matches!( | ||
| &schema.prefix_items[1], | ||
| ObjectOrReference::Ref { ref_path, .. } if ref_path == "#/components/schemas/Age", | ||
| Schema::Object(obj) if matches!( | ||
| &**obj, | ||
| ObjectOrReference::Ref { ref_path, .. } if ref_path == "#/components/schemas/Age" | ||
| ), | ||
| "Second prefixItems element should be reference to an Age schema", | ||
| ); | ||
| } | ||
|
|
@@ -793,4 +818,176 @@ | |
|
|
||
| pretty_assertions::assert_eq!(spec, serialized); | ||
| } | ||
|
|
||
| #[test] | ||
| fn properties_supports_boolean_schemas() { | ||
| // Test for issue #278 - boolean schemas in properties | ||
| let spec = indoc::indoc! {" | ||
| type: object | ||
| properties: | ||
| data: true | ||
| meta: false | ||
| "}; | ||
| let schema = serde_yaml::from_str::<ObjectSchema>(spec).unwrap(); | ||
|
Check failure on line 831 in crates/oas3/src/spec/schema.rs
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'YAML helper usages in crates/oas3/src/spec/schema.rs:\n'
rg -n --type=rust '\b(serde_yaml|yaml_serde)::' crates/oas3/src/spec/schema.rs
printf '\nCargo manifests mentioning YAML crates:\n'
rg -n --glob 'Cargo.toml' 'serde_yaml|yaml_serde'Repository: x52dev/oas3-rs Length of output: 1934 Replace Lines 831, 876, 913, and 958–959 use
🧰 Tools🪛 GitHub Actions: CI[error] 831-831: error[E0433]: failed to resolve: use of unresolved module or unlinked crate 🪛 GitHub Actions: Lint[error] 831-831: clippy/compile error: failed to resolve use of unresolved module or unlinked crate 🪛 GitHub Check: clippy[failure] 831-831: 🪛 GitHub Check: Test / msrv[failure] 831-831: 🪛 GitHub Check: Test / stable[failure] 831-831: 🤖 Prompt for AI Agents |
||
|
|
||
| assert_eq!( | ||
| 2, | ||
| schema.properties.len(), | ||
| "properties should have two elements", | ||
| ); | ||
|
|
||
| // Check data property is true boolean schema | ||
| let data_schema = schema | ||
| .properties | ||
| .get("data") | ||
| .expect("data property should exist"); | ||
| assert_matches!( | ||
| data_schema, | ||
| Schema::Boolean(BooleanSchema(true)), | ||
| "data property should be boolean schema with value true", | ||
| ); | ||
|
|
||
| // Check meta property is false boolean schema | ||
| let meta_schema = schema | ||
| .properties | ||
| .get("meta") | ||
| .expect("meta property should exist"); | ||
| assert_matches!( | ||
| meta_schema, | ||
| Schema::Boolean(BooleanSchema(false)), | ||
| "meta property should be boolean schema with value false", | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn allof_anyof_oneof_support_boolean_schemas() { | ||
| // Test for issue #278 - boolean schemas in allOf, anyOf, oneOf | ||
| let spec = indoc::indoc! {" | ||
| allOf: | ||
| - type: string | ||
| - true | ||
| anyOf: | ||
| - false | ||
| - type: number | ||
| oneOf: | ||
| - true | ||
| - $ref: '#/components/schemas/Test' | ||
| "}; | ||
| let schema = serde_yaml::from_str::<ObjectSchema>(spec).unwrap(); | ||
|
Check failure on line 876 in crates/oas3/src/spec/schema.rs
|
||
|
|
||
| // Check allOf | ||
| assert_eq!(2, schema.all_of.len(), "allOf should have two elements"); | ||
| assert_matches!( | ||
| &schema.all_of[1], | ||
| Schema::Boolean(BooleanSchema(true)), | ||
| "Second allOf element should be boolean schema with value true", | ||
| ); | ||
|
|
||
| // Check anyOf | ||
| assert_eq!(2, schema.any_of.len(), "anyOf should have two elements"); | ||
| assert_matches!( | ||
| &schema.any_of[0], | ||
| Schema::Boolean(BooleanSchema(false)), | ||
| "First anyOf element should be boolean schema with value false", | ||
| ); | ||
|
|
||
| // Check oneOf | ||
| assert_eq!(2, schema.one_of.len(), "oneOf should have two elements"); | ||
| assert_matches!( | ||
| &schema.one_of[0], | ||
| Schema::Boolean(BooleanSchema(true)), | ||
| "First oneOf element should be boolean schema with value true", | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn prefix_items_supports_boolean_schemas() { | ||
| // Test for issue #278 - boolean schemas in prefixItems | ||
| let spec = indoc::indoc! {" | ||
| type: array | ||
| prefixItems: | ||
| - true | ||
| - false | ||
| - type: string | ||
| "}; | ||
| let schema = serde_yaml::from_str::<ObjectSchema>(spec).unwrap(); | ||
|
Check failure on line 913 in crates/oas3/src/spec/schema.rs
|
||
|
|
||
| assert_eq!( | ||
| 3, | ||
| schema.prefix_items.len(), | ||
| "prefixItems should have three elements" | ||
| ); | ||
|
|
||
| assert_matches!( | ||
| &schema.prefix_items[0], | ||
| Schema::Boolean(BooleanSchema(true)), | ||
| "First prefixItems element should be boolean schema with value true", | ||
| ); | ||
|
|
||
| assert_matches!( | ||
| &schema.prefix_items[1], | ||
| Schema::Boolean(BooleanSchema(false)), | ||
| "Second prefixItems element should be boolean schema with value false", | ||
| ); | ||
|
|
||
| assert_matches!( | ||
| &schema.prefix_items[2], | ||
| Schema::Object(obj) if matches!( | ||
| &**obj, | ||
|
Check failure on line 936 in crates/oas3/src/spec/schema.rs
|
||
| ObjectOrReference::Object(ObjectSchema { | ||
| schema_type: Some(TypeSet::Single(Type::String)), | ||
| .. | ||
| }) | ||
| ), | ||
| "Third prefixItems element should be inline string schema", | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn boolean_schema_serialization_round_trip() { | ||
| // Test for issue #278 - boolean schemas serialization round trip | ||
| let spec = indoc::indoc! {" | ||
| properties: | ||
| data: true | ||
| meta: false | ||
| name: | ||
| type: string | ||
| type: object | ||
| "}; | ||
|
|
||
| let original = serde_yaml::from_str::<ObjectSchema>(spec).unwrap(); | ||
|
Check failure on line 958 in crates/oas3/src/spec/schema.rs
|
||
| let serialized = serde_yaml::to_string(&original).unwrap(); | ||
|
Check failure on line 959 in crates/oas3/src/spec/schema.rs
|
||
|
|
||
| pretty_assertions::assert_eq!(spec, serialized); | ||
| } | ||
|
|
||
| #[test] | ||
| fn boolean_schema_json_parsing() { | ||
| // Test for issue #278 - ensure JSON parsing works (aide generates JSON) | ||
| let json_spec = r#"{ | ||
| "type": "object", | ||
| "properties": { | ||
| "data": true, | ||
| "meta": true | ||
| } | ||
| }"#; | ||
|
|
||
| let schema: ObjectSchema = serde_json::from_str(json_spec).expect("should parse JSON"); | ||
|
|
||
| assert_eq!(2, schema.properties.len()); | ||
| assert_matches!( | ||
| schema.properties.get("data"), | ||
| Some(Schema::Boolean(BooleanSchema(true))) | ||
| ); | ||
| assert_matches!( | ||
| schema.properties.get("meta"), | ||
| Some(Schema::Boolean(BooleanSchema(true))) | ||
| ); | ||
|
|
||
| // Test round-trip | ||
| let json_output = serde_json::to_string(&schema).expect("should serialize to JSON"); | ||
| let schema2: ObjectSchema = | ||
| serde_json::from_str(&json_output).expect("should parse JSON again"); | ||
| assert_eq!(schema, schema2); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Schema::resolve()currently erasesfalseschema semantics.Line 612 turns
Schema::Boolean(false)intoObjectSchema::default(), which downstream code will treat the same as{}/ accept-all.crates/roast/src/validation/validator.rsalready calls.resolve()before building validation trees for properties, items, and composition branches, so afalseschema now validates everything instead of rejecting everything. I don’t think aResult<ObjectSchema, RefError>API can represent this correctly; please preserve the boolean in the return type, or force callers to pattern-matchSchemabefore resolving object refs.🤖 Prompt for AI Agents