1414
1515import OpenAPIKit
1616
17- /// Validates all content types from an OpenAPI document represented by a ParsedOpenAPIRepresentation.
18- ///
19- /// This function iterates through the paths, endpoints, and components of the OpenAPI document,
20- /// checking and reporting any invalid content types using the provided validation closure.
21- ///
22- /// - Parameters:
23- /// - doc: The OpenAPI document representation.
24- /// - validate: A closure to validate each content type.
25- /// - Throws: An error with diagnostic information if any invalid content types are found.
26- func validateContentTypes( in doc: ParsedOpenAPIRepresentation , validate: ( String ) -> Bool ) throws {
27- for (path, pathValue) in doc. paths {
28- guard let pathItem = pathValue. pathItemValue else { continue }
29- for endpoint in pathItem. endpoints {
30-
31- if let eitherRequest = endpoint. operation. requestBody {
32- if let actualRequest = eitherRequest. requestValue {
33- for contentType in actualRequest. content. keys {
34- if !validate( contentType. rawValue) {
35- throw Diagnostic . error (
36- message: " Invalid content type string. " ,
37- context: [
38- " contentType " : contentType. rawValue,
39- " location " : " \( path. rawValue) / \( endpoint. method. rawValue) /requestBody " ,
40- " recoverySuggestion " :
41- " Must have 2 components separated by a slash '<type>/<subtype>'. " ,
42- ]
43- )
44- }
45- }
46- }
47- }
48-
49- for eitherResponse in endpoint. operation. responses. values {
50- if let actualResponse = eitherResponse. responseValue {
51- for contentType in actualResponse. content. keys {
52- if !validate( contentType. rawValue) {
53- throw Diagnostic . error (
54- message: " Invalid content type string. " ,
55- context: [
56- " contentType " : contentType. rawValue,
57- " location " : " \( path. rawValue) / \( endpoint. method. rawValue) /responses " ,
58- " recoverySuggestion " :
59- " Must have 2 components separated by a slash '<type>/<subtype>'. " ,
60- ]
61- )
62- }
63- }
64- }
65- }
66- }
67- }
68-
69- for (key, component) in doc. components. requestBodies {
70- let component = try doc. components. assumeLookupOnce ( component)
71- for contentType in component. content. keys {
72- if !validate( contentType. rawValue) {
73- throw Diagnostic . error (
74- message: " Invalid content type string. " ,
75- context: [
76- " contentType " : contentType. rawValue, " location " : " #/components/requestBodies/ \( key. rawValue) " ,
77- " recoverySuggestion " : " Must have 2 components separated by a slash '<type>/<subtype>'. " ,
78- ]
79- )
80- }
81- }
82- }
83-
84- for (key, component) in doc. components. responses {
85- let component = try doc. components. assumeLookupOnce ( component)
86- for contentType in component. content. keys {
87- if !validate( contentType. rawValue) {
88- throw Diagnostic . error (
89- message: " Invalid content type string. " ,
90- context: [
91- " contentType " : contentType. rawValue, " location " : " #/components/responses/ \( key. rawValue) " ,
92- " recoverySuggestion " : " Must have 2 components separated by a slash '<type>/<subtype>'. " ,
93- ]
94- )
95- }
96- }
97- }
98- }
99-
100- /// Validates all references from an OpenAPI document represented by a ParsedOpenAPIRepresentation against its components.
101- ///
102- /// This method traverses the OpenAPI document to ensure that all references
103- /// within the document are valid and point to existing components.
104- ///
105- /// - Parameter doc: The OpenAPI document to validate.
106- /// - Throws: `Diagnostic.error` if an external reference is found or a reference is not found in components.
107- func validateReferences( in doc: ParsedOpenAPIRepresentation ) throws {
108- func validateReference< ReferenceType: ComponentDictionaryLocatable > (
109- _ reference: OpenAPI . Reference < ReferenceType > ,
110- in components: OpenAPI . Components ,
111- location: String
112- ) throws {
113- if reference. isExternal {
114- throw Diagnostic . error (
115- message: " External references are not suppported. " ,
116- context: [ " reference " : reference. absoluteString, " location " : location]
117- )
118- }
119- if components [ reference] == nil {
120- throw Diagnostic . error (
121- message: " Reference not found in components. " ,
122- context: [ " reference " : reference. absoluteString, " location " : location]
123- )
124- }
125- }
126-
127- func validateReferencesInContentTypes( _ content: OpenAPI . Content . Map , location: String ) throws {
128- for (contentKey, contentType) in content {
129- switch contentType {
130- case . a( let ref) :
131- try validateReference ( ref, in: doc. components, location: location + " /content/ \( contentKey. rawValue) " )
132- case . b( let contentType) :
133- if let reference: JSONReference < JSONSchema > = contentType. schema? . reference {
134- try validateReference (
135- . init( reference) ,
136- in: doc. components,
137- location: location + " /content/ \( contentKey. rawValue) /schema "
138- )
139- }
140- if let eitherExamples = contentType. examples? . values {
141- for example in eitherExamples {
142- if let reference = example. reference {
143- try validateReference (
144- reference,
145- in: doc. components,
146- location: location + " /content/ \( contentKey. rawValue) /examples "
147- )
148- }
149- }
150- }
151- }
152- }
153- }
154-
155- for (key, value) in doc. webhooks {
156- if let reference = value. reference { try validateReference ( reference, in: doc. components, location: key) }
157- }
158-
159- for (path, pathValue) in doc. paths {
160- if let reference = pathValue. reference {
161- try validateReference ( reference, in: doc. components, location: path. rawValue)
162- } else if let pathItem = pathValue. pathItemValue {
163-
164- for endpoint in pathItem. endpoints {
165- for (endpointKey, endpointValue) in endpoint. operation. callbacks {
166- if let reference = endpointValue. reference {
167- try validateReference (
168- reference,
169- in: doc. components,
170- location: " \( path. rawValue) / \( endpoint. method. rawValue) /callbacks/ \( endpointKey) "
171- )
172- }
173- }
174-
175- for eitherParameter in endpoint. operation. parameters {
176- if let reference = eitherParameter. reference {
177- try validateReference (
178- reference,
179- in: doc. components,
180- location: " \( path. rawValue) / \( endpoint. method. rawValue) /parameters "
181- )
182- } else if let parameter = eitherParameter. parameterValue {
183- if let reference = parameter. schemaOrContent. schemaReference {
184- try validateReference (
185- reference,
186- in: doc. components,
187- location: " \( path. rawValue) / \( endpoint. method. rawValue) /parameters/ \( parameter. name) "
188- )
189- } else if let content = parameter. schemaOrContent. contentValue {
190- try validateReferencesInContentTypes (
191- content,
192- location: " \( path. rawValue) / \( endpoint. method. rawValue) /parameters/ \( parameter. name) "
193- )
194- }
195- }
196- }
197- if let reference = endpoint. operation. requestBody? . reference {
198- try validateReference (
199- reference,
200- in: doc. components,
201- location: " \( path. rawValue) / \( endpoint. method. rawValue) /requestBody "
202- )
203- } else if let requestBodyValue = endpoint. operation. requestBody? . requestValue {
204- try validateReferencesInContentTypes (
205- requestBodyValue. content,
206- location: " \( path. rawValue) / \( endpoint. method. rawValue) /requestBody "
207- )
208- }
209-
210- for (statusCode, eitherResponse) in endpoint. operation. responses {
211- if let reference = eitherResponse. reference {
212- try validateReference (
213- reference,
214- in: doc. components,
215- location: " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) "
216- )
217- } else if let responseValue = eitherResponse. responseValue {
218- try validateReferencesInContentTypes (
219- responseValue. content,
220- location: " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) "
221- )
222- }
223- if let headers = eitherResponse. responseValue? . headers {
224- for (headerKey, eitherHeader) in headers {
225- if let reference = eitherHeader. reference {
226- try validateReference (
227- reference,
228- in: doc. components,
229- location:
230- " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) /headers/ \( headerKey) "
231- )
232- } else if let headerValue = eitherHeader. headerValue {
233- if let schemaReference = headerValue. schemaOrContent. schemaReference {
234- try validateReference (
235- schemaReference,
236- in: doc. components,
237- location:
238- " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) /headers/ \( headerKey) "
239- )
240- } else if let contentValue = headerValue. schemaOrContent. contentValue {
241- try validateReferencesInContentTypes (
242- contentValue,
243- location:
244- " \( path. rawValue) / \( endpoint. method. rawValue) /responses/ \( statusCode. rawValue) /headers/ \( headerKey) "
245- )
246- }
247- }
248- }
249- }
250- }
251- }
252-
253- for eitherParameter in pathItem. parameters {
254- if let reference = eitherParameter. reference {
255- try validateReference ( reference, in: doc. components, location: " \( path. rawValue) /parameters " )
256- }
257- }
258- }
259- }
17+ /// Validates all content types from an OpenAPI document can be parsed as a ContentType.
18+ var contentTypesValidation : Validation < OpenAPI . ContentType > {
19+ . init(
20+ description: " Content type is of form '<type>/<subtype>'. " ,
21+ check: { context in ( try ? _OpenAPIGeneratorCore. ContentType ( string: context. subject. rawValue) ) != nil }
22+ )
26023}
26124
26225/// Validates all type overrides from a Config are present in the components of a ParsedOpenAPIRepresentation.
@@ -288,13 +51,13 @@ func validateTypeOverrides(_ doc: ParsedOpenAPIRepresentation, config: Config) -
28851/// - Returns: An array of diagnostic messages representing validation warnings.
28952/// - Throws: An error if a fatal issue is found.
29053func validateDoc( _ doc: ParsedOpenAPIRepresentation , config: Config ) throws -> [ Diagnostic ] {
291- try validateReferences ( in: doc)
292- try validateContentTypes ( in: doc) { contentType in
293- ( try ? _OpenAPIGeneratorCore. ContentType ( string: contentType) ) != nil
294- }
29554 let typeOverrideDiagnostics = validateTypeOverrides ( doc, config: config)
29655
297- // Run OpenAPIKit's built-in validation.
56+ // Run OpenAPIKit's default built-in validations and additionally check
57+ // that all references point to entries in the Components Object, all
58+ // operations contain responses, and all content types parse by this
59+ // library's code.
60+ //
29861 // Pass `false` to `strict`, however, because we don't
29962 // want to turn schema loading warnings into errors.
30063 // We already propagate the warnings to the generator's
@@ -318,7 +81,8 @@ func validateDoc(_ doc: ParsedOpenAPIRepresentation, config: Config) throws -> [
31881
31982extension OpenAPIKit . Validator {
32083 static var swiftOpenAPICustomValidator : Validator {
321- Validator ( ) . validating ( \. operationsContainResponses)
84+ Validator ( ) . validatingAllReferencesFoundInComponents ( ) . validating ( \. operationsContainResponses)
85+ . validating ( contentTypesValidation)
32286 // Skip this one to be backwards compatible with previous versions of Swift OpenAPI Generator.
32387 // Even when run with strict=false, this one will cause OpenAPIKit to throw an error. Previous verions were more
32488 // lenient and Swift OpenAPI Generator would later emit a warning that it's unsupported.
0 commit comments