diff --git a/Package.swift b/Package.swift index f2705fef1..4ce2c2f96 100644 --- a/Package.swift +++ b/Package.swift @@ -45,8 +45,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.1.4"), // Read OpenAPI documents - .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "3.9.0"), - .package(url: "https://github.com/jpsim/Yams", "4.0.0"..<"7.0.0"), + .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "5.1.1"), + .package(url: "https://github.com/jpsim/Yams", "5.1.0"..<"7.0.0"), // CLI Tool .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), diff --git a/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift b/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift index ea055e6bb..76879870e 100644 --- a/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift +++ b/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift @@ -22,12 +22,36 @@ extension Either { /// - Throws: An error if there's an issue looking up the value in the components. func resolve(in components: OpenAPI.Components) throws -> B where A == OpenAPI.Reference { switch self { - case let .a(a): return try components.lookup(a) + case let .a(a): return try components.assumeLookupOnce(a) case let .b(b): return b } } } +extension OpenAPI.Components { + func assumeLookupOnce(_ reference: OpenAPI.Reference) throws -> ReferenceType{ + guard let result = try lookupOnce(reference).b else { + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) + } + return result + } + + func assumeLookupOnce(_ reference: JSONReference) throws -> ReferenceType{ + guard let result = try lookupOnce(reference).b else { + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) + } + return result + } + + func assumeLookupOnce(_ maybeReference: Either, ReferenceType>) throws -> ReferenceType{ + guard let result = try lookupOnce(maybeReference).b else { + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(maybeReference.a?.absoluteString) + } + return result + } + +} + extension JSONSchema.Schema { /// Returns the name of the schema. diff --git a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift index 613aa26da..a4ab3ca95 100644 --- a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift +++ b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift @@ -121,7 +121,7 @@ struct FilteredDocumentBuilder { guard let methods = requiredEndpoints[path] else { continue } switch pathItem { case .a(let reference): - components.pathItems[try reference.internalComponentKey] = try document.components.lookup(reference) + components.pathItems[try reference.internalComponentKey] = try document.components.assumeLookupOnce(reference) .filteringEndpoints { methods.contains($0.method) } case .b(let pathItem): filteredDocument.paths[path] = .b(pathItem.filteringEndpoints { methods.contains($0.method) }) @@ -187,7 +187,7 @@ struct FilteredDocumentBuilder { /// - Parameter name: The key in the `#/components/schemas` map in the OpenAPI document. /// - Throws: If the named schema does not exist in original OpenAPI document. mutating func includeSchema(_ name: String) throws { - try includeSchema(.a(OpenAPI.Reference.component(named: name))) + try includeComponentsReferencedBy(.reference(.component(named: name))) } } @@ -216,16 +216,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredPathItemReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) - case .b(let value): try includeComponentsReferencedBy(value) - } - } - - mutating func includeSchema(_ maybeReference: Either, JSONSchema>) throws { - switch maybeReference { - case .a(let reference): - guard requiredSchemaReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -236,7 +227,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredParameterReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -247,7 +238,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredResponseReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -256,7 +247,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredHeaderReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -265,7 +256,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredLinkReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -276,7 +267,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredCallbacksReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -287,7 +278,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredRequestReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -296,7 +287,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard requiredExampleReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -305,7 +296,7 @@ private extension FilteredDocumentBuilder { for (path, maybePathItemReference) in document.paths { let originalPathItem: OpenAPI.PathItem switch maybePathItemReference { - case .a(let reference): originalPathItem = try document.components.lookup(reference) + case .a(let reference): originalPathItem = try document.components.assumeLookupOnce(reference) case .b(let pathItem): originalPathItem = pathItem } @@ -347,7 +338,7 @@ private extension FilteredDocumentBuilder { case .reference(let reference, _): guard requiredSchemaReferences.insert(OpenAPI.Reference(reference)).inserted else { return } - try includeComponentsReferencedBy(document.components.lookup(reference)) + try includeComponentsReferencedBy(document.components.lookupOnce(reference).flattenToJsonSchema()) case .object(_, let object): for schema in object.properties.values { try includeComponentsReferencedBy(schema) } @@ -379,20 +370,24 @@ private extension FilteredDocumentBuilder { switch schemaContext.schema { case .a(let reference): guard requiredSchemaReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let schema): try includeComponentsReferencedBy(schema) } case .b(let contentMap): - for value in contentMap.values { - switch value.schema { - case .a(let reference): - guard requiredSchemaReferences.insert(reference).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) - case .b(let schema): try includeComponentsReferencedBy(schema) - case .none: continue - } - } + for value in contentMap.values { try includeComponentsReferencedBy(value) } + } + } + + mutating func includeComponentsReferencedBy( + _ contentMapEntry: Either, OpenAPI.Content> + ) throws { + let content: OpenAPI.Content + switch contentMapEntry { + case .a(let ref): content = try document.components.assumeLookupOnce(ref) + case .b(let value): content = value } + guard let schema = content.schema else { return } + try includeComponentsReferencedBy(schema) } mutating func includeComponentsReferencedBy(_ response: OpenAPI.Response) throws { @@ -402,8 +397,8 @@ private extension FilteredDocumentBuilder { } mutating func includeComponentsReferencedBy(_ content: OpenAPI.Content) throws { - if let schema = content.schema { try includeSchema(schema) } - if let encoding = content.encoding { + if let schema = content.schema { try includeComponentsReferencedBy(schema) } + if let encoding = content.encodingMap { for encoding in encoding.values { if let headers = encoding.headers { for header in headers.values { try includeHeader(header) } } } diff --git a/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift b/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift index ac646b56b..909507b8d 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift @@ -54,13 +54,6 @@ public struct YamsParser: ParserProtocol { let decoder = YAMLDecoder() let openapiData = input.contents - let decodingOptions = [ - DocumentConfiguration.versionMapKey: [ - // Until we move to OpenAPIKit v5.0+ we will parse OAS 3.2.0 as if it were OAS 3.1.2 - "3.2.0": OpenAPI.Document.Version.v3_1_2 - ] - ] - struct OpenAPIVersionedDocument: Decodable { var openapi: String? } let versionedDocument: OpenAPIVersionedDocument @@ -83,11 +76,7 @@ public struct YamsParser: ParserProtocol { case "3.1.0", "3.1.1", "3.1.2": document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) case "3.2.0": - document = try decoder.decode( - OpenAPIKit.OpenAPI.Document.self, - from: input.contents, - userInfo: decodingOptions - ) + document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) default: throw Diagnostic.openAPIVersionError( versionString: "openapi: \(openAPIVersion)", diff --git a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift index 8b83a9505..4698b772f 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift @@ -67,6 +67,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String } for (key, component) in doc.components.requestBodies { + let component = try doc.components.assumeLookupOnce(component) for contentType in component.content.keys { if !validate(contentType.rawValue) { throw Diagnostic.error( @@ -81,6 +82,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String } for (key, component) in doc.components.responses { + let component = try doc.components.assumeLookupOnce(component) for contentType in component.content.keys { if !validate(contentType.rawValue) { throw Diagnostic.error( @@ -124,21 +126,30 @@ func validateReferences(in doc: ParsedOpenAPIRepresentation) throws { func validateReferencesInContentTypes(_ content: OpenAPI.Content.Map, location: String) throws { for (contentKey, contentType) in content { - if let reference = contentType.schema?.reference { + switch contentType { + case .a(let ref): try validateReference( - reference, + ref, in: doc.components, - location: location + "/content/\(contentKey.rawValue)/schema" + location: location + "/content/\(contentKey.rawValue)" ) - } - if let eitherExamples = contentType.examples?.values { - for example in eitherExamples { - if let reference = example.reference { - try validateReference( - reference, - in: doc.components, - location: location + "/content/\(contentKey.rawValue)/examples" - ) + case .b(let contentType): + if let reference: JSONReference = contentType.schema?.reference { + try validateReference( + .init(reference), + in: doc.components, + location: location + "/content/\(contentKey.rawValue)/schema" + ) + } + if let eitherExamples = contentType.examples?.values { + for example in eitherExamples { + if let reference = example.reference { + try validateReference( + reference, + in: doc.components, + location: location + "/content/\(contentKey.rawValue)/examples" + ) + } } } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift index 737abe571..e1b984ebc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift @@ -100,7 +100,9 @@ extension FileTranslator { // In nullable enum schemas, empty strings are parsed as Void. // This is unlikely to be fixed, so handling that case here. // https://github.com/apple/swift-openapi-generator/issues/118 - if isNullable && anyValue is Void { + // Also handle nil values in nullable schemas. + let isNullValue = anyValue is Void || (anyValue as? String) == nil + if isNullable && isNullValue { try addIfUnique(id: .string(""), caseName: context.safeNameGenerator.swiftMemberName(for: "")) } else { guard let rawValue = anyValue as? String else { diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift index cc4cf8fc2..5e4a47a51 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift @@ -97,7 +97,7 @@ extension FileTranslator { return try contents.compactMap { key, value in try parseContentIfSupported( contentKey: key, - contentValue: value, + contentValue: try components.assumeLookupOnce(value), excludeBinary: excludeBinary, isRequired: isRequired, foundIn: foundIn + "/\(key.rawValue)" @@ -129,12 +129,14 @@ extension FileTranslator { let chosenContent: (type: ContentType, schema: SchemaContent, content: OpenAPI.Content)? if let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isJSON }) { - chosenContent = (contentType, .init(contentType: contentType, schema: contentValue.schema), contentValue) + let contentValue = try components.assumeLookupOnce(contentValue) + chosenContent = (contentType, .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)), contentValue) } else if !excludeBinary, let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isBinary }) { + let contentValue = try components.assumeLookupOnce(contentValue) chosenContent = ( - contentType, .init(contentType: contentType, schema: .b(.string(contentEncoding: .binary))), + contentType, .init(contentType: contentType, schema: .schema(.string(contentEncoding: .binary))), contentValue ) } else { @@ -188,8 +190,8 @@ extension FileTranslator { foundIn: "\(foundIn), content \(contentType.originallyCasedTypeAndSubtype)" ) } - if contentType.isJSON { return .init(contentType: contentType, schema: contentValue.schema) } - if contentType.isUrlEncodedForm { return .init(contentType: contentType, schema: contentValue.schema) } + if contentType.isJSON { return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) } + if contentType.isUrlEncodedForm { return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) } if contentType.isMultipart { guard isRequired else { try diagnostics.emit( @@ -201,10 +203,10 @@ extension FileTranslator { ) return nil } - return .init(contentType: contentType, schema: contentValue.schema, encoding: contentValue.encoding) + return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema), encoding: contentValue.encodingMap) } if !excludeBinary, contentType.isBinary { - return .init(contentType: contentType, schema: .b(.string(contentEncoding: .binary))) + return .init(contentType: contentType, schema: .schema(.string(contentEncoding: .binary))) } try diagnostics.emitUnsupported("Unsupported content", foundIn: foundIn) return nil diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index 19dcf61f3..b669d8a77 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift @@ -297,14 +297,14 @@ extension FileTranslator { case .string(_, let context): candidateSource = try inferStringContent(context) case .object, .all, .one, .any, .fragment, .array: candidateSource = .infer(.complex) case .reference(let ref, _): - guard let source = try inferSchema(components.lookup(ref))?.1 else { return nil } + guard let source = try inferSchema(components.assumeLookupOnce(ref))?.1 else { return nil } candidateSource = source } } else { candidateSource = .infer(.complex) } case .reference(let ref, _): - guard let (refRepetitionKind, refCandidateSource) = try inferSchema(components.lookup(ref)) else { + guard let (refRepetitionKind, refCandidateSource) = try inferSchema(components.assumeLookupOnce(ref)) else { return nil } repetitionKind = refRepetitionKind @@ -365,7 +365,12 @@ extension FileTranslator { func visitContentMap(_ contentMap: OpenAPI.Content.Map) throws { for (key, value) in contentMap { guard try key.asGeneratorContentType.isMultipart else { continue } - guard let schema = value.schema, case let .a(ref) = schema, let name = ref.name, + let content: OpenAPI.Content + switch value { + case .a(let ref): content = try components.assumeLookupOnce(ref) + case .b(let value): content = value + } + guard let ref = content.schema?.reference, let name = ref.name, let componentKey = OpenAPI.ComponentKey(rawValue: name) else { continue } refs.insert(componentKey) @@ -377,7 +382,7 @@ extension FileTranslator { if let requestBodyEither = operation.requestBody { let requestBody: OpenAPI.Request switch requestBodyEither { - case .a(let ref): requestBody = try components.lookup(ref) + case .a(let ref): requestBody = try components.assumeLookupOnce(ref) case .b(let value): requestBody = value } try visitContentMap(requestBody.content) @@ -385,7 +390,7 @@ extension FileTranslator { for responseOutcome in operation.responseOutcomes { let response: OpenAPI.Response switch responseOutcome.response { - case .a(let ref): response = try components.lookup(ref) + case .a(let ref): response = try components.assumeLookupOnce(ref) case .b(let value): response = value } try visitContentMap(response.content) @@ -395,7 +400,7 @@ extension FileTranslator { for (_, value) in paths { let pathItem: OpenAPI.PathItem switch value { - case .a(let ref): pathItem = try components.lookup(ref) + case .a(let ref): pathItem = try components.assumeLookupOnce(ref) case .b(let value): pathItem = value } try visitPath(pathItem) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index 7ac1f18fc..e8f846e9e 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -99,7 +99,7 @@ extension FileTranslator { // Collect the parameter let parameter: OpenAPI.Parameter switch unresolvedParameter { - case let .a(ref): parameter = try components.lookup(ref) + case let .a(ref): parameter = try components.assumeLookupOnce(ref) case let .b(_parameter): parameter = _parameter } @@ -151,6 +151,9 @@ extension FileTranslator { case .cookie: try diagnostics.emitUnsupported("Cookie params", foundIn: foundIn) return nil + case .querystring: + try diagnostics.emitUnsupported("QueryString params", foundIn: foundIn) + return nil } case let .b(contentMap): @@ -165,13 +168,12 @@ extension FileTranslator { codingStrategy = typedContent.content.contentType.codingStrategy // Defaults are defined by the OpenAPI specification: - // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-10 - switch parameter.location { - case .query, .cookie: - style = .form + // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#fixed-fields-10 + style = OpenAPI.Parameter.SchemaContext.Style.default(for: parameter.location) + switch style { + case .form: explode = true - case .path, .header: - style = .simple + default: explode = false } } @@ -221,6 +223,7 @@ extension OpenAPI.Parameter.Context.Location { case .header: return "Headers" case .query: return "Query" case .cookie: return "Cookies" + case .querystring: return "QueryString" } } @@ -236,6 +239,7 @@ extension OpenAPI.Parameter.Context.Location { case .header: return "headers" case .query: return "query" case .cookie: return "cookies" + case .querystring: return "querystring" } } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift index 2279bd1a2..707534585 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift @@ -73,7 +73,7 @@ extension FileTranslator { let isInlined: Bool switch unresolvedRequest { case .a(let reference): - request = try components.lookup(reference) + request = try components.assumeLookupOnce(reference) isInlined = false case .b(let _request): request = _request diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift index ee4e371f0..78f9b7318 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift @@ -45,7 +45,7 @@ extension FileTranslator { switch unresolvedResponse { case .a(let reference): typeName = try typeAssigner.typeName(for: reference) - response = try components.lookup(reference) + response = try components.assumeLookupOnce(reference) isInlined = false case .b(let _response): let responseKind = outcome.status.value.asKind diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index f8b0e866b..78e127a54 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift @@ -105,7 +105,7 @@ extension FileTranslator { // Collect the header let header: OpenAPI.Header switch unresolvedResponseHeader { - case let .a(ref): header = try components.lookup(ref) + case let .a(ref): header = try components.assumeLookupOnce(ref) case let .b(_header): header = _header } @@ -132,12 +132,16 @@ extension FileTranslator { switch unresolvedResponseHeader { case let .a(ref): type = try typeAssigner.typeName(for: ref).asUsage case .b: - switch schema { - case let .a(reference): type = try typeAssigner.typeName(for: reference).asUsage - case let .b(schema): + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case let .reference(reference, _): type = try typeAssigner.typeName(for: reference).asUsage + default: type = try typeAssigner.typeUsage( forParameterNamed: name, - withSchema: schema, + withSchema: unboxedSchema, components: components, inParent: parent ) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 1555731d3..c011a0bc4 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -91,12 +91,16 @@ struct TypeAssigner { ) throws -> TypeUsage? { let associatedType: TypeUsage? if let schema { - switch schema { - case let .a(reference): associatedType = try typeName(for: reference).asUsage - case let .b(schema): + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case let .reference(reference, _): associatedType = try typeName(for: reference).asUsage + default: associatedType = try _typeUsage( forPotentiallyInlinedValueNamed: hint, - withSchema: schema, + withSchema: unboxedSchema, components: components, inParent: parent, subtype: .appendScope @@ -534,6 +538,10 @@ enum JSONReferenceParsingError: Swift.Error { /// An error thrown when parsing a JSON reference that points to /// other OpenAPI documents. case externalPathsUnsupported(String) + + // An error thrown when following a reference leads to a component entry + // that is itself a reference. + case componentsReferenceEntryUnsupported(String?) } extension JSONReferenceParsingError: CustomStringConvertible { @@ -543,6 +551,8 @@ extension JSONReferenceParsingError: CustomStringConvertible { return "JSON references outside of #/components are not supported, found: \(string ?? "")" case let .externalPathsUnsupported(string): return "External JSON references are not supported, found: \(string)" + case let .componentsReferenceEntryUnsupported(string): + return "#/components entries that are themselves references are not supported, found: \(string ?? "")" } } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index 1c503ae74..2bc65ee23 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -156,8 +156,18 @@ struct TypeMatcher { ) -> OpenAPI.Reference? { // If the schema is a ref AND no encoding is provided, we can reference the type. // Otherwise, we must inline. - guard case .a(let ref) = schema, encoding == nil || encoding!.isEmpty else { return nil } - return ref + guard let schema, encoding == nil || encoding!.isEmpty else { return nil } + + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case let .reference(ref, _): + return .init(ref) + default: + return nil + } } /// Returns a Boolean value that indicates whether the schema @@ -199,7 +209,7 @@ struct TypeMatcher { // only key-value pair schemas can be valid recursive types. return true } - let targetSchema = try components.lookup(ref) + let targetSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isKeyValuePair(targetSchema, referenceStack: &referenceStack, components: components) @@ -231,7 +241,7 @@ struct TypeMatcher { } let schemaToCheck: JSONSchema switch schema { - case .a(let ref): schemaToCheck = try components.lookup(ref) + case .a(let ref): schemaToCheck = try components.assumeLookupOnce(ref) case let .b(schema): schemaToCheck = schema } return try isKeyValuePair(schemaToCheck, referenceStack: &referenceStack, components: components) @@ -246,7 +256,7 @@ struct TypeMatcher { func isOptional(_ schema: JSONSchema, components: OpenAPI.Components) throws -> Bool { if schema.nullable || !schema.required { return true } guard case .reference(let ref, _) = schema.value else { return false } - let targetSchema = try components.lookup(ref) + let targetSchema = try components.assumeLookupOnce(ref) return try isOptional(targetSchema, components: components) } @@ -263,7 +273,7 @@ struct TypeMatcher { } switch schema { case .a(let ref): - let targetSchema = try components.lookup(ref) + let targetSchema = try components.assumeLookupOnce(ref) return try isOptional(targetSchema, components: components) case .b(let schema): return try isOptional(schema, components: components) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift index 49fb4466f..a0ce8549a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift @@ -145,7 +145,7 @@ extension FileTranslator { return .supported } // reference is supported iff the existing type is supported - let existingSchema = try components.lookup(ref) + let existingSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isSchemaSupported(existingSchema, referenceStack: &referenceStack) @@ -192,11 +192,16 @@ extension FileTranslator { // fragment type is supported return .supported } - switch schema { - case .a: + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case .reference: // references are supported return .supported - case let .b(schema): return try isSchemaSupported(schema, referenceStack: &referenceStack) + default: + return try isSchemaSupported(unboxedSchema, referenceStack: &referenceStack) } } @@ -315,7 +320,7 @@ extension FileTranslator { return .supported } // reference is supported iff the existing type is supported - let referencedSchema = try components.lookup(ref) + let referencedSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isObjectishSchemaAndSupported(referencedSchema, referenceStack: &referenceStack) @@ -365,7 +370,7 @@ extension FileTranslator { return .supported } // reference is supported iff the existing type is supported - let referencedSchema = try components.lookup(ref) + let referencedSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isObjectOrRefToObjectSchemaAndSupported(referencedSchema, referenceStack: &referenceStack) @@ -391,7 +396,7 @@ extension FileTranslator { return nil } // reference is supported iff the existing type is supported - let referencedSchema = try components.lookup(ref) + let referencedSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try flattenedTopLevelMultipartObject(referencedSchema, referenceStack: &referenceStack) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift index 54a1b6b9f..fff0fe522 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift @@ -29,10 +29,14 @@ extension TypesFileTranslator { { let schemas = try translateSchemas(components.schemas, multipartSchemaNames: multipartSchemaNames) - let parameters = try translateComponentParameters(components.parameters) - let requestBodies = try translateComponentRequestBodies(components.requestBodies) - let responses = try translateComponentResponses(components.responses) - let headers = try translateComponentHeaders(components.headers) + let resolvedParameters = try components.parameters.mapValues { try components.assumeLookupOnce($0) } + let parameters = try translateComponentParameters(resolvedParameters) + let resolvedRequestBodies = try components.requestBodies.mapValues { try components.assumeLookupOnce($0) } + let requestBodies = try translateComponentRequestBodies(resolvedRequestBodies) + let resolvedResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } + let responses = try translateComponentResponses(resolvedResponses) + let resolvedHeaders = try components.headers.mapValues { try components.assumeLookupOnce($0) } + let headers = try translateComponentHeaders(resolvedHeaders) let componentsDecl: Declaration = .commentable( .doc( diff --git a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift index 46bcd0294..71939e14d 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift @@ -126,7 +126,7 @@ final class Test_YamsParser: Test_Core { /foo.yaml: error: Found neither a $ref nor a PathItem in Document.paths['/system']. PathItem could not be decoded because: - Inconsistency encountered when parsing `Vendor Extension` for the **GET** endpoint under `/system`: Found at least one vendor extension property that does not begin with the required 'x-' prefix. Invalid properties: [ resonance ].. + Problem encountered when parsing `Vendor Extension` for the **GET** endpoint under `/system`: Found at least one vendor extension property that does not begin with the required 'x-' prefix. Invalid properties: [ resonance ].. """ assertThrownError(try _test(yaml), expectedDiagnostic: expected) } diff --git a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift index a4a48f39b..62948dd94 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift @@ -81,13 +81,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -97,12 +97,12 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 2", - content: [.init(rawValue: "text/plain")!: .init(schema: .string)] + content: [.init(rawValue: "text/plain")!: .content(.init(schema: .string))] ) ) ] @@ -127,12 +127,12 @@ final class Test_validateDoc: Test_Core { "/path1": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "application/")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "application/")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -142,12 +142,12 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 2", - content: [.init(rawValue: "text/plain")!: .init(schema: .string)] + content: [.init(rawValue: "text/plain")!: .content(.init(schema: .string))] ) ) ] @@ -179,13 +179,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -195,12 +195,12 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 2", - content: [.init(rawValue: "/plain")!: .init(schema: .string)] + content: [.init(rawValue: "/plain")!: .content(.init(schema: .string))] ) ) ] @@ -232,13 +232,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -246,9 +246,9 @@ final class Test_validateDoc: Test_Core { ) ) ], - components: .init(requestBodies: [ - "exampleRequestBody1": .init(content: [.init(rawValue: "application/pdf")!: .init(schema: .string)]), - "exampleRequestBody2": .init(content: [.init(rawValue: "image/")!: .init(schema: .string)]), + components: .direct(requestBodies: [ + "exampleRequestBody1": .init(content: [.init(rawValue: "application/pdf")!: .content(.init(schema: .string))]), + "exampleRequestBody2": .init(content: [.init(rawValue: "image/")!: .content(.init(schema: .string))]), ]) ) XCTAssertThrowsError( @@ -273,13 +273,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -287,14 +287,14 @@ final class Test_validateDoc: Test_Core { ) ) ], - components: .init(responses: [ + components: .direct(responses: [ "exampleRequestBody1": .init( description: "Test description 1", - content: [.init(rawValue: "application/pdf")!: .init(schema: .string)] + content: [.init(rawValue: "application/pdf")!: .content(.init(schema: .string))] ), "exampleRequestBody2": .init( description: "Test description 2", - content: [.init(rawValue: "")!: .init(schema: .string)] + content: [.init(rawValue: "")!: .content(.init(schema: .string))] ), ]) ) @@ -321,13 +321,12 @@ final class Test_validateDoc: Test_Core { get: .init( parameters: .init( arrayLiteral: .b( - .init( + .path( name: "ID", - context: .path, content: [ - .init(rawValue: "text/plain")!: .init( - schema: .a(.component(named: "Path1ParametersContentSchemaReference")) - ) + .init(rawValue: "text/plain")!: .content(.init( + schema: .reference(.component(named: "Path1ParametersContentSchemaReference")) + )) ] ) ), @@ -340,9 +339,9 @@ final class Test_validateDoc: Test_Core { .init( description: "ResponseDescription", content: [ - .init(rawValue: "text/plain")!: .init( - schema: .a(.component(named: "ResponsesContentSchemaReference")) - ) + .init(rawValue: "text/plain")!: .content(.init( + schema: .reference(.component(named: "ResponsesContentSchemaReference")) + )) ] ) ), @@ -360,9 +359,9 @@ final class Test_validateDoc: Test_Core { parameters: .init(arrayLiteral: .a(.component(named: "Path3ExampleID"))), requestBody: .b( .init(content: [ - .init(rawValue: "text/html")!: .init( - schema: .a(.component(named: "RequestBodyContentSchemaReference")) - ) + .init(rawValue: "text/html")!: .content(.init( + schema: .reference(.component(named: "RequestBodyContentSchemaReference")) + )) ]) ), responses: [:], @@ -371,7 +370,7 @@ final class Test_validateDoc: Test_Core { ) ), ], - components: .init( + components: .direct( schemas: [ "ResponsesContentSchemaReference": .init(schema: .string(.init(), .init())), "RequestBodyContentSchemaReference": .init(schema: .integer(.init(), .init())), @@ -379,8 +378,8 @@ final class Test_validateDoc: Test_Core { ], responses: ["ResponsesReference": .init(description: "Description")], parameters: [ - "Path3ExampleID": .init(name: "ID", context: .path, content: .init()), - "Path1ParametersReference": .init(name: "Schema", context: .path, schema: .array), + "Path3ExampleID": .path(name: "ID", content: .init()), + "Path1ParametersReference": .path(name: "Schema", schema: .array), ], requestBodies: [ "RequestBodyReference": .init(content: .init()) @@ -404,9 +403,9 @@ final class Test_validateDoc: Test_Core { get: .init( requestBody: .b( .init(content: [ - .init(rawValue: "text/html")!: .init( - schema: .a(.component(named: "RequestBodyContentSchemaReference")) - ) + .init(rawValue: "text/html")!: .content(.init( + schema: .reference(.component(named: "RequestBodyContentSchemaReference")) + )) ]) ), responses: [:] diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift index abd341a71..d371ed1b8 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift @@ -19,10 +19,10 @@ final class Test_OperationDescription: Test_Core { func testAllParameters_duplicates_retainOnlyOperationParameters() throws { let pathLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test", required: false, schema: .integer) ) let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .query(required: false), schema: .string) + OpenAPI.Parameter.query(name: "test", required: false, schema: .string) ) let pathItem = OpenAPI.PathItem( @@ -37,10 +37,10 @@ final class Test_OperationDescription: Test_Core { func testAllParameters_duplicates_keepsDuplicatesFromDifferentLocation() throws { let pathLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test", required: false, schema: .integer) ) let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .path, schema: .string) + OpenAPI.Parameter.path(name: "test", schema: .string) ) let pathItem = OpenAPI.PathItem( @@ -55,13 +55,13 @@ final class Test_OperationDescription: Test_Core { func testAllParameters_duplicates_ordering() throws { let pathLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test1", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test1", required: false, schema: .integer) ) let duplicatedParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test2", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test2", required: false, schema: .integer) ) let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test3", context: .query(required: false), schema: .string) + OpenAPI.Parameter.query(name: "test3", required: false, schema: .string) ) let pathItem = OpenAPI.PathItem( diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift index ec8aafc77..45e5944e6 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift @@ -245,7 +245,7 @@ final class Test_TypeMatcher: Test_Core { static let multipartElementTypeReferenceIfReferenceableTypes: [(UnresolvedSchema?, OrderedDictionary?, String?)] = [ (nil, nil, nil), (.b(.string), nil, nil), (.a(.component(named: "Foo")), nil, "Foo"), - (.a(.component(named: "Foo")), ["foo": .init(contentType: .json)], nil), + (.a(.component(named: "Foo")), ["foo": .init(contentTypes: [.json])], nil), ] func testMultipartElementTypeReferenceIfReferenceableTypes() throws { for (schema, encoding, name) in Self.multipartElementTypeReferenceIfReferenceableTypes { diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift index 1d1e89f52..ee25c10f9 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift @@ -35,7 +35,7 @@ class Test_translateSchemas: Test_Core { ( schemaWithWarnings, [ - "warning: Schema warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"]. [context: codingPath=, contextString=, subjectName=OpenAPI Schema]" + "warning: Schema warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"]. [context: codingPath=, contextString=, subjectName=OpenAPI Schema]" ] ), ] diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index d1e6341c0..3bd8d77ce 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -6321,7 +6321,9 @@ extension SnippetBasedReferenceTests { line: UInt = #line ) throws { let translator = try makeTypesTranslator(componentsYAML: componentsYAML) - let translation = try translator.translateComponentHeaders(translator.components.headers) + let components = translator.components + let componentHeaders = try components.headers.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentHeaders(componentHeaders) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } @@ -6333,7 +6335,9 @@ extension SnippetBasedReferenceTests { line: UInt = #line ) throws { let translator = try makeTypesTranslator(accessModifier: accessModifier, componentsYAML: componentsYAML) - let translation = try translator.translateComponentParameters(translator.components.parameters) + let components = translator.components + let componentParameters = try components.parameters.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentParameters(componentParameters) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } @@ -6379,8 +6383,9 @@ extension SnippetBasedReferenceTests { try XCTAssertSwiftEquivalent(generatedSchemasStructuredSwift, expectedSchemasSwift, file: file, line: line) } if let expectedRequestBodiesSwift { + let componentRequestBodies = try components.requestBodies.mapValues { try components.assumeLookupOnce($0) } let generatedRequestBodiesStructuredSwift = try types.translateComponentRequestBodies( - document.components.requestBodies + componentRequestBodies ) try XCTAssertSwiftEquivalent( generatedRequestBodiesStructuredSwift, @@ -6438,8 +6443,10 @@ extension SnippetBasedReferenceTests { try XCTAssertSwiftEquivalent(generatedSchemasStructuredSwift, expectedSchemasSwift, file: file, line: line) } if let expectedResponsesSwift { + let components = document.components + let componentResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } let generatedRequestBodiesStructuredSwift = try types.translateComponentResponses( - document.components.responses + componentResponses ) try XCTAssertSwiftEquivalent( generatedRequestBodiesStructuredSwift, @@ -6496,7 +6503,9 @@ extension SnippetBasedReferenceTests { ignoredDiagnosticMessages: ignoredDiagnosticMessages, componentsYAML: componentsYAML ) - let translation = try translator.translateComponentResponses(translator.components.responses) + let components = translator.components + let componentResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentResponses(componentResponses) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } @@ -6513,7 +6522,9 @@ extension SnippetBasedReferenceTests { ignoredDiagnosticMessages: ignoredDiagnosticMessages, componentsYAML: componentsYAML ) - let translation = try translator.translateComponentRequestBodies(translator.components.requestBodies) + let components = translator.components + let componentRequestBodies = try components.requestBodies.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentRequestBodies(componentRequestBodies) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) }