@@ -427,3 +427,308 @@ func TestComponents_MediaTypes(t *testing.T) {
427427 hash2 := n .Hash ()
428428 assert .NotEqual (t , hash1 , hash2 )
429429}
430+
431+ // TestComponents_XPrefixedComponentNames tests that component names starting with x- are correctly
432+ // parsed as components and not incorrectly filtered out as extensions.
433+ // This is a regression test for https://github.com/pb33f/libopenapi/issues/503
434+ func TestComponents_XPrefixedComponentNames (t * testing.T ) {
435+ low .ClearHashCache ()
436+
437+ yml := `schemas:
438+ x-custom-schema:
439+ type: object
440+ description: A schema with x- prefix
441+ RegularSchema:
442+ type: string
443+ parameters:
444+ x-custom-param:
445+ name: x-custom-param
446+ in: header
447+ schema:
448+ type: string
449+ regular-param:
450+ name: regular-param
451+ in: query
452+ schema:
453+ type: string
454+ responses:
455+ x-custom-response:
456+ description: A response with x- prefix
457+ headers:
458+ x-rate-limit:
459+ schema:
460+ type: integer
461+ description: Rate limit header
462+ examples:
463+ x-custom-example:
464+ value: example-value
465+ description: An example with x- prefix
466+ requestBodies:
467+ x-custom-body:
468+ description: A request body with x- prefix
469+ content:
470+ application/json:
471+ schema:
472+ type: object
473+ securitySchemes:
474+ x-custom-auth:
475+ type: apiKey
476+ name: X-API-Key
477+ in: header
478+ description: Custom auth scheme
479+ links:
480+ x-custom-link:
481+ description: A link with x- prefix
482+ callbacks:
483+ x-custom-callback:
484+ '{$request.body#/callbackUrl}':
485+ post:
486+ description: Callback operation
487+ pathItems:
488+ /x-custom-path:
489+ get:
490+ description: A path item with x- prefix
491+ mediaTypes:
492+ x-custom-media:
493+ schema:
494+ type: object
495+ description: Custom media type`
496+
497+ var idxNode yaml.Node
498+ mErr := yaml .Unmarshal ([]byte (yml ), & idxNode )
499+ assert .NoError (t , mErr )
500+ idx := index .NewSpecIndex (& idxNode )
501+
502+ var n Components
503+ err := low .BuildModel (idxNode .Content [0 ], & n )
504+ assert .NoError (t , err )
505+
506+ err = n .Build (context .Background (), idxNode .Content [0 ], idx )
507+ assert .NoError (t , err )
508+
509+ // Test x-prefixed schemas are included
510+ xSchema := n .FindSchema ("x-custom-schema" )
511+ assert .NotNil (t , xSchema , "x-custom-schema should be found" )
512+ assert .Equal (t , "A schema with x- prefix" , xSchema .Value .Schema ().Description .Value )
513+
514+ regularSchema := n .FindSchema ("RegularSchema" )
515+ assert .NotNil (t , regularSchema , "RegularSchema should also be found" )
516+
517+ // Test x-prefixed parameters are included
518+ xParam := n .FindParameter ("x-custom-param" )
519+ assert .NotNil (t , xParam , "x-custom-param should be found" )
520+ assert .Equal (t , "x-custom-param" , xParam .Value .Name .Value )
521+ assert .Equal (t , "header" , xParam .Value .In .Value )
522+
523+ regularParam := n .FindParameter ("regular-param" )
524+ assert .NotNil (t , regularParam , "regular-param should also be found" )
525+
526+ // Test x-prefixed responses are included
527+ xResponse := n .FindResponse ("x-custom-response" )
528+ assert .NotNil (t , xResponse , "x-custom-response should be found" )
529+ assert .Equal (t , "A response with x- prefix" , xResponse .Value .Description .Value )
530+
531+ // Test x-prefixed headers are included
532+ xHeader := n .FindHeader ("x-rate-limit" )
533+ assert .NotNil (t , xHeader , "x-rate-limit should be found" )
534+ assert .Equal (t , "Rate limit header" , xHeader .Value .Description .Value )
535+
536+ // Test x-prefixed examples are included
537+ xExample := n .FindExample ("x-custom-example" )
538+ assert .NotNil (t , xExample , "x-custom-example should be found" )
539+ assert .Equal (t , "An example with x- prefix" , xExample .Value .Description .Value )
540+
541+ // Test x-prefixed request bodies are included
542+ xRequestBody := n .FindRequestBody ("x-custom-body" )
543+ assert .NotNil (t , xRequestBody , "x-custom-body should be found" )
544+ assert .Equal (t , "A request body with x- prefix" , xRequestBody .Value .Description .Value )
545+
546+ // Test x-prefixed security schemes are included
547+ xSecurityScheme := n .FindSecurityScheme ("x-custom-auth" )
548+ assert .NotNil (t , xSecurityScheme , "x-custom-auth should be found" )
549+ assert .Equal (t , "Custom auth scheme" , xSecurityScheme .Value .Description .Value )
550+ assert .Equal (t , "apiKey" , xSecurityScheme .Value .Type .Value )
551+
552+ // Test x-prefixed links are included
553+ xLink := n .FindLink ("x-custom-link" )
554+ assert .NotNil (t , xLink , "x-custom-link should be found" )
555+ assert .Equal (t , "A link with x- prefix" , xLink .Value .Description .Value )
556+
557+ // Test x-prefixed callbacks are included
558+ xCallback := n .FindCallback ("x-custom-callback" )
559+ assert .NotNil (t , xCallback , "x-custom-callback should be found" )
560+ expr := xCallback .Value .FindExpression ("{$request.body#/callbackUrl}" )
561+ assert .NotNil (t , expr , "Callback expression should be found" )
562+ assert .Equal (t , "Callback operation" , expr .Value .Post .Value .Description .Value )
563+
564+ // Test x-prefixed path items are included
565+ xPathItem := n .FindPathItem ("/x-custom-path" )
566+ assert .NotNil (t , xPathItem , "/x-custom-path should be found" )
567+ assert .Equal (t , "A path item with x- prefix" , xPathItem .Value .Get .Value .Description .Value )
568+
569+ // Test x-prefixed media types are included
570+ xMediaType := n .FindMediaType ("x-custom-media" )
571+ assert .NotNil (t , xMediaType , "x-custom-media should be found" )
572+ assert .Equal (t , "Custom media type" , xMediaType .Value .Schema .Value .Schema ().Description .Value )
573+ }
574+
575+ // TestComponents_XPrefixedWithUpperCase tests that both x- (lowercase) and X- (uppercase)
576+ // prefixed component names are correctly parsed.
577+ func TestComponents_XPrefixedWithUpperCase (t * testing.T ) {
578+ low .ClearHashCache ()
579+
580+ yml := `schemas:
581+ x-lowercase-schema:
582+ type: string
583+ description: lowercase x- prefix
584+ X-UPPERCASE-SCHEMA:
585+ type: string
586+ description: uppercase X- prefix
587+ parameters:
588+ x-lowercase-param:
589+ name: x-param
590+ in: header
591+ schema:
592+ type: string
593+ X-UPPERCASE-PARAM:
594+ name: X-param
595+ in: header
596+ schema:
597+ type: string`
598+
599+ var idxNode yaml.Node
600+ mErr := yaml .Unmarshal ([]byte (yml ), & idxNode )
601+ assert .NoError (t , mErr )
602+ idx := index .NewSpecIndex (& idxNode )
603+
604+ var n Components
605+ err := low .BuildModel (idxNode .Content [0 ], & n )
606+ assert .NoError (t , err )
607+
608+ err = n .Build (context .Background (), idxNode .Content [0 ], idx )
609+ assert .NoError (t , err )
610+
611+ // Test lowercase x- prefix
612+ xLowerSchema := n .FindSchema ("x-lowercase-schema" )
613+ assert .NotNil (t , xLowerSchema , "x-lowercase-schema should be found" )
614+ assert .Equal (t , "lowercase x- prefix" , xLowerSchema .Value .Schema ().Description .Value )
615+
616+ // Test uppercase X- prefix
617+ xUpperSchema := n .FindSchema ("X-UPPERCASE-SCHEMA" )
618+ assert .NotNil (t , xUpperSchema , "X-UPPERCASE-SCHEMA should be found" )
619+ assert .Equal (t , "uppercase X- prefix" , xUpperSchema .Value .Schema ().Description .Value )
620+
621+ // Test lowercase x- param
622+ xLowerParam := n .FindParameter ("x-lowercase-param" )
623+ assert .NotNil (t , xLowerParam , "x-lowercase-param should be found" )
624+
625+ // Test uppercase X- param
626+ xUpperParam := n .FindParameter ("X-UPPERCASE-PARAM" )
627+ assert .NotNil (t , xUpperParam , "X-UPPERCASE-PARAM should be found" )
628+ }
629+
630+ // TestComponents_XPrefixedExtensionsStillWork verifies that extensions at the Components level
631+ // (like x-custom-extension) are still captured correctly, while x-prefixed component names
632+ // within schemas/parameters/etc are also captured.
633+ func TestComponents_XPrefixedExtensionsStillWork (t * testing.T ) {
634+ low .ClearHashCache ()
635+
636+ yml := `x-components-extension: this is an extension at components level
637+ x-another-extension:
638+ nested: value
639+ schemas:
640+ x-custom-schema:
641+ type: object
642+ description: This is a schema, not an extension
643+ RegularSchema:
644+ type: string`
645+
646+ var idxNode yaml.Node
647+ mErr := yaml .Unmarshal ([]byte (yml ), & idxNode )
648+ assert .NoError (t , mErr )
649+ idx := index .NewSpecIndex (& idxNode )
650+
651+ var n Components
652+ err := low .BuildModel (idxNode .Content [0 ], & n )
653+ assert .NoError (t , err )
654+
655+ err = n .Build (context .Background (), idxNode .Content [0 ], idx )
656+ assert .NoError (t , err )
657+
658+ // Extensions at Components level should still work
659+ ext1 := n .FindExtension ("x-components-extension" )
660+ assert .NotNil (t , ext1 , "x-components-extension should be found as extension" )
661+ var ext1Val string
662+ _ = ext1 .Value .Decode (& ext1Val )
663+ assert .Equal (t , "this is an extension at components level" , ext1Val )
664+
665+ ext2 := n .FindExtension ("x-another-extension" )
666+ assert .NotNil (t , ext2 , "x-another-extension should be found as extension" )
667+
668+ // x-prefixed schemas should be found as schemas
669+ xSchema := n .FindSchema ("x-custom-schema" )
670+ assert .NotNil (t , xSchema , "x-custom-schema should be found as a schema" )
671+ assert .Equal (t , "This is a schema, not an extension" , xSchema .Value .Schema ().Description .Value )
672+
673+ // Regular schemas also work
674+ regularSchema := n .FindSchema ("RegularSchema" )
675+ assert .NotNil (t , regularSchema , "RegularSchema should be found" )
676+ }
677+
678+ // TestComponents_XPrefixedReferenceResolution tests that references to x-prefixed components
679+ // resolve correctly.
680+ func TestComponents_XPrefixedReferenceResolution (t * testing.T ) {
681+ low .ClearHashCache ()
682+
683+ yml := `schemas:
684+ x-base-schema:
685+ type: object
686+ properties:
687+ id:
688+ type: integer
689+ derived-schema:
690+ allOf:
691+ - $ref: '#/schemas/x-base-schema'
692+ - type: object
693+ properties:
694+ name:
695+ type: string
696+ parameters:
697+ x-auth-header:
698+ name: Authorization
699+ in: header
700+ schema:
701+ type: string
702+ uses-x-param:
703+ $ref: '#/parameters/x-auth-header'`
704+
705+ var idxNode yaml.Node
706+ mErr := yaml .Unmarshal ([]byte (yml ), & idxNode )
707+ assert .NoError (t , mErr )
708+ idx := index .NewSpecIndex (& idxNode )
709+
710+ var n Components
711+ err := low .BuildModel (idxNode .Content [0 ], & n )
712+ assert .NoError (t , err )
713+
714+ err = n .Build (context .Background (), idxNode .Content [0 ], idx )
715+ assert .NoError (t , err )
716+
717+ // The x-prefixed schema should be found
718+ xBaseSchema := n .FindSchema ("x-base-schema" )
719+ assert .NotNil (t , xBaseSchema , "x-base-schema should be found" )
720+
721+ // The derived schema should also be found
722+ derivedSchema := n .FindSchema ("derived-schema" )
723+ assert .NotNil (t , derivedSchema , "derived-schema should be found" )
724+
725+ // The x-prefixed parameter should be found
726+ xAuthHeader := n .FindParameter ("x-auth-header" )
727+ assert .NotNil (t , xAuthHeader , "x-auth-header should be found" )
728+ assert .Equal (t , "Authorization" , xAuthHeader .Value .Name .Value )
729+
730+ // The parameter that references x-auth-header should have reference
731+ usesXParam := n .FindParameter ("uses-x-param" )
732+ assert .NotNil (t , usesXParam , "uses-x-param should be found" )
733+ assert .Equal (t , "#/parameters/x-auth-header" , usesXParam .Value .GetReference ())
734+ }
0 commit comments