@@ -25,11 +25,11 @@ import OpenAPIKit
2525/// - Throws: An error with diagnostic information if any invalid content types are found.
2626func validateContentTypes( in doc: ParsedOpenAPIRepresentation , validate: ( String ) -> Bool ) throws {
2727 for (path, pathValue) in doc. paths {
28- guard case . b ( let pathItem) = pathValue else { continue }
28+ guard let pathItem = pathValue. pathItemValue else { continue }
2929 for endpoint in pathItem. endpoints {
3030
3131 if let eitherRequest = endpoint. operation. requestBody {
32- if case . b ( let actualRequest) = eitherRequest {
32+ if let actualRequest = eitherRequest. requestValue {
3333 for contentType in actualRequest. content. keys {
3434 if !validate( contentType. rawValue) {
3535 throw Diagnostic . error (
@@ -47,7 +47,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String
4747 }
4848
4949 for eitherResponse in endpoint. operation. responses. values {
50- if case . b ( let actualResponse) = eitherResponse {
50+ if let actualResponse = eitherResponse. responseValue {
5151 for contentType in actualResponse. content. keys {
5252 if !validate( contentType. rawValue) {
5353 throw Diagnostic . error (
@@ -95,13 +95,175 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String
9595 }
9696}
9797
98+ /// Validates all references from an OpenAPI document represented by a ParsedOpenAPIRepresentation against its components.
99+ ///
100+ /// This method traverses the OpenAPI document to ensure that all references
101+ /// within the document are valid and point to existing components.
102+ ///
103+ /// - Parameter doc: The OpenAPI document to validate.
104+ /// - Throws: `Diagnostic.error` if an external reference is found or a reference is not found in components.
105+ func validateReferences( in doc: ParsedOpenAPIRepresentation ) throws {
106+ func validateReference< ReferenceType: ComponentDictionaryLocatable > (
107+ _ reference: OpenAPI . Reference < ReferenceType > ,
108+ in components: OpenAPI . Components ,
109+ location: String
110+ ) throws {
111+ if reference. isExternal {
112+ throw Diagnostic . error (
113+ message: " External references are not suppported. " ,
114+ context: [ " reference " : reference. absoluteString, " location " : location]
115+ )
116+ }
117+ if components [ reference] == nil {
118+ throw Diagnostic . error (
119+ message: " Reference not found in components. " ,
120+ context: [ " reference " : reference. absoluteString, " location " : location]
121+ )
122+ }
123+ }
124+
125+ func validateReferencesInContentTypes( _ content: OpenAPI . Content . Map , location: String ) throws {
126+ for (contentKey, contentType) in content {
127+ if let reference = contentType. schema? . reference {
128+ try validateReference (
129+ reference,
130+ in: doc. components,
131+ location: location + " /content/ \( contentKey. rawValue) /schema "
132+ )
133+ }
134+ if let eitherExamples = contentType. examples? . values {
135+ for example in eitherExamples {
136+ if let reference = example. reference {
137+ try validateReference (
138+ reference,
139+ in: doc. components,
140+ location: location + " /content/ \( contentKey. rawValue) /examples "
141+ )
142+ }
143+ }
144+ }
145+ }
146+ }
147+
148+ for (key, value) in doc. webhooks {
149+ if let reference = value. reference { try validateReference ( reference, in: doc. components, location: key) }
150+ }
151+
152+ for (path, pathValue) in doc. paths {
153+ if let reference = pathValue. reference {
154+ try validateReference ( reference, in: doc. components, location: path. rawValue)
155+ } else if let pathItem = pathValue. pathItemValue {
156+
157+ for endpoint in pathItem. endpoints {
158+ for (endpointKey, endpointValue) in endpoint. operation. callbacks {
159+ if let reference = endpointValue. reference {
160+ try validateReference (
161+ reference,
162+ in: doc. components,
163+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /callbacks/ \( endpointKey) "
164+ )
165+ }
166+ }
167+
168+ for eitherParameter in endpoint. operation. parameters {
169+ if let reference = eitherParameter. reference {
170+ try validateReference (
171+ reference,
172+ in: doc. components,
173+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /parameters "
174+ )
175+ } else if let parameter = eitherParameter. parameterValue {
176+ if let reference = parameter. schemaOrContent. schemaReference {
177+ try validateReference (
178+ reference,
179+ in: doc. components,
180+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /parameters/ \( parameter. name) "
181+ )
182+ } else if let content = parameter. schemaOrContent. contentValue {
183+ try validateReferencesInContentTypes (
184+ content,
185+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /parameters/ \( parameter. name) "
186+ )
187+ }
188+ }
189+ }
190+ if let reference = endpoint. operation. requestBody? . reference {
191+ try validateReference (
192+ reference,
193+ in: doc. components,
194+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /requestBody "
195+ )
196+ } else if let requestBodyValue = endpoint. operation. requestBody? . requestValue {
197+ try validateReferencesInContentTypes (
198+ requestBodyValue. content,
199+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /requestBody "
200+ )
201+ }
202+
203+ for (statusCode, eitherResponse) in endpoint. operation. responses {
204+ if let reference = eitherResponse. reference {
205+ try validateReference (
206+ reference,
207+ in: doc. components,
208+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) "
209+ )
210+ } else if let responseValue = eitherResponse. responseValue {
211+ try validateReferencesInContentTypes (
212+ responseValue. content,
213+ location: " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) "
214+ )
215+ }
216+ if let headers = eitherResponse. responseValue? . headers {
217+ for (headerKey, eitherHeader) in headers {
218+ if let reference = eitherHeader. reference {
219+ try validateReference (
220+ reference,
221+ in: doc. components,
222+ location:
223+ " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) /headers/ \( headerKey) "
224+ )
225+ } else if let headerValue = eitherHeader. headerValue {
226+ if let schemaReference = headerValue. schemaOrContent. schemaReference {
227+ try validateReference (
228+ schemaReference,
229+ in: doc. components,
230+ location:
231+ " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) /headers/ \( headerKey) "
232+ )
233+ } else if let contentValue = headerValue. schemaOrContent. contentValue {
234+ try validateReferencesInContentTypes (
235+ contentValue,
236+ location:
237+ " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) /headers/ \( headerKey) "
238+ )
239+ }
240+ }
241+ }
242+ }
243+ }
244+ }
245+
246+ for eitherParameter in pathItem. parameters {
247+ if let reference = eitherParameter. reference {
248+ try validateReference ( reference, in: doc. components, location: " \( path. rawValue) /parameters " )
249+ }
250+ }
251+ }
252+ }
253+ }
254+
98255/// Runs validation steps on the incoming OpenAPI document.
99256/// - Parameters:
100257/// - doc: The OpenAPI document to validate.
101258/// - config: The generator config.
102259/// - Returns: An array of diagnostic messages representing validation warnings.
103260/// - Throws: An error if a fatal issue is found.
104261func validateDoc( _ doc: ParsedOpenAPIRepresentation , config: Config ) throws -> [ Diagnostic ] {
262+ try validateReferences ( in: doc)
263+ try validateContentTypes ( in: doc) { contentType in
264+ ( try ? _OpenAPIGeneratorCore. ContentType ( string: contentType) ) != nil
265+ }
266+
105267 // Run OpenAPIKit's built-in validation.
106268 // Pass `false` to `strict`, however, because we don't
107269 // want to turn schema loading warnings into errors.
@@ -111,10 +273,6 @@ func validateDoc(_ doc: ParsedOpenAPIRepresentation, config: Config) throws -> [
111273 // block the generator from running.
112274 // Validation errors continue to be fatal, such as
113275 // structural issues, like non-unique operationIds, etc.
114- try validateContentTypes ( in: doc) { contentType in
115- ( try ? _OpenAPIGeneratorCore. ContentType ( string: contentType) ) != nil
116- }
117-
118276 let warnings = try doc. validate ( using: Validator ( ) . validating ( . operationsContainResponses) , strict: false )
119277 let diagnostics : [ Diagnostic ] = warnings. map { warning in
120278 . warning(
0 commit comments