66//
77
88import  OpenAPIKitCore
9+ import  Foundation
910
1011extension  OpenAPI  { 
1112    /// The root of an OpenAPI 3.1 document.
@@ -45,14 +46,17 @@ extension OpenAPI {
4546    ///
4647    /// See the documentation on `DereferencedDocument.resolved()` for more.
4748    ///
48-     public  struct  Document :  HasWarnings ,  CodableVendorExtendable ,  Sendable  { 
49+     public  struct  Document :  HasConditionalWarnings ,   HasWarnings ,  CodableVendorExtendable ,  Sendable  { 
4950        /// OpenAPI Spec "openapi" field.
5051        ///
5152        /// OpenAPIKit only explicitly supports versions that can be found in
5253        /// the `Version` enum. Other versions may or may not be decodable
5354        /// by OpenAPIKit to a certain extent.
5455        public  var  openAPIVersion :  Version 
5556
57+         /// OpenAPI Spec "$self" field.
58+         public  var  selfURI :  URL ? 
59+ 
5660        /// Information about the API described by this OpenAPI Document.
5761        ///
5862        /// Licensing, Terms of Service, contact information, API version (the
@@ -142,9 +146,11 @@ extension OpenAPI {
142146        public  var  vendorExtensions :  [ String :  AnyCodable ] 
143147
144148        public  let  warnings :  [ Warning ] 
149+         public  let  conditionalWarnings :  [ ( any  Condition ,  OpenAPI . Warning ) ] 
145150
146151        public  init ( 
147152            openAPIVersion:  Version  =  . v3_1_1, 
153+             selfURI:  URL ? =  nil , 
148154            info:  Info , 
149155            servers:  [ Server ] , 
150156            paths:  PathItem . Map , 
@@ -156,6 +162,7 @@ extension OpenAPI {
156162            vendorExtensions:  [ String :  AnyCodable ]  =  [ : ] 
157163        )  { 
158164            self . openAPIVersion =  openAPIVersion
165+             self . selfURI =  selfURI
159166            self . info =  info
160167            self . servers =  servers
161168            self . paths =  paths
@@ -167,13 +174,28 @@ extension OpenAPI {
167174            self . vendorExtensions =  vendorExtensions
168175
169176            self . warnings =  [ ] 
177+ 
178+             self . conditionalWarnings =  [ 
179+                 // If $self is non-nil, the document must be OAS version 3.2.0 or greater
180+                 nonNilVersionWarning ( fieldName:  " $self " ,  value:  selfURI,  minimumVersion:  . v3_2_0) , 
181+             ] . compactMap  {  $0 } 
170182        } 
171183    } 
172184} 
173185
186+ fileprivate  func  nonNilVersionWarning< Subject> ( fieldName:  String ,  value:  Subject ? ,  minimumVersion:  OpenAPI . Document . Version )  ->  ( any  Condition ,  OpenAPI . Warning ) ? { 
187+     value. map  {  _ in 
188+         OpenAPI . Document. ConditionalWarnings. version ( 
189+             lessThan:  minimumVersion, 
190+             doesNotSupport:  " The Document  \( fieldName)  field " 
191+         ) 
192+     } 
193+ } 
194+ 
174195extension  OpenAPI . Document :  Equatable  { 
175196    public  static  func  ==  ( lhs:  Self ,  rhs:  Self )  ->  Bool  { 
176197        lhs. openAPIVersion ==  rhs. openAPIVersion
198+         && lhs. selfURI ==  rhs. selfURI
177199        && lhs. info ==  rhs. info
178200        && lhs. servers ==  rhs. servers
179201        && lhs. paths ==  rhs. paths
@@ -602,6 +624,9 @@ extension OpenAPI.Document: Encodable {
602624        var  container  =  encoder. container ( keyedBy:  CodingKeys . self) 
603625
604626        try . encode ( openAPIVersion,  forKey:  . openAPIVersion) 
627+ 
628+         try . encodeIfPresent ( selfURI? . absoluteString,  forKey:  . selfURI) 
629+ 
605630        try . encode ( info,  forKey:  . info) 
606631
607632        try . encodeIfPresent ( externalDocs,  forKey:  . externalDocs) 
@@ -661,6 +686,11 @@ extension OpenAPI.Document: Decodable {
661686                ) 
662687            } 
663688
689+             let  selfURIString :  String ? =  try . decodeIfPresent ( String . self,  forKey:  . selfURI) 
690+             selfURI =  try . map  {  
691+               try decodeURIString ( $0,  forKey:  CodingKeys . selfURI,  atPath:  decoder. codingPath) 
692+             } 
693+ 
664694            info =  try . decode ( OpenAPI . Document. Info. self,  forKey:  . info) 
665695            servers =  try . decodeIfPresent ( [ OpenAPI . Server ] . self,  forKey:  . servers)  ??  [ ] 
666696
@@ -681,6 +711,11 @@ extension OpenAPI.Document: Decodable {
681711
682712            self . warnings =  warnings
683713
714+             self . conditionalWarnings =  [ 
715+                 // If $self is non-nil, the document must be OAS version 3.2.0 or greater
716+                 nonNilVersionWarning ( fieldName:  " $self " ,  value:  selfURI,  minimumVersion:  . v3_2_0) , 
717+             ] . compactMap  {  $0 } 
718+ 
684719        }  catch  let  errorOpenAPI . Error . Decoding . Path  { 
685720
686721            throw  OpenAPI . Error. Decoding. Document ( error) 
@@ -697,9 +732,34 @@ extension OpenAPI.Document: Decodable {
697732    } 
698733} 
699734
735+ fileprivate  func  decodeURIString( _ str:  String ,  forKey key:  CodingKey ,  atPath path:  [ CodingKey ] )  throws  ->  URL  { 
736+     let  uri :  URL ? 
737+     #if canImport(FoundationEssentials) 
738+     uri =  URL ( string:  str,  encodingInvalidCharacters:  false ) 
739+     #elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 
740+     if  #available( macOS 14 . 0 ,  iOS 17 . 0 ,  watchOS 10 . 0 ,  tvOS 17 . 0 ,  * )  { 
741+         uri =  URL ( string:  str,  encodingInvalidCharacters:  false ) 
742+     }  else  { 
743+         uri =  URL ( string:  str) 
744+     } 
745+     #else 
746+     uri =  URL ( string:  str) 
747+     #endif 
748+     guard  let  uri else  { 
749+         throw  GenericError ( 
750+             subjectName:  key. stringValue, 
751+             details:  " Failed to parse a valid URI from ' \( str) ' " , 
752+             codingPath:  path
753+         ) 
754+     } 
755+ 
756+     return  uri
757+ } 
758+ 
700759extension  OpenAPI . Document  { 
701760    internal  enum  CodingKeys :  ExtendableCodingKey  { 
702761        case  openAPIVersion
762+         case  selfURI
703763        case  info
704764        case  jsonSchemaDialect // TODO: implement parsing (https://github.com/mattpolzin/OpenAPIKit/issues/202)
705765        case  servers
@@ -714,6 +774,7 @@ extension OpenAPI.Document {
714774        static  var  allBuiltinKeys :  [ CodingKeys ]  { 
715775            return  [ 
716776                . openAPIVersion, 
777+                 . selfURI, 
717778                . info, 
718779                . jsonSchemaDialect, 
719780                . servers, 
@@ -734,6 +795,8 @@ extension OpenAPI.Document {
734795            switch  stringValue { 
735796            case  " openapi " : 
736797                self  =  . openAPIVersion
798+             case  " $self " : 
799+                 self  =  . selfURI
737800            case  " info " : 
738801                self  =  . info
739802            case  " jsonSchemaDialect " : 
@@ -761,6 +824,8 @@ extension OpenAPI.Document {
761824            switch  self  { 
762825            case  . openAPIVersion: 
763826                return  " openapi " 
827+             case  . selfURI: 
828+                 return  " $self " 
764829            case  . info: 
765830                return  " info " 
766831            case  . jsonSchemaDialect: 
0 commit comments