Skip to content
Open
2 changes: 2 additions & 0 deletions crates/oas3/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Support boolean schemas in `spec::ObjectSchema::{all_of, any_of, one_of, prefix_items, properties}`.
- Add `spec::Schema::resolve()` method.
- The type of the `spec::Operation::callbacks` field is now `BTreeMap<String, ObjectOrReference<Callback>>`.
- Add `spec::Operation::callbacks()` method.
- Migrate YAML parsing to `yaml_serde`. Exposed error type(s) have been altered.
Expand Down
217 changes: 207 additions & 10 deletions crates/oas3/src/spec/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
///
Expand Down Expand Up @@ -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),
}
}
}
Comment on lines +599 to +616
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Schema::resolve() currently erases false schema semantics.

Line 612 turns Schema::Boolean(false) into ObjectSchema::default(), which downstream code will treat the same as {} / accept-all. crates/roast/src/validation/validator.rs already calls .resolve() before building validation trees for properties, items, and composition branches, so a false schema now validates everything instead of rejecting everything. I don’t think a Result<ObjectSchema, RefError> API can represent this correctly; please preserve the boolean in the return type, or force callers to pattern-match Schema before resolving object refs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/oas3/src/spec/schema.rs` around lines 599 - 616, Schema::resolve
currently converts Schema::Boolean(false) into ObjectSchema::default(), losing
reject-all semantics; update the API so boolean schemas are preserved instead of
being erased: change Schema::resolve (and its return type Result<ObjectSchema,
RefError>) to return an enum that can represent both booleans and object schemas
(e.g., ResolvedSchema::Boolean(bool) | ResolvedSchema::Object(ObjectSchema)) or
alternatively remove resolve from handling boolean schemas and require callers
to pattern-match Schema before calling obj_ref.resolve; update the function
signature and implementations that call Schema::resolve (including the code
paths in crates/roast/src/validation/validator.rs) to handle the new
ResolvedSchema variants and ensure Schema::Boolean(false) continues to mean
reject-all rather than being treated as an empty accept-all ObjectSchema.


/// 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
Expand Down Expand Up @@ -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",
);
}
Expand Down Expand Up @@ -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

View workflow job for this annotation

GitHub Actions / clippy

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 831 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / stable

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 831 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / msrv

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 serde_yaml with yaml_serde in these new tests.

Lines 831, 876, 913, and 958–959 use serde_yaml, which is not a declared dependency. The codebase consistently uses yaml_serde throughout this module. Swap the calls to restore compatibility:

  • Line 831: serde_yaml::from_stryaml_serde::from_str
  • Line 876: serde_yaml::from_stryaml_serde::from_str
  • Line 913: serde_yaml::from_stryaml_serde::from_str
  • Lines 958–959: serde_yaml::from_str and serde_yaml::to_stringyaml_serde::from_str and yaml_serde::to_string
🧰 Tools
🪛 GitHub Actions: CI

[error] 831-831: error[E0433]: failed to resolve: use of unresolved module or unlinked crate serde_yaml (missing dependency).

🪛 GitHub Actions: Lint

[error] 831-831: clippy/compile error: failed to resolve use of unresolved module or unlinked crate serde_yaml

🪛 GitHub Check: clippy

[failure] 831-831:
failed to resolve: use of unresolved module or unlinked crate serde_yaml

🪛 GitHub Check: Test / msrv

[failure] 831-831:
failed to resolve: use of unresolved module or unlinked crate serde_yaml

🪛 GitHub Check: Test / stable

[failure] 831-831:
failed to resolve: use of unresolved module or unlinked crate serde_yaml

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/oas3/src/spec/schema.rs` at line 831, The tests use serde_yaml instead
of the project-standard yaml_serde causing missing dependency errors; replace
calls to serde_yaml::from_str and serde_yaml::to_string with
yaml_serde::from_str and yaml_serde::to_string in the test code that
constructs/parses ObjectSchema and related test helpers so the
parsing/serialization for ObjectSchema uses yaml_serde (specifically change the
usages at the ObjectSchema deserialization and the subsequent to_string
round-trip calls).


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

View workflow job for this annotation

GitHub Actions / clippy

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 876 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / stable

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 876 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / msrv

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

// 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

View workflow job for this annotation

GitHub Actions / clippy

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 913 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / stable

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 913 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / msrv

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

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

View workflow job for this annotation

GitHub Actions / clippy

type `spec::r#ref::ObjectOrReference<spec::schema::ObjectSchema>` cannot be dereferenced

Check failure on line 936 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / stable

type `r#ref::ObjectOrReference<schema::ObjectSchema>` cannot be dereferenced

Check failure on line 936 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / msrv

type `r#ref::ObjectOrReference<schema::ObjectSchema>` cannot be dereferenced
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

View workflow job for this annotation

GitHub Actions / clippy

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 958 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / stable

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 958 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / msrv

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`
let serialized = serde_yaml::to_string(&original).unwrap();

Check failure on line 959 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / clippy

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 959 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / stable

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

Check failure on line 959 in crates/oas3/src/spec/schema.rs

View workflow job for this annotation

GitHub Actions / Test / msrv

failed to resolve: use of unresolved module or unlinked crate `serde_yaml`

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);
}
}
Loading