@@ -601,19 +601,35 @@ func parseYaml(t *testing.T, yamlStr string) *yaml.Node {
601601}
602602
603603func TestDetectOpenAPIComponentType_QuotedKeys (t * testing.T ) {
604- // Test that quoted "items" key is NOT treated as schema
605- quotedItemsYaml := `
604+ // mixed-style: a user deliberately escaping a single OpenAPI keyword in otherwise
605+ // plain YAML. The escape signals "treat this key as a literal string", so the
606+ // quoted key must not participate in component-type detection. The unquoted key
607+ // (`type`) still classifies this mapping as a schema.
608+ mixedQuotedItemsYaml := `
609+ type: object
606610"items":
607611 - product_id: 1000012
608612 fulfillment_node_id: US01
609613 count: 10
610614`
611- node := parseYaml (t , quotedItemsYaml )
615+ node := parseYaml (t , mixedQuotedItemsYaml )
612616 componentType , detected := DetectOpenAPIComponentType (node )
617+ assert .Equal (t , v3 .SchemasLabel , componentType )
618+ assert .True (t , detected )
619+
620+ // fully escaped: every key is quoted with no other schema indicators. With nothing
621+ // left to classify on, detection correctly fails.
622+ fullyEscapedYaml := `
623+ description: not a schema
624+ "items":
625+ - product_id: 1000012
626+ `
627+ node = parseYaml (t , fullyEscapedYaml )
628+ componentType , detected = DetectOpenAPIComponentType (node )
613629 assert .Equal (t , "" , componentType )
614630 assert .False (t , detected )
615631
616- // Test that unquoted items key IS treated as schema
632+ // unquoted ` items` key on its own still detects as a schema.
617633 unquotedItemsYaml := `
618634items:
619635 type: string
@@ -623,17 +639,7 @@ items:
623639 assert .Equal (t , v3 .SchemasLabel , componentType )
624640 assert .True (t , detected )
625641
626- // Test that quoted "type" key is NOT treated as schema
627- quotedTypeYaml := `
628- "type": "user"
629- "name": "John"
630- `
631- node = parseYaml (t , quotedTypeYaml )
632- componentType , detected = DetectOpenAPIComponentType (node )
633- assert .Equal (t , "" , componentType )
634- assert .False (t , detected )
635-
636- // Test that unquoted type key IS treated as schema
642+ // plain-style schema is unaffected.
637643 unquotedTypeYaml := `
638644type: object
639645properties:
@@ -645,7 +651,7 @@ properties:
645651 assert .Equal (t , v3 .SchemasLabel , componentType )
646652 assert .True (t , detected )
647653
648- // Test mixed quoted and unquoted keys - should detect schema from unquoted key
654+ // mixed-style: the quoted `items` escape is ignored, classification driven by `type`.
649655 mixedYaml := `
650656type: object
651657"items":
@@ -656,3 +662,89 @@ type: object
656662 assert .Equal (t , v3 .SchemasLabel , componentType )
657663 assert .True (t , detected )
658664}
665+
666+ // TestDetectOpenAPIComponentType_JSONSourced covers https://github.com/pb33f/libopenapi/issues/562:
667+ // SON-parsed mappings arrive with every key marked yaml.DoubleQuotedStyle.
668+ // Uniform quoting on a JSON-sourced mapping is syntactic — not a
669+ // user escape — and must not disable component-type detection.
670+ func TestDetectOpenAPIComponentType_JSONSourced (t * testing.T ) {
671+ cases := []struct {
672+ name string
673+ json string
674+ want string
675+ }{
676+ {
677+ name : "schema with type and properties" ,
678+ json : `{"type": "object", "properties": {"id": {"type": "string"}}}` ,
679+ want : v3 .SchemasLabel ,
680+ },
681+ {
682+ name : "schema with items" ,
683+ json : `{"type": "array", "items": {"type": "string"}}` ,
684+ want : v3 .SchemasLabel ,
685+ },
686+ {
687+ name : "schema with allOf" ,
688+ json : `{"allOf": [{"type": "object"}]}` ,
689+ want : v3 .SchemasLabel ,
690+ },
691+ {
692+ name : "parameter" ,
693+ json : `{"name": "id", "in": "query"}` ,
694+ want : v3 .ParametersLabel ,
695+ },
696+ {
697+ name : "response with content" ,
698+ json : `{"description": "ok", "content": {"application/json": {"schema": {"type": "object"}}}}` ,
699+ want : v3 .ResponsesLabel ,
700+ },
701+ }
702+
703+ for _ , tc := range cases {
704+ t .Run (tc .name , func (t * testing.T ) {
705+ node := parseYaml (t , tc .json )
706+ componentType , detected := DetectOpenAPIComponentType (node )
707+ assert .True (t , detected , "expected detection to succeed for JSON input" )
708+ assert .Equal (t , tc .want , componentType )
709+ })
710+ }
711+ }
712+
713+ // TestGetNodeKeys_UniformQuoting guards the detector's key-filter against JSON. When every
714+ // key in a mapping shares the same quote style, the style carries no intent to escape
715+ // keywords, the filter only kicks in when styles are mixed within one mapping.
716+ func TestGetNodeKeys_UniformQuoting (t * testing.T ) {
717+ cases := []struct {
718+ name string
719+ yaml string
720+ want []string
721+ }{
722+ {
723+ name : "JSON (all double-quoted)" ,
724+ yaml : `{"type": "object", "properties": {"x": {"type": "string"}}}` ,
725+ want : []string {"type" , "properties" },
726+ },
727+ {
728+ name : "YAML all single-quoted" ,
729+ yaml : "'type': object\n 'properties':\n name:\n type: string\n " ,
730+ want : []string {"type" , "properties" },
731+ },
732+ {
733+ name : "mixed quoting drops the quoted keys" ,
734+ yaml : "type: object\n \" properties\" : literal\n " ,
735+ want : []string {"type" },
736+ },
737+ {
738+ name : "plain YAML untouched" ,
739+ yaml : "type: object\n properties:\n x:\n type: string\n " ,
740+ want : []string {"type" , "properties" },
741+ },
742+ }
743+
744+ for _ , tc := range cases {
745+ t .Run (tc .name , func (t * testing.T ) {
746+ node := parseYaml (t , tc .yaml )
747+ assert .ElementsMatch (t , tc .want , getNodeKeys (node ))
748+ })
749+ }
750+ }
0 commit comments