@@ -191,10 +191,20 @@ pub struct Schema {
191191 serialize_with = "serialize_number_constraint"
192192 ) ]
193193 pub maximum : Option < f64 > ,
194- /// Exclusive minimum
194+ /// Exclusive minimum.
195+ ///
196+ /// NOTE: currently modeled as the OpenAPI 3.0 / draft-04 **boolean
197+ /// flag** (paired with `minimum`). Migrating this to the JSON Schema
198+ /// 2020-12 / OpenAPI 3.1 **numeric** form is tracked as a deliberate,
199+ /// breaking spec-conformance change (it alters generated output and the
200+ /// `#[schema(exclusive_minimum)]` attribute semantics) — see the 3.1
201+ /// conformance decision, not done here to avoid a half-migrated model.
195202 #[ serde( skip_serializing_if = "Option::is_none" ) ]
196203 pub exclusive_minimum : Option < bool > ,
197- /// Exclusive maximum
204+ /// Exclusive maximum.
205+ ///
206+ /// See [`Schema::exclusive_minimum`]: still the OpenAPI 3.0 boolean
207+ /// flag, pending the bundled strict-3.1 conformance migration.
198208 #[ serde( skip_serializing_if = "Option::is_none" ) ]
199209 pub exclusive_maximum : Option < bool > ,
200210 /// Multiple of
@@ -411,17 +421,23 @@ impl Schema {
411421 Ok ( schema) => schema,
412422 Err ( e) => {
413423 // Surface the (in-practice-unreachable) macro/serde drift in
414- // debug / CI builds while degrading gracefully in release.
415- // `debug_assert!` keeps `e` referenced in both profiles (its
416- // release expansion is a dead `if false` branch), so there is
417- // no unused-binding warning.
424+ // debug / CI builds via `debug_assert!`. In release, degrade
425+ // to a VISIBLE sentinel schema (a description-only object)
426+ // rather than a silent `Schema::default()`, so a drift never
427+ // disappears unnoticed from the generated spec yet never
428+ // panics in downstream user code.
418429 debug_assert ! (
419430 false ,
420431 "vespera: Schema::from_compiled_json failed to parse macro-emitted \
421- JSON ({e}); falling back to Schema::default() . This indicates a \
432+ JSON ({e}); emitting a sentinel schema . This indicates a \
422433 vespera bug — the macro serialized a Schema that cannot round-trip."
423434 ) ;
424- Self :: default ( )
435+ Self {
436+ description : Some ( format ! (
437+ "vespera: schema unavailable — macro/serde drift ({e})"
438+ ) ) ,
439+ ..Self :: default ( )
440+ }
425441 }
426442 }
427443 }
@@ -489,6 +505,10 @@ pub enum SecuritySchemeType {
489505 /// `mutualTls` the container rule would produce).
490506 #[ serde( rename = "mutualTLS" ) ]
491507 MutualTls ,
508+ /// OpenAPI's canonical wire name is `oauth2`; the `camelCase` container
509+ /// rule would otherwise lowercase only the leading char and emit the
510+ /// invalid `oAuth2`.
511+ #[ serde( rename = "oauth2" ) ]
492512 OAuth2 ,
493513 OpenIdConnect ,
494514}
@@ -662,6 +682,38 @@ mod tests {
662682 ) ;
663683 }
664684
685+ // ── CORE: OpenAPI 3.1 conformance of the schema model ────────────
686+
687+ #[ test]
688+ fn oauth2_security_scheme_serializes_to_canonical_lowercase ( ) {
689+ // OpenAPI's canonical wire name is `oauth2`. serde's `camelCase`
690+ // container rule lowercases only the leading char, which would emit
691+ // the invalid `oAuth2` without the explicit `#[serde(rename)]`.
692+ let json = serde_json:: to_string ( & SecuritySchemeType :: OAuth2 ) . unwrap ( ) ;
693+ assert_eq ! ( json, "\" oauth2\" " , "must be exactly \" oauth2\" " ) ;
694+ }
695+
696+ #[ rstest]
697+ #[ case( SecuritySchemeType :: ApiKey , "\" apiKey\" " ) ]
698+ #[ case( SecuritySchemeType :: Http , "\" http\" " ) ]
699+ #[ case( SecuritySchemeType :: MutualTls , "\" mutualTLS\" " ) ]
700+ #[ case( SecuritySchemeType :: OAuth2 , "\" oauth2\" " ) ]
701+ #[ case( SecuritySchemeType :: OpenIdConnect , "\" openIdConnect\" " ) ]
702+ fn security_scheme_type_uses_openapi_canonical_wire_names (
703+ #[ case] ty : SecuritySchemeType ,
704+ #[ case] expected : & str ,
705+ ) {
706+ assert_eq ! ( serde_json:: to_string( & ty) . unwrap( ) , expected) ;
707+ }
708+
709+ #[ test]
710+ #[ should_panic( expected = "from_compiled_json failed to parse" ) ]
711+ fn from_compiled_json_invalid_input_trips_debug_assert ( ) {
712+ // In debug / test builds the (in-practice-unreachable) macro/serde
713+ // drift guard fires loudly so a bug never goes unnoticed in CI.
714+ let _ = Schema :: from_compiled_json ( "{not valid json" ) ;
715+ }
716+
665717 // ── CORE-04: typed `additionalProperties` (untagged) ─────────────
666718 //
667719 // The untagged enum MUST serialize to the bare JSON Schema wire form
0 commit comments