@@ -637,11 +637,17 @@ fn enum_context(root: &Value, path: &[String]) -> EnumContext {
637637 schema : path[ 2 ] . clone ( ) ,
638638 } ;
639639 }
640- if path. len ( ) >= 5 && path[ 0 ] == "components" && path[ 1 ] == "schemas" && path[ 3 ] == "properties"
640+ if path. len ( ) >= 5
641+ && path[ 0 ] == "components"
642+ && path[ 1 ] == "schemas"
643+ && let Some ( properties_index) = path[ 3 ..]
644+ . iter ( )
645+ . position ( |part| part == "properties" )
646+ . map ( |index| index + 3 )
641647 {
642648 return EnumContext :: Property {
643649 schema : path[ 2 ] . clone ( ) ,
644- steps : property_steps ( & path[ 3 ..] ) ,
650+ steps : property_steps ( & path[ properties_index ..] ) ,
645651 } ;
646652 }
647653 if path. len ( ) > 3
@@ -895,6 +901,82 @@ mod tests {
895901 }
896902 }
897903
904+ #[ test]
905+ fn attributes_property_enums_beneath_top_level_compositions ( ) {
906+ let spec = serde_json:: json!( {
907+ "paths" : { } ,
908+ "components" : { "schemas" : {
909+ "AllOfWidget" : { "allOf" : [ { "properties" : {
910+ "status" : { "enum" : [ "on" ] }
911+ } } ] } ,
912+ "OneOfWidget" : { "oneOf" : [ { "properties" : {
913+ "states" : { "type" : "array" , "items" : { "enum" : [ "ready" ] } }
914+ } } ] } ,
915+ "AnyOfWidget" : { "anyOf" : [ { "properties" : {
916+ "settings" : { "properties" : {
917+ "mode" : { "enum" : [ "fast" ] }
918+ } }
919+ } } ] } ,
920+ "NamedChoice" : { "allOf" : [ { "enum" : [ "a" ] } ] }
921+ } }
922+ } ) ;
923+ let inventory = OpenApiInventory :: build ( & spec, & AnalyzerConfig :: default ( ) ) . unwrap ( ) ;
924+ let by_pointer: BTreeMap < String , EnumContext > = inventory
925+ . enum_constraints
926+ . iter ( )
927+ . map ( |constraint| ( constraint. pointer . clone ( ) , constraint. context . clone ( ) ) )
928+ . collect ( ) ;
929+
930+ for ( pointer, schema, steps) in [
931+ (
932+ "/components/schemas/AllOfWidget/allOf/0/properties/status" ,
933+ "AllOfWidget" ,
934+ vec ! [ PropertyStep {
935+ property: "status" . to_string( ) ,
936+ array_item: false ,
937+ } ] ,
938+ ) ,
939+ (
940+ "/components/schemas/OneOfWidget/oneOf/0/properties/states/items" ,
941+ "OneOfWidget" ,
942+ vec ! [ PropertyStep {
943+ property: "states" . to_string( ) ,
944+ array_item: true ,
945+ } ] ,
946+ ) ,
947+ (
948+ "/components/schemas/AnyOfWidget/anyOf/0/properties/settings/properties/mode" ,
949+ "AnyOfWidget" ,
950+ vec ! [
951+ PropertyStep {
952+ property: "settings" . to_string( ) ,
953+ array_item: false ,
954+ } ,
955+ PropertyStep {
956+ property: "mode" . to_string( ) ,
957+ array_item: false ,
958+ } ,
959+ ] ,
960+ ) ,
961+ ] {
962+ match & by_pointer[ pointer] {
963+ EnumContext :: Property {
964+ schema : actual_schema,
965+ steps : actual_steps,
966+ } => {
967+ assert_eq ! ( actual_schema, schema) ;
968+ assert_eq ! ( actual_steps, & steps) ;
969+ }
970+ other => panic ! ( "expected property context for {pointer}, got {other:?}" ) ,
971+ }
972+ }
973+
974+ assert ! ( matches!(
975+ & by_pointer[ "/components/schemas/NamedChoice/allOf/0" ] ,
976+ EnumContext :: NamedSchema { schema } if schema == "NamedChoice"
977+ ) ) ;
978+ }
979+
898980 #[ test]
899981 fn keeps_single_property_chains_for_the_common_case ( ) {
900982 let spec = serde_json:: json!( {
0 commit comments