diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift index 4195400e..4c484818 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Client.swift @@ -31,17 +31,26 @@ extension Converter { ) } - // | client | set | request path | text | string-convertible | required | renderedRequestPath | - public func renderedRequestPath( + // | client | set | request path | URI | required | renderedPath | + public func renderedPath( template: String, - parameters: [any _StringConvertible] + parameters: [any Encodable] ) throws -> String { var renderedString = template + let encoder = URIEncoder( + configuration: .init( + style: .simple, + explode: false, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) for parameter in parameters { + let value = try encoder.encode(parameter, forKey: "") if let range = renderedString.range(of: "{}") { renderedString = renderedString.replacingOccurrences( of: "{}", - with: parameter.description, + with: value, range: range ) } @@ -49,80 +58,34 @@ extension Converter { return renderedString } - // | client | set | request query | text | string-convertible | both | setQueryItemAsText | - public func setQueryItemAsText( + // | client | set | request query | URI | both | setQueryItemAsURI | + public func setQueryItemAsURI( in request: inout Request, style: ParameterStyle?, explode: Bool?, name: String, value: T? ) throws { - try setQueryItem( + try setEscapedQueryItem( in: &request, style: style, explode: explode, name: name, value: value, - convert: convertStringConvertibleToText - ) - } - - // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | - public func setQueryItemAsText( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - value: [T]? - ) throws { - try setQueryItems( - in: &request, - style: style, - explode: explode, - name: name, - values: value, - convert: convertStringConvertibleToText - ) - } - - // | client | set | request query | text | date | both | setQueryItemAsText | - public func setQueryItemAsText( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - value: Date? - ) throws { - try setQueryItem( - in: &request, - style: style, - explode: explode, - name: name, - value: value, - convert: convertDateToText - ) - } - - // | client | set | request query | text | array of dates | both | setQueryItemAsText | - public func setQueryItemAsText( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - value: [Date]? - ) throws { - try setQueryItems( - in: &request, - style: style, - explode: explode, - name: name, - values: value, - convert: convertDateToText + convert: { value, style, explode in + try convertToURI( + style: style, + explode: explode, + inBody: false, + key: name, + value: value + ) + } ) } - // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | - public func setOptionalRequestBodyAsText( + // | client | set | request body | string | optional | setOptionalRequestBodyAsString | + public func setOptionalRequestBodyAsString( _ value: T?, headerFields: inout [HeaderField], contentType: String @@ -131,12 +94,12 @@ extension Converter { value, headerFields: &headerFields, contentType: contentType, - convert: convertStringConvertibleToTextData + convert: convertToStringData ) } - // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | - public func setRequiredRequestBodyAsText( + // | client | set | request body | string | required | setRequiredRequestBodyAsString | + public func setRequiredRequestBodyAsString( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -145,39 +108,11 @@ extension Converter { value, headerFields: &headerFields, contentType: contentType, - convert: convertStringConvertibleToTextData - ) - } - - // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | - public func setOptionalRequestBodyAsText( - _ value: Date?, - headerFields: inout [HeaderField], - contentType: String - ) throws -> Data? { - try setOptionalRequestBody( - value, - headerFields: &headerFields, - contentType: contentType, - convert: convertDateToTextData + convert: convertToStringData ) } - // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | - public func setRequiredRequestBodyAsText( - _ value: Date, - headerFields: inout [HeaderField], - contentType: String - ) throws -> Data { - try setRequiredRequestBody( - value, - headerFields: &headerFields, - contentType: contentType, - convert: convertDateToTextData - ) - } - - // | client | set | request body | JSON | codable | optional | setOptionalRequestBodyAsJSON | + // | client | set | request body | JSON | optional | setOptionalRequestBodyAsJSON | public func setOptionalRequestBodyAsJSON( _ value: T?, headerFields: inout [HeaderField], @@ -191,7 +126,7 @@ extension Converter { ) } - // | client | set | request body | JSON | codable | required | setRequiredRequestBodyAsJSON | + // | client | set | request body | JSON | required | setRequiredRequestBodyAsJSON | public func setRequiredRequestBodyAsJSON( _ value: T, headerFields: inout [HeaderField], @@ -205,7 +140,7 @@ extension Converter { ) } - // | client | set | request body | binary | data | optional | setOptionalRequestBodyAsBinary | + // | client | set | request body | binary | optional | setOptionalRequestBodyAsBinary | public func setOptionalRequestBodyAsBinary( _ value: Data?, headerFields: inout [HeaderField], @@ -219,7 +154,7 @@ extension Converter { ) } - // | client | set | request body | binary | data | required | setRequiredRequestBodyAsBinary | + // | client | set | request body | binary | required | setRequiredRequestBodyAsBinary | public func setRequiredRequestBodyAsBinary( _ value: Data, headerFields: inout [HeaderField], @@ -233,8 +168,8 @@ extension Converter { ) } - // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | - public func getResponseBodyAsText( + // | client | get | response body | string | required | getResponseBodyAsString | + public func getResponseBodyAsString( _ type: T.Type, from data: Data, transforming transform: (T) -> C @@ -243,25 +178,11 @@ extension Converter { type, from: data, transforming: transform, - convert: convertTextDataToStringConvertible - ) - } - - // | client | get | response body | text | date | required | getResponseBodyAsText | - public func getResponseBodyAsText( - _ type: Date.Type, - from data: Data, - transforming transform: (Date) -> C - ) throws -> C { - try getResponseBody( - type, - from: data, - transforming: transform, - convert: convertTextDataToDate + convert: convertFromStringData ) } - // | client | get | response body | JSON | codable | required | getResponseBodyAsJSON | + // | client | get | response body | JSON | required | getResponseBodyAsJSON | public func getResponseBodyAsJSON( _ type: T.Type, from data: Data, @@ -271,11 +192,11 @@ extension Converter { type, from: data, transforming: transform, - convert: convertJSONToCodable + convert: convertJSONToBodyCodable ) } - // | client | get | response body | binary | data | required | getResponseBodyAsBinary | + // | client | get | response body | binary | required | getResponseBodyAsBinary | public func getResponseBodyAsBinary( _ type: Data.Type, from data: Data, diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift index 373b8105..1630a397 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Common.swift @@ -70,63 +70,32 @@ extension Converter { // MARK: - Converter helper methods - // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( + // | common | set | header field | URI | both | setHeaderFieldAsURI | + public func setHeaderFieldAsURI( in headerFields: inout [HeaderField], name: String, value: T? ) throws { + guard let value else { + return + } try setHeaderField( in: &headerFields, name: name, value: value, - convert: convertStringConvertibleToText - ) - } - - // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( - in headerFields: inout [HeaderField], - name: String, - value values: [T]? - ) throws { - try setHeaderFields( - in: &headerFields, - name: name, - values: values, - convert: convertStringConvertibleToText - ) - } - - // | common | set | header field | text | date | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( - in headerFields: inout [HeaderField], - name: String, - value: Date? - ) throws { - try setHeaderField( - in: &headerFields, - name: name, - value: value, - convert: convertDateToText + convert: { value in + try convertToURI( + style: .simple, + explode: false, + inBody: false, + key: "", + value: value + ) + } ) } - // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | - public func setHeaderFieldAsText( - in headerFields: inout [HeaderField], - name: String, - value values: [Date]? - ) throws { - try setHeaderFields( - in: &headerFields, - name: name, - values: values, - convert: convertDateToText - ) - } - - // | common | set | header field | JSON | codable | both | setHeaderFieldAsJSON | + // | common | set | header field | JSON | both | setHeaderFieldAsJSON | public func setHeaderFieldAsJSON( in headerFields: inout [HeaderField], name: String, @@ -140,8 +109,8 @@ extension Converter { ) } - // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | - public func getOptionalHeaderFieldAsText( + // | common | get | header field | URI | optional | getOptionalHeaderFieldAsURI | + public func getOptionalHeaderFieldAsURI( in headerFields: [HeaderField], name: String, as type: T.Type @@ -150,12 +119,20 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertTextToStringConvertible + convert: { encodedValue in + try convertFromURI( + style: .simple, + explode: false, + inBody: false, + key: "", + encodedValue: encodedValue + ) + } ) } - // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | - public func getRequiredHeaderFieldAsText( + // | common | get | header field | URI | required | getRequiredHeaderFieldAsURI | + public func getRequiredHeaderFieldAsURI( in headerFields: [HeaderField], name: String, as type: T.Type @@ -164,95 +141,19 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertTextToStringConvertible - ) - } - - // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | - public func getOptionalHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [T].Type - ) throws -> [T]? { - try getOptionalHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | - public func getRequiredHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [T].Type - ) throws -> [T] { - try getRequiredHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | - public func getOptionalHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: Date.Type - ) throws -> Date? { - try getOptionalHeaderField( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate - ) - } - - // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | - public func getRequiredHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: Date.Type - ) throws -> Date { - try getRequiredHeaderField( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate - ) - } - - // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | - public func getOptionalHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [Date].Type - ) throws -> [Date]? { - try getOptionalHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate - ) - } - - // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | - public func getRequiredHeaderFieldAsText( - in headerFields: [HeaderField], - name: String, - as type: [Date].Type - ) throws -> [Date] { - try getRequiredHeaderFields( - in: headerFields, - name: name, - as: type, - convert: convertHeaderFieldTextToDate + convert: { encodedValue in + try convertFromURI( + style: .simple, + explode: false, + inBody: false, + key: "", + encodedValue: encodedValue + ) + } ) } - // | common | get | header field | JSON | codable | optional | getOptionalHeaderFieldAsJSON | + // | common | get | header field | JSON | optional | getOptionalHeaderFieldAsJSON | public func getOptionalHeaderFieldAsJSON( in headerFields: [HeaderField], name: String, @@ -262,11 +163,11 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertHeaderFieldJSONToCodable + convert: convertJSONToHeaderFieldCodable ) } - // | common | get | header field | JSON | codable | required | getRequiredHeaderFieldAsJSON | + // | common | get | header field | JSON | required | getRequiredHeaderFieldAsJSON | public func getRequiredHeaderFieldAsJSON( in headerFields: [HeaderField], name: String, @@ -276,7 +177,7 @@ extension Converter { in: headerFields, name: name, as: type, - convert: convertHeaderFieldJSONToCodable + convert: convertJSONToHeaderFieldCodable ) } } diff --git a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift index 98e83e06..05c34097 100644 --- a/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift +++ b/Sources/OpenAPIRuntime/Conversion/Converter+Server.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation -public extension Converter { +extension Converter { // MARK: Miscs @@ -22,7 +22,7 @@ public extension Converter { /// header. /// - Returns: The parsed content types, or the default content types if /// the header was not provided. - func extractAcceptHeaderIfPresent( + public func extractAcceptHeaderIfPresent( in headerFields: [HeaderField] ) throws -> [AcceptHeaderContentType] { guard let rawValue = headerFields.firstValue(name: "accept") else { @@ -48,7 +48,7 @@ public extension Converter { /// - substring: Expected content type, for example "application/json". /// - headerFields: Header fields in which to look for "Accept". /// Also supports wildcars, such as "application/\*" and "\*/\*". - func validateAcceptIfPresent( + public func validateAcceptIfPresent( _ substring: String, in headerFields: [HeaderField] ) throws { @@ -82,8 +82,8 @@ public extension Converter { throw RuntimeError.unexpectedAcceptHeader(acceptHeader) } - // | server | get | request path | text | string-convertible | required | getPathParameterAsText | - func getPathParameterAsText( + // | server | get | request path | URI | required | getPathParameterAsURI | + public func getPathParameterAsURI( in pathParameters: [String: String], name: String, as type: T.Type @@ -91,157 +91,94 @@ public extension Converter { try getRequiredRequestPath( in: pathParameters, name: name, - as: type, - convert: convertTextToStringConvertible + as: T.self, + convert: { encodedString in + let decoder = URIDecoder( + configuration: .init( + style: .simple, + explode: false, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) + let value = try decoder.decode( + T.self, + forKey: name, + from: encodedString + ) + return value + } ) } - // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], + // | server | get | request query | URI | optional | getOptionalQueryItemAsURI | + public func getOptionalQueryItemAsURI( + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type ) throws -> T? { try getOptionalQueryItem( - in: queryParameters, + in: query, style: style, explode: explode, name: name, as: type, - convert: convertTextToStringConvertible + convert: { query, style, explode in + let decoder = URIDecoder( + configuration: .init( + style: .init(style), + explode: explode, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) + let value = try decoder.decode( + T.self, + forKey: name, + from: query + ) + return value + } ) } - // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], + // | server | get | request query | URI | required | getRequiredQueryItemAsURI | + public func getRequiredQueryItemAsURI( + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type ) throws -> T { try getRequiredQueryItem( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type - ) throws -> [T]? { - try getOptionalQueryItems( - in: queryParameters, + in: query, style: style, explode: explode, name: name, as: type, - convert: convertTextToStringConvertible - ) - } - - // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type - ) throws -> [T] { - try getRequiredQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToStringConvertible - ) - } - - // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: Date.Type - ) throws -> Date? { - try getOptionalQueryItem( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate - ) - } - - // | server | get | request query | text | date | required | getRequiredQueryItemAsText | - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: Date.Type - ) throws -> Date { - try getRequiredQueryItem( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate - ) - } - - // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | - func getOptionalQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [Date].Type - ) throws -> [Date]? { - try getOptionalQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate - ) - } - - // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | - func getRequiredQueryItemAsText( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [Date].Type - ) throws -> [Date] { - try getRequiredQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convertTextToDate + convert: { query, style, explode in + let decoder = URIDecoder( + configuration: .init( + style: .init(style), + explode: explode, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) + ) + let value = try decoder.decode( + T.self, + forKey: name, + from: query + ) + return value + } ) } - // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | - func getOptionalRequestBodyAsText( + // | server | get | request body | string | optional | getOptionalRequestBodyAsString | + public func getOptionalRequestBodyAsString( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -250,12 +187,18 @@ public extension Converter { type, from: data, transforming: transform, - convert: convertTextDataToStringConvertible + convert: { encodedData in + let decoder = StringDecoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = String(decoding: encodedData, as: UTF8.self) + return try decoder.decode(T.self, from: encodedString) + } ) } - // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | - func getRequiredRequestBodyAsText( + // | server | get | request body | string | required | getRequiredRequestBodyAsString | + public func getRequiredRequestBodyAsString( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -264,40 +207,18 @@ public extension Converter { type, from: data, transforming: transform, - convert: convertTextDataToStringConvertible - ) - } - - // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | - func getOptionalRequestBodyAsText( - _ type: Date.Type, - from data: Data?, - transforming transform: (Date) -> C - ) throws -> C? { - try getOptionalRequestBody( - type, - from: data, - transforming: transform, - convert: convertTextDataToDate - ) - } - - // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | - func getRequiredRequestBodyAsText( - _ type: Date.Type, - from data: Data?, - transforming transform: (Date) -> C - ) throws -> C { - try getRequiredRequestBody( - type, - from: data, - transforming: transform, - convert: convertTextDataToDate + convert: { encodedData in + let decoder = StringDecoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = String(decoding: encodedData, as: UTF8.self) + return try decoder.decode(T.self, from: encodedString) + } ) } - // | server | get | request body | JSON | codable | optional | getOptionalRequestBodyAsJSON | - func getOptionalRequestBodyAsJSON( + // | server | get | request body | JSON | optional | getOptionalRequestBodyAsJSON | + public func getOptionalRequestBodyAsJSON( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -306,12 +227,12 @@ public extension Converter { type, from: data, transforming: transform, - convert: convertJSONToCodable + convert: convertJSONToBodyCodable ) } - // | server | get | request body | JSON | codable | required | getRequiredRequestBodyAsJSON | - func getRequiredRequestBodyAsJSON( + // | server | get | request body | JSON | required | getRequiredRequestBodyAsJSON | + public func getRequiredRequestBodyAsJSON( _ type: T.Type, from data: Data?, transforming transform: (T) -> C @@ -320,12 +241,12 @@ public extension Converter { type, from: data, transforming: transform, - convert: convertJSONToCodable + convert: convertJSONToBodyCodable ) } - // | server | get | request body | binary | data | optional | getOptionalRequestBodyAsBinary | - func getOptionalRequestBodyAsBinary( + // | server | get | request body | binary | optional | getOptionalRequestBodyAsBinary | + public func getOptionalRequestBodyAsBinary( _ type: Data.Type, from data: Data?, transforming transform: (Data) -> C @@ -338,8 +259,8 @@ public extension Converter { ) } - // | server | get | request body | binary | data | required | getRequiredRequestBodyAsBinary | - func getRequiredRequestBodyAsBinary( + // | server | get | request body | binary | required | getRequiredRequestBodyAsBinary | + public func getRequiredRequestBodyAsBinary( _ type: Data.Type, from data: Data?, transforming transform: (Data) -> C @@ -352,8 +273,8 @@ public extension Converter { ) } - // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | - func setResponseBodyAsText( + // | server | set | response body | string | required | setResponseBodyAsString | + public func setResponseBodyAsString( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -362,26 +283,19 @@ public extension Converter { value, headerFields: &headerFields, contentType: contentType, - convert: convertStringConvertibleToTextData - ) - } - - // | server | set | response body | text | date | required | setResponseBodyAsText | - func setResponseBodyAsText( - _ value: Date, - headerFields: inout [HeaderField], - contentType: String - ) throws -> Data { - try setResponseBody( - value, - headerFields: &headerFields, - contentType: contentType, - convert: convertDateToTextData + convert: { value in + let encoder = StringEncoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = try encoder.encode(value) + let encodedData = Data(encodedString.utf8) + return encodedData + } ) } - // | server | set | response body | JSON | codable | required | setResponseBodyAsJSON | - func setResponseBodyAsJSON( + // | server | set | response body | JSON | required | setResponseBodyAsJSON | + public func setResponseBodyAsJSON( _ value: T, headerFields: inout [HeaderField], contentType: String @@ -394,8 +308,8 @@ public extension Converter { ) } - // | server | set | response body | binary | data | required | setResponseBodyAsBinary | - func setResponseBodyAsBinary( + // | server | set | response body | binary | required | setResponseBodyAsBinary | + public func setResponseBodyAsBinary( _ value: Data, headerFields: inout [HeaderField], contentType: String diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 881aca62..5b444431 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -122,38 +122,62 @@ extension ParameterStyle { extension Converter { - // MARK: Common functions for Converter's SPI helper methods - - func convertStringConvertibleToText( - _ value: T - ) throws -> String { - value.description - } - - func convertStringConvertibleToTextData( - _ value: T - ) throws -> Data { - try Data(convertStringConvertibleToText(value).utf8) - } - - func convertDateToText(_ value: Date) throws -> String { - try configuration.dateTranscoder.encode(value) - } - - func convertDateToTextData(_ value: Date) throws -> Data { - try Data(convertDateToText(value).utf8) + // MARK: Converter helpers + + func uriCoderConfiguration( + style: ParameterStyle, + explode: Bool, + inBody: Bool + ) -> URICoderConfiguration { + .init( + style: .init(style), + explode: explode, + spaceEscapingCharacter: inBody ? .plus : .percentEncoded, + dateTranscoder: configuration.dateTranscoder + ) } - func convertTextToDate(_ stringValue: String) throws -> Date { - try configuration.dateTranscoder.decode(stringValue) + func convertToURI( + style: ParameterStyle, + explode: Bool, + inBody: Bool, + key: String, + value: T + ) throws -> String { + let encoder = URIEncoder( + configuration: uriCoderConfiguration( + style: style, + explode: explode, + inBody: inBody + ) + ) + let encodedString = try encoder.encode(value, forKey: key) + return encodedString } - func convertTextDataToDate(_ data: Data) throws -> Date { - let stringValue = String(decoding: data, as: UTF8.self) - return try convertTextToDate(stringValue) + func convertFromURI( + style: ParameterStyle, + explode: Bool, + inBody: Bool, + key: String, + encodedValue: String + ) throws -> T { + let decoder = URIDecoder( + configuration: uriCoderConfiguration( + style: style, + explode: explode, + inBody: inBody + ) + ) + let value = try decoder.decode( + T.self, + forKey: key, + from: encodedValue + ) + return value } - func convertJSONToCodable( + func convertJSONToBodyCodable( _ data: Data ) throws -> T { try decoder.decode(T.self, from: data) @@ -165,10 +189,6 @@ extension Converter { try encoder.encode(value) } - func convertHeaderFieldTextToDate(_ stringValue: String) throws -> Date { - try convertTextToDate(stringValue) - } - func convertHeaderFieldCodableToJSON( _ value: T ) throws -> String { @@ -177,31 +197,51 @@ extension Converter { return stringValue } - func convertHeaderFieldJSONToCodable( + func convertJSONToHeaderFieldCodable( _ stringValue: String ) throws -> T { let data = Data(stringValue.utf8) return try decoder.decode(T.self, from: data) } - func convertTextToStringConvertible( - _ stringValue: String + func convertFromStringData( + _ data: Data ) throws -> T { - guard let value = T.init(stringValue) else { - throw RuntimeError.failedToDecodeStringConvertibleValue( - type: String(describing: T.self) - ) - } + let encodedString = String(decoding: data, as: UTF8.self) + let decoder = StringDecoder( + dateTranscoder: configuration.dateTranscoder + ) + let value = try decoder.decode( + T.self, + from: encodedString + ) return value } - func convertTextDataToStringConvertible( + func convertToStringData( + _ value: T + ) throws -> Data { + let encoder = StringEncoder( + dateTranscoder: configuration.dateTranscoder + ) + let encodedString = try encoder.encode(value) + return Data(encodedString.utf8) + } + + func convertBinaryToData( + _ binary: Data + ) throws -> Data { + binary + } + + func convertDataToBinary( _ data: Data - ) throws -> T { - let stringValue = String(decoding: data, as: UTF8.self) - return try convertTextToStringConvertible(stringValue) + ) throws -> Data { + data } + // MARK: - Helpers for specific types of parameters + func setHeaderField( in headerFields: inout [HeaderField], name: String, @@ -217,21 +257,15 @@ extension Converter { ) } - func setHeaderFields( - in headerFields: inout [HeaderField], - name: String, - values: [T]?, - convert: (T) throws -> String - ) throws { - guard let values else { - return - } - for value in values { - headerFields.add( - name: name, - value: try convert(value) - ) + func getHeaderFieldValuesString( + in headerFields: [HeaderField], + name: String + ) -> String? { + let values = headerFields.values(name: name) + guard !values.isEmpty else { + return nil } + return values.joined(separator: ",") } func getOptionalHeaderField( @@ -240,7 +274,12 @@ extension Converter { as type: T.Type, convert: (String) throws -> T ) throws -> T? { - guard let stringValue = headerFields.firstValue(name: name) else { + guard + let stringValue = getHeaderFieldValuesString( + in: headerFields, + name: name + ) + else { return nil } return try convert(stringValue) @@ -252,123 +291,67 @@ extension Converter { as type: T.Type, convert: (String) throws -> T ) throws -> T { - guard let stringValue = headerFields.firstValue(name: name) else { + guard + let stringValue = getHeaderFieldValuesString( + in: headerFields, + name: name + ) + else { throw RuntimeError.missingRequiredHeaderField(name) } return try convert(stringValue) } - func getOptionalHeaderFields( - in headerFields: [HeaderField], - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T]? { - let values = headerFields.values(name: name) - if values.isEmpty { - return nil - } - return try values.map { value in try convert(value) } - } - - func getRequiredHeaderFields( - in headerFields: [HeaderField], - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T] { - let values = headerFields.values(name: name) - if values.isEmpty { - throw RuntimeError.missingRequiredHeaderField(name) - } - return try values.map { value in try convert(value) } - } - - func setQueryItem( + func setEscapedQueryItem( in request: inout Request, style: ParameterStyle?, explode: Bool?, name: String, value: T?, - convert: (T) throws -> String + convert: (T, ParameterStyle, Bool) throws -> String ) throws { guard let value else { return } - let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( - name: name, - style: style, - explode: explode - ) - request.addQueryItem( - name: name, - value: try convert(value), - explode: resolvedExplode - ) - } - - func setQueryItems( - in request: inout Request, - style: ParameterStyle?, - explode: Bool?, - name: String, - values: [T]?, - convert: (T) throws -> String - ) throws { - guard let values else { - return - } - let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + let (resolvedStyle, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( name: name, style: style, explode: explode ) - for value in values { - request.addQueryItem( - name: name, - value: try convert(value), - explode: resolvedExplode - ) - } + let uriSnippet = try convert(value, resolvedStyle, resolvedExplode) + request.addEscapedQuerySnippet(uriSnippet) } func getOptionalQueryItem( - in queryParameters: [URLQueryItem], + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type, - convert: (String) throws -> T + convert: (String, ParameterStyle, Bool) throws -> T ) throws -> T? { - // Even though the return value isn't used, the validation - // in the method is important for consistently handling - // style+explode combinations in all the helper functions. - let (_, _) = try ParameterStyle.resolvedQueryStyleAndExplode( + guard let query else { + return nil + } + let (resolvedStyle, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( name: name, style: style, explode: explode ) - guard - let untypedValue = - queryParameters - .first(where: { $0.name == name }) - else { - return nil - } - return try convert(untypedValue.value ?? "") + return try convert(query, resolvedStyle, resolvedExplode) } func getRequiredQueryItem( - in queryParameters: [URLQueryItem], + in query: String?, style: ParameterStyle?, explode: Bool?, name: String, as type: T.Type, - convert: (String) throws -> T + convert: (String, ParameterStyle, Bool) throws -> T ) throws -> T { guard let value = try getOptionalQueryItem( - in: queryParameters, + in: query, style: style, explode: explode, name: name, @@ -381,61 +364,6 @@ extension Converter { return value } - func getOptionalQueryItems( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T]? { - let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( - name: name, - style: style, - explode: explode - ) - let untypedValues = - queryParameters - .filter { $0.name == name } - .map { $0.value ?? "" } - // If explode is false, some of the items might have multiple - // comma-separate values, so we need to split them here. - let processedValues: [String] - if resolvedExplode { - processedValues = untypedValues - } else { - processedValues = untypedValues.flatMap { multiValue in - multiValue - .split(separator: ",", omittingEmptySubsequences: false) - .map(String.init) - } - } - return try processedValues.map(convert) - } - - func getRequiredQueryItems( - in queryParameters: [URLQueryItem], - style: ParameterStyle?, - explode: Bool?, - name: String, - as type: [T].Type, - convert: (String) throws -> T - ) throws -> [T] { - guard - let values = try getOptionalQueryItems( - in: queryParameters, - style: style, - explode: explode, - name: name, - as: type, - convert: convert - ), !values.isEmpty - else { - throw RuntimeError.missingRequiredQueryParameter(name) - } - return values - } - func setRequiredRequestBody( _ value: T, headerFields: inout [HeaderField], @@ -516,18 +444,6 @@ extension Converter { return try convert(value) } - func convertBinaryToData( - _ binary: Data - ) throws -> Data { - binary - } - - func convertDataToBinary( - _ data: Data - ) throws -> Data { - data - } - func getRequiredRequestPath( in pathParameters: [String: String], name: String, diff --git a/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift b/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift index 4c2b5cb8..ce6d6833 100644 --- a/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/FoundationExtensions.swift @@ -36,69 +36,19 @@ extension Request { query = urlComponents.percentEncodedQuery } - /// Adds the provided name and value to the URL's query. + /// Adds the provided URI snippet to the URL's query. + /// + /// Percent encoding is already applied. /// - Parameters: - /// - name: The name of the query item. - /// - value: The value of the query item. - /// - explode: A Boolean value indicating whether query items with the - /// same name should be provided as separate key-value pairs (`true`) or - /// if all the values for one key should be concatenated with a comma - /// and provided as a single key-value pair (`false`). - mutating func addQueryItem(name: String, value: String, explode: Bool) { - mutateQuery { urlComponents in - urlComponents.addStringQueryItem( - name: name, - value: value, - explode: explode - ) - } - } -} - -extension URLComponents { - - /// Adds the provided name and value to the URL's query. - /// - Parameters: - /// - name: The name of the query item. - /// - value: The value of the query item. - /// - explode: A Boolean value indicating whether query items with the - /// same name should be provided as separate key-value pairs (`true`) or - /// if all the values for one key should be concatenated with a comma - /// and provided as a single key-value pair (`false`). - mutating func addStringQueryItem( - name: String, - value: String, - explode: Bool - ) { - if explode { - queryItems = - (queryItems ?? []) + [ - .init(name: name, value: value) - ] - return - } - // When explode is false, we need to collect all the potential existing - // values from the array with the same name, add the new one, and - // concatenate them with a comma. - let originalQueryItems = queryItems ?? [] - struct GroupedQueryItems { - var matchingValues: [String] = [] - var otherItems: [URLQueryItem] = [] + /// - snippet: A full URI snippet. + mutating func addEscapedQuerySnippet(_ snippet: String) { + let prefix: String + if let query { + prefix = query + "&" + } else { + prefix = "" } - let groups = - originalQueryItems - .reduce(into: GroupedQueryItems()) { partialResult, item in - if item.name == name { - partialResult.matchingValues.append(item.value ?? "") - } else { - partialResult.otherItems.append(item) - } - } - let newItem = URLQueryItem( - name: name, - value: (groups.matchingValues + [value]).joined(separator: ",") - ) - queryItems = groups.otherItems + [newItem] + query = prefix + snippet } } diff --git a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift index 13f3e8a7..5a833cba 100644 --- a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift +++ b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift @@ -49,3 +49,14 @@ extension ParameterStyle { style == .form } } + +extension URICoderConfiguration.Style { + init(_ style: ParameterStyle) { + switch style { + case .form: + self = .form + case .simple: + self = .simple + } + } +} diff --git a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift index 351347c8..8d04bc44 100644 --- a/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated.swift @@ -983,13 +983,15 @@ extension Converter { name: String, value: T? ) throws { - try setQueryItem( + try setUnescapedQueryItem( in: &request, style: nil, explode: nil, name: name, value: value, - convert: convertStringConvertibleToText + convert: { value, _, _ in + try convertStringConvertibleToText(value) + } ) } @@ -1017,13 +1019,15 @@ extension Converter { name: String, value: Date? ) throws { - try setQueryItem( + try setUnescapedQueryItem( in: &request, style: nil, explode: nil, name: name, value: value, - convert: convertDateToText + convert: { value, _, _ in + try convertDateToText(value) + } ) } @@ -1179,4 +1183,968 @@ extension Converter { convert: convertTextToDate ) } + + // MARK: - Deprecated as part of moving to URI coder + + // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value: T? + ) throws { + try setHeaderField( + in: &headerFields, + name: name, + value: value, + convert: convertStringConvertibleToText + ) + } + + // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value values: [T]? + ) throws { + try setHeaderFields( + in: &headerFields, + name: name, + values: values, + convert: convertStringConvertibleToText + ) + } + + // | common | set | header field | text | date | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value: Date? + ) throws { + try setHeaderField( + in: &headerFields, + name: name, + value: value, + convert: convertDateToText + ) + } + + // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | + @available(*, deprecated) + public func setHeaderFieldAsText( + in headerFields: inout [HeaderField], + name: String, + value values: [Date]? + ) throws { + try setHeaderFields( + in: &headerFields, + name: name, + values: values, + convert: convertDateToText + ) + } + + // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: T.Type + ) throws -> T? { + try getOptionalHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: T.Type + ) throws -> T { + try getRequiredHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [T].Type + ) throws -> [T]? { + try getOptionalHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [T].Type + ) throws -> [T] { + try getRequiredHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: Date.Type + ) throws -> Date? { + try getOptionalHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: Date.Type + ) throws -> Date { + try getRequiredHeaderField( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + public func getOptionalHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [Date].Type + ) throws -> [Date]? { + try getOptionalHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + public func getRequiredHeaderFieldAsText( + in headerFields: [HeaderField], + name: String, + as type: [Date].Type + ) throws -> [Date] { + try getRequiredHeaderFields( + in: headerFields, + name: name, + as: type, + convert: convertHeaderFieldTextToDate + ) + } + + // | client | set | request path | text | string-convertible | required | renderedRequestPath | + @available(*, deprecated) + public func renderedRequestPath( + template: String, + parameters: [any _StringConvertible] + ) throws -> String { + var renderedString = template + for parameter in parameters { + if let range = renderedString.range(of: "{}") { + renderedString = renderedString.replacingOccurrences( + of: "{}", + with: parameter.description, + range: range + ) + } + } + return renderedString + } + + // | client | set | request query | text | string-convertible | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: T? + ) throws { + try setUnescapedQueryItem( + in: &request, + style: style, + explode: explode, + name: name, + value: value, + convert: { value, _, _ in + try convertStringConvertibleToText(value) + } + ) + } + + // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: [T]? + ) throws { + try setQueryItems( + in: &request, + style: style, + explode: explode, + name: name, + values: value, + convert: convertStringConvertibleToText + ) + } + + // | client | set | request query | text | date | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: Date? + ) throws { + try setUnescapedQueryItem( + in: &request, + style: style, + explode: explode, + name: name, + value: value, + convert: { value, _, _ in + try convertDateToText(value) + } + ) + } + + // | client | set | request query | text | array of dates | both | setQueryItemAsText | + @available(*, deprecated) + public func setQueryItemAsText( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: [Date]? + ) throws { + try setQueryItems( + in: &request, + style: style, + explode: explode, + name: name, + values: value, + convert: convertDateToText + ) + } + + // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | + @available(*, deprecated) + public func getResponseBodyAsText( + _ type: T.Type, + from data: Data, + transforming transform: (T) -> C + ) throws -> C { + try getResponseBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToStringConvertible + ) + } + + // | client | get | response body | text | date | required | getResponseBodyAsText | + @available(*, deprecated) + public func getResponseBodyAsText( + _ type: Date.Type, + from data: Data, + transforming transform: (Date) -> C + ) throws -> C { + try getResponseBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToDate + ) + } + + // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + public func setOptionalRequestBodyAsText( + _ value: T?, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data? { + try setOptionalRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertStringConvertibleToTextData + ) + } + + // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + public func setRequiredRequestBodyAsText( + _ value: T, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setRequiredRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertStringConvertibleToTextData + ) + } + + // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + public func setOptionalRequestBodyAsText( + _ value: Date?, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data? { + try setOptionalRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertDateToTextData + ) + } + + // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + public func setRequiredRequestBodyAsText( + _ value: Date, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setRequiredRequestBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertDateToTextData + ) + } + + @available(*, deprecated) + func convertStringConvertibleToText( + _ value: T + ) throws -> String { + value.description + } + + @available(*, deprecated) + func convertStringConvertibleToTextData( + _ value: T + ) throws -> Data { + try Data(convertStringConvertibleToText(value).utf8) + } + + @available(*, deprecated) + func convertDateToText(_ value: Date) throws -> String { + try configuration.dateTranscoder.encode(value) + } + + @available(*, deprecated) + func convertDateToTextData(_ value: Date) throws -> Data { + try Data(convertDateToText(value).utf8) + } + + @available(*, deprecated) + func convertTextToDate(_ stringValue: String) throws -> Date { + try configuration.dateTranscoder.decode(stringValue) + } + + @available(*, deprecated) + func convertTextDataToDate(_ data: Data) throws -> Date { + let stringValue = String(decoding: data, as: UTF8.self) + return try convertTextToDate(stringValue) + } + + @available(*, deprecated) + func convertHeaderFieldTextToDate(_ stringValue: String) throws -> Date { + try convertTextToDate(stringValue) + } + + @available(*, deprecated) + func convertTextToStringConvertible( + _ stringValue: String + ) throws -> T { + guard let value = T.init(stringValue) else { + throw RuntimeError.failedToDecodeStringConvertibleValue( + type: String(describing: T.self) + ) + } + return value + } + + @available(*, deprecated) + func convertTextDataToStringConvertible( + _ data: Data + ) throws -> T { + let stringValue = String(decoding: data, as: UTF8.self) + return try convertTextToStringConvertible(stringValue) + } + + @available(*, deprecated) + func setHeaderFields( + in headerFields: inout [HeaderField], + name: String, + values: [T]?, + convert: (T) throws -> String + ) throws { + guard let values else { + return + } + for value in values { + headerFields.add( + name: name, + value: try convert(value) + ) + } + } + + @available(*, deprecated) + func getOptionalHeaderFields( + in headerFields: [HeaderField], + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T]? { + let values = headerFields.values(name: name) + if values.isEmpty { + return nil + } + return try values.map { value in try convert(value) } + } + + @available(*, deprecated) + func getRequiredHeaderFields( + in headerFields: [HeaderField], + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T] { + let values = headerFields.values(name: name) + if values.isEmpty { + throw RuntimeError.missingRequiredHeaderField(name) + } + return try values.map { value in try convert(value) } + } + + @available(*, deprecated) + func setQueryItems( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + values: [T]?, + convert: (T) throws -> String + ) throws { + guard let values else { + return + } + let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + for value in values { + request.addUnescapedQueryItem( + name: name, + value: try convert(value), + explode: resolvedExplode + ) + } + } + + @available(*, deprecated) + func getOptionalQueryItems( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T]? { + let (_, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + let untypedValues = + queryParameters + .filter { $0.name == name } + .map { $0.value ?? "" } + // If explode is false, some of the items might have multiple + // comma-separate values, so we need to split them here. + let processedValues: [String] + if resolvedExplode { + processedValues = untypedValues + } else { + processedValues = untypedValues.flatMap { multiValue in + multiValue + .split(separator: ",", omittingEmptySubsequences: false) + .map(String.init) + } + } + return try processedValues.map(convert) + } + + @available(*, deprecated) + func getRequiredQueryItems( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type, + convert: (String) throws -> T + ) throws -> [T] { + guard + let values = try getOptionalQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convert + ), !values.isEmpty + else { + throw RuntimeError.missingRequiredQueryParameter(name) + } + return values + } + + @available(*, deprecated) + func setUnescapedQueryItem( + in request: inout Request, + style: ParameterStyle?, + explode: Bool?, + name: String, + value: T?, + convert: (T, ParameterStyle, Bool) throws -> String + ) throws { + guard let value else { + return + } + let (resolvedStyle, resolvedExplode) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + request.addUnescapedQueryItem( + name: name, + value: try convert(value, resolvedStyle, resolvedExplode), + explode: resolvedExplode + ) + } +} + +extension Request { + /// Adds the provided name and value to the URL's query. + /// - Parameters: + /// - name: The name of the query item. + /// - value: The value of the query item. + /// - explode: A Boolean value indicating whether query items with the + /// same name should be provided as separate key-value pairs (`true`) or + /// if all the values for one key should be concatenated with a comma + /// and provided as a single key-value pair (`false`). + @available(*, deprecated) + mutating func addUnescapedQueryItem(name: String, value: String, explode: Bool) { + mutateQuery { urlComponents in + urlComponents.addUnescapedStringQueryItem( + name: name, + value: value, + explode: explode + ) + } + } +} + +extension URLComponents { + + /// Adds the provided name and value to the URL's query. + /// - Parameters: + /// - name: The name of the query item. + /// - value: The value of the query item. + /// - explode: A Boolean value indicating whether query items with the + /// same name should be provided as separate key-value pairs (`true`) or + /// if all the values for one key should be concatenated with a comma + /// and provided as a single key-value pair (`false`). + @available(*, deprecated) + mutating func addUnescapedStringQueryItem( + name: String, + value: String, + explode: Bool + ) { + if explode { + queryItems = + (queryItems ?? []) + [ + .init(name: name, value: value) + ] + return + } + // When explode is false, we need to collect all the potential existing + // values from the array with the same name, add the new one, and + // concatenate them with a comma. + let originalQueryItems = queryItems ?? [] + struct GroupedQueryItems { + var matchingValues: [String] = [] + var otherItems: [URLQueryItem] = [] + } + let groups = + originalQueryItems + .reduce(into: GroupedQueryItems()) { partialResult, item in + if item.name == name { + partialResult.matchingValues.append(item.value ?? "") + } else { + partialResult.otherItems.append(item) + } + } + let newItem = URLQueryItem( + name: name, + value: (groups.matchingValues + [value]).joined(separator: ",") + ) + queryItems = groups.otherItems + [newItem] + } +} + +extension Converter { + // | server | get | request path | text | string-convertible | required | getPathParameterAsText | + @available(*, deprecated) + public func getPathParameterAsText( + in pathParameters: [String: String], + name: String, + as type: T.Type + ) throws -> T { + try getRequiredRequestPath( + in: pathParameters, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type + ) throws -> T? { + try getOptionalQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type + ) throws -> T { + try getRequiredQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type + ) throws -> [T]? { + try getOptionalQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [T].Type + ) throws -> [T] { + try getRequiredQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToStringConvertible + ) + } + + // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: Date.Type + ) throws -> Date? { + try getOptionalQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + // | server | get | request query | text | date | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: Date.Type + ) throws -> Date { + try getRequiredQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + public func getOptionalQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [Date].Type + ) throws -> [Date]? { + try getOptionalQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | + @available(*, deprecated) + public func getRequiredQueryItemAsText( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: [Date].Type + ) throws -> [Date] { + try getRequiredQueryItems( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convertTextToDate + ) + } + + @available(*, deprecated) + func getOptionalQueryItem( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type, + convert: (String) throws -> T + ) throws -> T? { + // Even though the return value isn't used, the validation + // in the method is important for consistently handling + // style+explode combinations in all the helper functions. + let (_, _) = try ParameterStyle.resolvedQueryStyleAndExplode( + name: name, + style: style, + explode: explode + ) + guard + let untypedValue = + queryParameters + .first(where: { $0.name == name }) + else { + return nil + } + return try convert(untypedValue.value ?? "") + } + + @available(*, deprecated) + func getRequiredQueryItem( + in queryParameters: [URLQueryItem], + style: ParameterStyle?, + explode: Bool?, + name: String, + as type: T.Type, + convert: (String) throws -> T + ) throws -> T { + guard + let value = try getOptionalQueryItem( + in: queryParameters, + style: style, + explode: explode, + name: name, + as: type, + convert: convert + ) + else { + throw RuntimeError.missingRequiredQueryParameter(name) + } + return value + } + + // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + public func getOptionalRequestBodyAsText( + _ type: T.Type, + from data: Data?, + transforming transform: (T) -> C + ) throws -> C? { + try getOptionalRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToStringConvertible + ) + } + + // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + public func getRequiredRequestBodyAsText( + _ type: T.Type, + from data: Data?, + transforming transform: (T) -> C + ) throws -> C { + try getRequiredRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToStringConvertible + ) + } + + // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + public func getOptionalRequestBodyAsText( + _ type: Date.Type, + from data: Data?, + transforming transform: (Date) -> C + ) throws -> C? { + try getOptionalRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToDate + ) + } + + // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + public func getRequiredRequestBodyAsText( + _ type: Date.Type, + from data: Data?, + transforming transform: (Date) -> C + ) throws -> C { + try getRequiredRequestBody( + type, + from: data, + transforming: transform, + convert: convertTextDataToDate + ) + } + + // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + @available(*, deprecated) + public func setResponseBodyAsText( + _ value: T, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setResponseBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertStringConvertibleToTextData + ) + } + + // | server | set | response body | text | date | required | setResponseBodyAsText | + @available(*, deprecated) + public func setResponseBodyAsText( + _ value: Date, + headerFields: inout [HeaderField], + contentType: String + ) throws -> Data { + try setResponseBody( + value, + headerFields: &headerFields, + contentType: contentType, + convert: convertDateToTextData + ) + } + } diff --git a/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated_AutoLosslessStringConvertible.swift similarity index 95% rename from Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift rename to Sources/OpenAPIRuntime/Deprecated/Deprecated_AutoLosslessStringConvertible.swift index 38116c73..c7588d8e 100644 --- a/Sources/OpenAPIRuntime/Base/_AutoLosslessStringConvertible.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated_AutoLosslessStringConvertible.swift @@ -19,10 +19,12 @@ /// /// Cannot be marked as SPI, as it's added on public types, but should be /// considered an internal implementation detail of the generator. +@available(*, deprecated) public protocol _AutoLosslessStringConvertible: RawRepresentable, LosslessStringConvertible, _StringConvertible where RawValue == String {} +@available(*, deprecated) extension _AutoLosslessStringConvertible { public init?(_ description: String) { self.init(rawValue: description) diff --git a/Sources/OpenAPIRuntime/Base/_StringConvertible.swift b/Sources/OpenAPIRuntime/Deprecated/Deprecated_StringConvertible.swift similarity index 84% rename from Sources/OpenAPIRuntime/Base/_StringConvertible.swift rename to Sources/OpenAPIRuntime/Deprecated/Deprecated_StringConvertible.swift index e680e822..69302270 100644 --- a/Sources/OpenAPIRuntime/Base/_StringConvertible.swift +++ b/Sources/OpenAPIRuntime/Deprecated/Deprecated_StringConvertible.swift @@ -18,12 +18,26 @@ import Foundation /// /// Cannot be marked as SPI, as it's added on public types, but should be /// considered an internal implementation detail of the generator. +@available(*, deprecated) public protocol _StringConvertible: LosslessStringConvertible {} +@available(*, deprecated) extension String: _StringConvertible {} + +@available(*, deprecated) extension Bool: _StringConvertible {} + +@available(*, deprecated) extension Int: _StringConvertible {} + +@available(*, deprecated) extension Int64: _StringConvertible {} + +@available(*, deprecated) extension Int32: _StringConvertible {} + +@available(*, deprecated) extension Float: _StringConvertible {} + +@available(*, deprecated) extension Double: _StringConvertible {} diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift index 370d44e9..11c44cbf 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Client.swift @@ -32,22 +32,23 @@ final class Test_ClientConverterExtensions: Test_Runtime { // MARK: Converter helper methods - // | client | set | request path | text | string-convertible | required | renderedRequestPath | - func test_renderedRequestPath_stringConvertible() throws { - let renderedPath = try converter.renderedRequestPath( - template: "/items/{}/detail/{}", + // | client | set | request path | URI | required | renderedPath | + func test_renderedPath_string() throws { + let renderedPath = try converter.renderedPath( + template: "/items/{}/detail/{}/habitats/{}", parameters: [ 1 as Int, "foo" as String, + [.land, .air] as [TestHabitat], ] ) - XCTAssertEqual(renderedPath, "/items/1/detail/foo") + XCTAssertEqual(renderedPath, "/items/1/detail/foo/habitats/land,air") } - // | client | set | request query | text | string-convertible | both | setQueryItemAsText | - func test_setQueryItemAsText_stringConvertible() throws { + // | client | set | request query | URI | both | setQueryItemAsURI | + func test_setQueryItemAsURI_string() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, @@ -57,9 +58,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=foo") } - func test_setQueryItemAsText_stringConvertible_needsEncoding() throws { + func test_setQueryItemAsURI_stringConvertible_needsEncoding() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, @@ -69,10 +70,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=h%25llo") } - // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | - func test_setQueryItemAsText_arrayOfStringConvertibles() throws { + func test_setQueryItemAsURI_arrayOfStrings() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, @@ -82,10 +82,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=foo&search=bar") } - // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | - func test_setQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { + func test_setQueryItemAsURI_arrayOfStrings_unexploded() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: false, @@ -95,36 +94,34 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(request.query, "search=foo,bar") } - // | client | set | request query | text | date | both | setQueryItemAsText | - func test_setQueryItemAsText_date() throws { + func test_setQueryItemAsURI_date() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, name: "search", value: testDate ) - XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z") + XCTAssertEqual(request.query, "search=2023-01-18T10%3A04%3A11Z") } - // | client | set | request query | text | array of dates | both | setQueryItemAsText | - func test_setQueryItemAsText_arrayOfDates() throws { + func test_setQueryItemAsURI_arrayOfDates() throws { var request = testRequest - try converter.setQueryItemAsText( + try converter.setQueryItemAsURI( in: &request, style: nil, explode: nil, name: "search", value: [testDate, testDate] ) - XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z&search=2023-01-18T10:04:11Z") + XCTAssertEqual(request.query, "search=2023-01-18T10%3A04%3A11Z&search=2023-01-18T10%3A04%3A11Z") } - // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | - func test_setOptionalRequestBodyAsText_stringConvertible() throws { + // | client | set | request body | string | optional | setOptionalRequestBodyAsString | + func test_setOptionalRequestBodyAsString_string() throws { var headerFields: [HeaderField] = [] - let body = try converter.setOptionalRequestBodyAsText( + let body = try converter.setOptionalRequestBodyAsString( testString, headerFields: &headerFields, contentType: "text/plain" @@ -138,10 +135,10 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | - func test_setRequiredRequestBodyAsText_stringConvertible() throws { + // | client | set | request body | string | required | setRequiredRequestBodyAsString | + func test_setRequiredRequestBodyAsString_string() throws { var headerFields: [HeaderField] = [] - let body = try converter.setRequiredRequestBodyAsText( + let body = try converter.setRequiredRequestBodyAsString( testString, headerFields: &headerFields, contentType: "text/plain" @@ -155,10 +152,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | - func test_setOptionalRequestBodyAsText_date() throws { + func test_setOptionalRequestBodyAsString_date() throws { var headerFields: [HeaderField] = [] - let body = try converter.setOptionalRequestBodyAsText( + let body = try converter.setOptionalRequestBodyAsString( testDate, headerFields: &headerFields, contentType: "text/plain" @@ -172,10 +168,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | - func test_setRequiredRequestBodyAsText_date() throws { + func test_setRequiredRequestBodyAsString_date() throws { var headerFields: [HeaderField] = [] - let body = try converter.setRequiredRequestBodyAsText( + let body = try converter.setRequiredRequestBodyAsString( testDate, headerFields: &headerFields, contentType: "text/plain" @@ -189,7 +184,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | JSON | codable | optional | setOptionalRequestBodyAsJSON | + // | client | set | request body | JSON | optional | setOptionalRequestBodyAsJSON | func test_setOptionalRequestBodyAsJSON_codable() throws { var headerFields: [HeaderField] = [] let body = try converter.setOptionalRequestBodyAsJSON( @@ -222,7 +217,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | JSON | codable | required | setRequiredRequestBodyAsJSON | + // | client | set | request body | JSON | required | setRequiredRequestBodyAsJSON | func test_setRequiredRequestBodyAsJSON_codable() throws { var headerFields: [HeaderField] = [] let body = try converter.setRequiredRequestBodyAsJSON( @@ -239,7 +234,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | binary | data | optional | setOptionalRequestBodyAsBinary | + // | client | set | request body | binary | optional | setOptionalRequestBodyAsBinary | func test_setOptionalRequestBodyAsBinary_data() throws { var headerFields: [HeaderField] = [] let body = try converter.setOptionalRequestBodyAsBinary( @@ -256,7 +251,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | set | request body | binary | data | required | setRequiredRequestBodyAsBinary | + // | client | set | request body | binary | required | setRequiredRequestBodyAsBinary | func test_setRequiredRequestBodyAsBinary_data() throws { var headerFields: [HeaderField] = [] let body = try converter.setRequiredRequestBodyAsBinary( @@ -273,9 +268,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { ) } - // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | - func test_getResponseBodyAsText_stringConvertible() throws { - let value: String = try converter.getResponseBodyAsText( + // | client | get | response body | string | required | getResponseBodyAsString | + func test_getResponseBodyAsString_stringConvertible() throws { + let value: String = try converter.getResponseBodyAsString( String.self, from: testStringData, transforming: { $0 } @@ -283,9 +278,9 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(value, testString) } - // | client | get | response body | text | date | required | getResponseBodyAsText | - func test_getResponseBodyAsText_date() throws { - let value: Date = try converter.getResponseBodyAsText( + // | client | get | response body | string | required | getResponseBodyAsString | + func test_getResponseBodyAsString_date() throws { + let value: Date = try converter.getResponseBodyAsString( Date.self, from: testDateStringData, transforming: { $0 } @@ -293,7 +288,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(value, testDate) } - // | client | get | response body | JSON | codable | required | getResponseBodyAsJSON | + // | client | get | response body | JSON | required | getResponseBodyAsJSON | func test_getResponseBodyAsJSON_codable() throws { let value: TestPet = try converter.getResponseBodyAsJSON( TestPet.self, @@ -303,7 +298,7 @@ final class Test_ClientConverterExtensions: Test_Runtime { XCTAssertEqual(value, testStruct) } - // | client | get | response body | binary | data | required | getResponseBodyAsBinary | + // | client | get | response body | binary | required | getResponseBodyAsBinary | func test_getResponseBodyAsBinary_data() throws { let value: Data = try converter.getResponseBodyAsBinary( Data.self, diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift index 27408e60..23c3d1bc 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Common.swift @@ -49,10 +49,10 @@ final class Test_CommonConverterExtensions: Test_Runtime { // MARK: Converter helper methods - // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_stringConvertible() throws { + // | common | set | header field | URI | both | setHeaderFieldAsURI | + func test_setHeaderFieldAsURI_string() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: "bar" @@ -65,10 +65,9 @@ final class Test_CommonConverterExtensions: Test_Runtime { ) } - // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_arrayOfStringConvertible() throws { + func test_setHeaderFieldAsURI_arrayOfStrings() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: ["bar", "baz"] as [String] @@ -76,16 +75,14 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual( headerFields, [ - .init(name: "foo", value: "bar"), - .init(name: "foo", value: "baz"), + .init(name: "foo", value: "bar,baz") ] ) } - // | common | set | header field | text | date | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_date() throws { + func test_setHeaderFieldAsURI_date() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: testDate @@ -93,15 +90,14 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual( headerFields, [ - .init(name: "foo", value: testDateString) + .init(name: "foo", value: testDateEscapedString) ] ) } - // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | - func test_setHeaderFieldAsText_arrayOfDates() throws { + func test_setHeaderFieldAsURI_arrayOfDates() throws { var headerFields: [HeaderField] = [] - try converter.setHeaderFieldAsText( + try converter.setHeaderFieldAsURI( in: &headerFields, name: "foo", value: [testDate, testDate] @@ -109,13 +105,27 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual( headerFields, [ - .init(name: "foo", value: testDateString), - .init(name: "foo", value: testDateString), + .init(name: "foo", value: "\(testDateEscapedString),\(testDateEscapedString)") ] ) } - // | common | set | header field | JSON | codable | both | setHeaderFieldAsJSON | + func test_setHeaderFieldAsURI_struct() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsURI( + in: &headerFields, + name: "foo", + value: testStruct + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: "name,Fluffz") + ] + ) + } + + // | common | set | header field | JSON | both | setHeaderFieldAsJSON | func test_setHeaderFieldAsJSON_codable() throws { var headerFields: [HeaderField] = [] try converter.setHeaderFieldAsJSON( @@ -146,12 +156,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { ) } - // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_stringConvertible() throws { + // | common | get | header field | URI | optional | getOptionalHeaderFieldAsURI | + func test_getOptionalHeaderFieldAsURI_string() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: "bar") ] - let value: String? = try converter.getOptionalHeaderFieldAsText( + let value: String? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: String.self @@ -159,12 +169,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, "bar") } - // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_stringConvertible() throws { + // | common | get | header field | URI | required | getRequiredHeaderFieldAsURI | + func test_getRequiredHeaderFieldAsURI_stringConvertible() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: "bar") ] - let value: String = try converter.getRequiredHeaderFieldAsText( + let value: String = try converter.getRequiredHeaderFieldAsURI( in: headerFields, name: "foo", as: String.self @@ -172,13 +182,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, "bar") } - // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_arrayOfStringConvertibles() throws { + func test_getOptionalHeaderFieldAsURI_arrayOfStrings_splitHeaders() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: "bar"), .init(name: "foo", value: "baz"), ] - let value: [String]? = try converter.getOptionalHeaderFieldAsText( + let value: [String]? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: [String].self @@ -186,13 +195,11 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["bar", "baz"]) } - // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_arrayOfStringConvertibles() throws { + func test_getOptionalHeaderFieldAsURI_arrayOfStrings_singleHeader() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: "bar"), - .init(name: "foo", value: "baz"), + .init(name: "foo", value: "bar,baz") ] - let value: [String] = try converter.getRequiredHeaderFieldAsText( + let value: [String]? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: [String].self @@ -200,12 +207,11 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["bar", "baz"]) } - // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_date() throws { + func test_getOptionalHeaderFieldAsURI_date() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString) + .init(name: "foo", value: testDateEscapedString) ] - let value: Date? = try converter.getOptionalHeaderFieldAsText( + let value: Date? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", as: Date.self @@ -213,26 +219,12 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, testDate) } - // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_date() throws { + func test_getRequiredHeaderFieldAsURI_arrayOfDates() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString) + .init(name: "foo", value: testDateString), // escaped + .init(name: "foo", value: testDateEscapedString), // unescaped ] - let value: Date = try converter.getRequiredHeaderFieldAsText( - in: headerFields, - name: "foo", - as: Date.self - ) - XCTAssertEqual(value, testDate) - } - - // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | - func test_getOptionalHeaderFieldAsText_arrayOfDates() throws { - let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString), - .init(name: "foo", value: testDateString), - ] - let value: [Date]? = try converter.getOptionalHeaderFieldAsText( + let value: [Date] = try converter.getRequiredHeaderFieldAsURI( in: headerFields, name: "foo", as: [Date].self @@ -240,21 +232,19 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, [testDate, testDate]) } - // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | - func test_getRequiredHeaderFieldAsText_arrayOfDates() throws { + func test_getOptionalHeaderFieldAsURI_struct() throws { let headerFields: [HeaderField] = [ - .init(name: "foo", value: testDateString), - .init(name: "foo", value: testDateString), + .init(name: "foo", value: "name,Sprinkles") ] - let value: [Date] = try converter.getRequiredHeaderFieldAsText( + let value: TestPet? = try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "foo", - as: [Date].self + as: TestPet.self ) - XCTAssertEqual(value, [testDate, testDate]) + XCTAssertEqual(value, .init(name: "Sprinkles")) } - // | common | get | header field | JSON | codable | optional | getOptionalHeaderFieldAsJSON | + // | common | get | header field | JSON | optional | getOptionalHeaderFieldAsJSON | func test_getOptionalHeaderFieldAsJSON_codable() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: testStructString) @@ -267,7 +257,7 @@ final class Test_CommonConverterExtensions: Test_Runtime { XCTAssertEqual(value, testStruct) } - // | common | get | header field | JSON | codable | required | getRequiredHeaderFieldAsJSON | + // | common | get | header field | JSON | required | getRequiredHeaderFieldAsJSON | func test_getRequiredHeaderFieldAsJSON_codable() throws { let headerFields: [HeaderField] = [ .init(name: "foo", value: testStructString) diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift index 8cd8bc5e..713a2232 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_Converter+Server.swift @@ -119,26 +119,43 @@ final class Test_ServerConverterExtensions: Test_Runtime { // MARK: Converter helper methods - // | server | get | request path | text | string-convertible | required | getPathParameterAsText | - func test_getPathParameterAsText_stringConvertible() throws { + // | server | get | request path | URI | required | getPathParameterAsURI | + func test_getPathParameterAsURI_various() throws { let path: [String: String] = [ - "foo": "bar" + "foo": "bar", + "number": "1", + "habitats": "land,air", ] - let value: String = try converter.getPathParameterAsText( - in: path, - name: "foo", - as: String.self - ) - XCTAssertEqual(value, "bar") + do { + let value = try converter.getPathParameterAsURI( + in: path, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + do { + let value = try converter.getPathParameterAsURI( + in: path, + name: "number", + as: Int.self + ) + XCTAssertEqual(value, 1) + } + do { + let value = try converter.getPathParameterAsURI( + in: path, + name: "habitats", + as: [TestHabitat].self + ) + XCTAssertEqual(value, [.land, .air]) + } } - // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_stringConvertible() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo") - ] - let value: String? = try converter.getOptionalQueryItemAsText( - in: query, + // | server | get | request query | URI | optional | getOptionalQueryItemAsURI | + func test_getOptionalQueryItemAsURI_string() throws { + let value: String? = try converter.getOptionalQueryItemAsURI( + in: "search=foo", style: nil, explode: nil, name: "search", @@ -147,13 +164,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, "foo") } - // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_stringConvertible() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo") - ] - let value: String = try converter.getRequiredQueryItemAsText( - in: query, + // | server | get | request query | URI | required | getRequiredQueryItemAsURI | + func test_getRequiredQueryItemAsURI_string() throws { + let value: String = try converter.getRequiredQueryItemAsURI( + in: "search=foo", style: nil, explode: nil, name: "search", @@ -162,13 +176,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, "foo") } - // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_arrayOfStringConvertibles() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo"), - .init(name: "search", value: "bar"), - ] - let value: [String]? = try converter.getOptionalQueryItemAsText( + func test_getOptionalQueryItemAsURI_arrayOfStrings() throws { + let query = "search=foo&search=bar" + let value: [String]? = try converter.getOptionalQueryItemAsURI( in: query, style: nil, explode: nil, @@ -178,13 +188,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["foo", "bar"]) } - // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_arrayOfStringConvertibles() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo"), - .init(name: "search", value: "bar"), - ] - let value: [String] = try converter.getRequiredQueryItemAsText( + func test_getRequiredQueryItemAsURI_arrayOfStrings() throws { + let query = "search=foo&search=bar" + let value: [String] = try converter.getRequiredQueryItemAsURI( in: query, style: nil, explode: nil, @@ -194,12 +200,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["foo", "bar"]) } - // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: "foo,bar") - ] - let value: [String] = try converter.getRequiredQueryItemAsText( + func test_getRequiredQueryItemAsURI_arrayOfStrings_unexploded() throws { + let query = "search=foo,bar" + let value: [String] = try converter.getRequiredQueryItemAsURI( in: query, style: nil, explode: false, @@ -209,27 +212,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, ["foo", "bar"]) } - // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_date() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString) - ] - let value: Date? = try converter.getOptionalQueryItemAsText( - in: query, - style: nil, - explode: nil, - name: "search", - as: Date.self - ) - XCTAssertEqual(value, testDate) - } - - // | server | get | request query | text | date | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_date() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString) - ] - let value: Date = try converter.getRequiredQueryItemAsText( + func test_getOptionalQueryItemAsURI_date() throws { + let query = "search=\(testDateEscapedString)" + let value: Date? = try converter.getOptionalQueryItemAsURI( in: query, style: nil, explode: nil, @@ -239,29 +224,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, testDate) } - // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | - func test_getOptionalQueryItemAsText_arrayOfDates() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString), - .init(name: "search", value: testDateString), - ] - let value: [Date]? = try converter.getOptionalQueryItemAsText( - in: query, - style: nil, - explode: nil, - name: "search", - as: [Date].self - ) - XCTAssertEqual(value, [testDate, testDate]) - } - - // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | - func test_getRequiredQueryItemAsText_arrayOfDates() throws { - let query: [URLQueryItem] = [ - .init(name: "search", value: testDateString), - .init(name: "search", value: testDateString), - ] - let value: [Date] = try converter.getRequiredQueryItemAsText( + func test_getRequiredQueryItemAsURI_arrayOfDates() throws { + let query = "search=\(testDateEscapedString)&search=\(testDateEscapedString)" + let value: [Date] = try converter.getRequiredQueryItemAsURI( in: query, style: nil, explode: nil, @@ -271,9 +236,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(value, [testDate, testDate]) } - // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | - func test_getOptionalRequestBodyAsText_stringConvertible() throws { - let body: String? = try converter.getOptionalRequestBodyAsText( + // | server | get | request body | string | optional | getOptionalRequestBodyAsString | + func test_getOptionalRequestBodyAsText_string() throws { + let body: String? = try converter.getOptionalRequestBodyAsString( String.self, from: testStringData, transforming: { $0 } @@ -281,9 +246,9 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testString) } - // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + // | server | get | request body | string | required | getRequiredRequestBodyAsString | func test_getRequiredRequestBodyAsText_stringConvertible() throws { - let body: String = try converter.getRequiredRequestBodyAsText( + let body: String = try converter.getRequiredRequestBodyAsString( String.self, from: testStringData, transforming: { $0 } @@ -291,19 +256,8 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testString) } - // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | - func test_getOptionalRequestBodyAsText_date() throws { - let body: Date? = try converter.getOptionalRequestBodyAsText( - Date.self, - from: testDateStringData, - transforming: { $0 } - ) - XCTAssertEqual(body, testDate) - } - - // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | func test_getRequiredRequestBodyAsText_date() throws { - let body: Date = try converter.getRequiredRequestBodyAsText( + let body: Date = try converter.getRequiredRequestBodyAsString( Date.self, from: testDateStringData, transforming: { $0 } @@ -311,7 +265,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testDate) } - // | server | get | request body | JSON | codable | optional | getOptionalRequestBodyAsJSON | + // | server | get | request body | JSON | optional | getOptionalRequestBodyAsJSON | func test_getOptionalRequestBodyAsJSON_codable() throws { let body: TestPet? = try converter.getOptionalRequestBodyAsJSON( TestPet.self, @@ -330,7 +284,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testString) } - // | server | get | request body | JSON | codable | required | getRequiredRequestBodyAsJSON | + // | server | get | request body | JSON | required | getRequiredRequestBodyAsJSON | func test_getRequiredRequestBodyAsJSON_codable() throws { let body: TestPet = try converter.getRequiredRequestBodyAsJSON( TestPet.self, @@ -340,7 +294,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testStruct) } - // | server | get | request body | binary | data | optional | getOptionalRequestBodyAsBinary | + // | server | get | request body | binary | optional | getOptionalRequestBodyAsBinary | func test_getOptionalRequestBodyAsBinary_data() throws { let body: Data? = try converter.getOptionalRequestBodyAsBinary( Data.self, @@ -350,7 +304,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testStringData) } - // | server | get | request body | binary | data | required | getRequiredRequestBodyAsBinary | + // | server | get | request body | binary | required | getRequiredRequestBodyAsBinary | func test_getRequiredRequestBodyAsBinary_data() throws { let body: Data = try converter.getRequiredRequestBodyAsBinary( Data.self, @@ -360,10 +314,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { XCTAssertEqual(body, testStringData) } - // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + // | server | set | response body | string | required | setResponseBodyAsString | func test_setResponseBodyAsText_stringConvertible() throws { var headers: [HeaderField] = [] - let data = try converter.setResponseBodyAsText( + let data = try converter.setResponseBodyAsString( testString, headerFields: &headers, contentType: "text/plain" @@ -377,10 +331,10 @@ final class Test_ServerConverterExtensions: Test_Runtime { ) } - // | server | set | response body | text | date | required | setResponseBodyAsText | + // | server | set | response body | string | required | setResponseBodyAsString | func test_setResponseBodyAsText_date() throws { var headers: [HeaderField] = [] - let data = try converter.setResponseBodyAsText( + let data = try converter.setResponseBodyAsString( testDate, headerFields: &headers, contentType: "text/plain" @@ -394,7 +348,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { ) } - // | server | set | response body | JSON | codable | required | setResponseBodyAsJSON | + // | server | set | response body | JSON | required | setResponseBodyAsJSON | func test_setResponseBodyAsJSON_codable() throws { var headers: [HeaderField] = [] let data = try converter.setResponseBodyAsJSON( @@ -411,7 +365,7 @@ final class Test_ServerConverterExtensions: Test_Runtime { ) } - // | server | set | response body | binary | data | required | setResponseBodyAsBinary | + // | server | set | response body | binary | required | setResponseBodyAsBinary | func test_setResponseBodyAsBinary_data() throws { var headers: [HeaderField] = [] let data = try converter.setResponseBodyAsBinary( diff --git a/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift b/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift index c5c4e68b..ef4e6bba 100644 --- a/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift +++ b/Tests/OpenAPIRuntimeTests/Conversion/Test_FoundationExtensions.swift @@ -16,25 +16,28 @@ import XCTest final class Test_FoundationExtensions: Test_Runtime { + @available(*, deprecated) func testURLComponents_addStringQueryItem() throws { var components = testComponents - components.addStringQueryItem(name: "key", value: "value", explode: true) + components.addUnescapedStringQueryItem(name: "key", value: "value", explode: true) XCTAssertEqualURLString(components.url, "/api?key=value") } + @available(*, deprecated) func testURLComponents_addStringQueryItems() throws { var components = testComponents - components.addStringQueryItem(name: "key2", value: "value3", explode: true) - components.addStringQueryItem(name: "key", value: "value1", explode: true) - components.addStringQueryItem(name: "key", value: "value2", explode: true) + components.addUnescapedStringQueryItem(name: "key2", value: "value3", explode: true) + components.addUnescapedStringQueryItem(name: "key", value: "value1", explode: true) + components.addUnescapedStringQueryItem(name: "key", value: "value2", explode: true) XCTAssertEqualURLString(components.url, "/api?key2=value3&key=value1&key=value2") } + @available(*, deprecated) func testURLComponents_addStringQueryItems_unexploded() throws { var components = testComponents - components.addStringQueryItem(name: "key2", value: "value3", explode: false) - components.addStringQueryItem(name: "key", value: "value1", explode: false) - components.addStringQueryItem(name: "key", value: "value2", explode: false) + components.addUnescapedStringQueryItem(name: "key2", value: "value3", explode: false) + components.addUnescapedStringQueryItem(name: "key", value: "value1", explode: false) + components.addUnescapedStringQueryItem(name: "key", value: "value2", explode: false) XCTAssertEqualURLString(components.url, "/api?key2=value3&key=value1,value2") } } diff --git a/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift index 4faafbd8..7eff20eb 100644 --- a/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift +++ b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated.swift @@ -375,4 +375,621 @@ final class Test_Deprecated: Test_Runtime { ) } + // | common | set | header field | text | string-convertible | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_stringConvertible() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: "bar" + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: "bar") + ] + ) + } + + // | common | set | header field | text | array of string-convertibles | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_arrayOfStringConvertible() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: ["bar", "baz"] as [String] + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: "bar"), + .init(name: "foo", value: "baz"), + ] + ) + } + + // | common | set | header field | text | date | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_date() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: testDate + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: testDateString) + ] + ) + } + + // | common | set | header field | text | array of dates | both | setHeaderFieldAsText | + @available(*, deprecated) + func test_setHeaderFieldAsText_arrayOfDates() throws { + var headerFields: [HeaderField] = [] + try converter.setHeaderFieldAsText( + in: &headerFields, + name: "foo", + value: [testDate, testDate] + ) + XCTAssertEqual( + headerFields, + [ + .init(name: "foo", value: testDateString), + .init(name: "foo", value: testDateString), + ] + ) + } + + // | common | get | header field | text | string-convertible | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_stringConvertible() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar") + ] + let value: String? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + + // | common | get | header field | text | string-convertible | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_stringConvertible() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar") + ] + let value: String = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + + // | common | get | header field | text | array of string-convertibles | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_arrayOfStringConvertibles() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar"), + .init(name: "foo", value: "baz"), + ] + let value: [String]? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [String].self + ) + XCTAssertEqual(value, ["bar", "baz"]) + } + + // | common | get | header field | text | array of string-convertibles | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_arrayOfStringConvertibles() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: "bar"), + .init(name: "foo", value: "baz"), + ] + let value: [String] = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [String].self + ) + XCTAssertEqual(value, ["bar", "baz"]) + } + + // | common | get | header field | text | date | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_date() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString) + ] + let value: Date? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | common | get | header field | text | date | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_date() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString) + ] + let value: Date = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | common | get | header field | text | array of dates | optional | getOptionalHeaderFieldAsText | + @available(*, deprecated) + func test_getOptionalHeaderFieldAsText_arrayOfDates() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString), + .init(name: "foo", value: testDateString), + ] + let value: [Date]? = try converter.getOptionalHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | common | get | header field | text | array of dates | required | getRequiredHeaderFieldAsText | + @available(*, deprecated) + func test_getRequiredHeaderFieldAsText_arrayOfDates() throws { + let headerFields: [HeaderField] = [ + .init(name: "foo", value: testDateString), + .init(name: "foo", value: testDateString), + ] + let value: [Date] = try converter.getRequiredHeaderFieldAsText( + in: headerFields, + name: "foo", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | client | set | request path | text | string-convertible | required | renderedRequestPath | + @available(*, deprecated) + func test_renderedRequestPath_stringConvertible() throws { + let renderedPath = try converter.renderedRequestPath( + template: "/items/{}/detail/{}", + parameters: [ + 1 as Int, + "foo" as String, + ] + ) + XCTAssertEqual(renderedPath, "/items/1/detail/foo") + } + + // | client | set | request query | text | string-convertible | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_stringConvertible() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: "foo" + ) + XCTAssertEqual(request.query, "search=foo") + } + + @available(*, deprecated) + func test_setQueryItemAsText_stringConvertible_needsEncoding() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: "h%llo" + ) + XCTAssertEqual(request.query, "search=h%25llo") + } + + // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_arrayOfStringConvertibles() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: ["foo", "bar"] + ) + XCTAssertEqual(request.query, "search=foo&search=bar") + } + + // | client | set | request query | text | array of string-convertibles | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: false, + name: "search", + value: ["foo", "bar"] + ) + XCTAssertEqual(request.query, "search=foo,bar") + } + + // | client | set | request query | text | date | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_date() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: testDate + ) + XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z") + } + + // | client | set | request query | text | array of dates | both | setQueryItemAsText | + @available(*, deprecated) + func test_setQueryItemAsText_arrayOfDates() throws { + var request = testRequest + try converter.setQueryItemAsText( + in: &request, + style: nil, + explode: nil, + name: "search", + value: [testDate, testDate] + ) + XCTAssertEqual(request.query, "search=2023-01-18T10:04:11Z&search=2023-01-18T10:04:11Z") + } + + // | client | set | request body | text | string-convertible | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + func test_setOptionalRequestBodyAsText_stringConvertible() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setOptionalRequestBodyAsText( + testString, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | set | request body | text | string-convertible | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + func test_setRequiredRequestBodyAsText_stringConvertible() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setRequiredRequestBodyAsText( + testString, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | set | request body | text | date | optional | setOptionalRequestBodyAsText | + @available(*, deprecated) + func test_setOptionalRequestBodyAsText_date() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setOptionalRequestBodyAsText( + testDate, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testDateStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | set | request body | text | date | required | setRequiredRequestBodyAsText | + @available(*, deprecated) + func test_setRequiredRequestBodyAsText_date() throws { + var headerFields: [HeaderField] = [] + let body = try converter.setRequiredRequestBodyAsText( + testDate, + headerFields: &headerFields, + contentType: "text/plain" + ) + XCTAssertEqual(body, testDateStringData) + XCTAssertEqual( + headerFields, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | client | get | response body | text | string-convertible | required | getResponseBodyAsText | + @available(*, deprecated) + func test_getResponseBodyAsText_stringConvertible() throws { + let value: String = try converter.getResponseBodyAsText( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(value, testString) + } + + // | client | get | response body | text | date | required | getResponseBodyAsText | + @available(*, deprecated) + func test_getResponseBodyAsText_date() throws { + let value: Date = try converter.getResponseBodyAsText( + Date.self, + from: testDateStringData, + transforming: { $0 } + ) + XCTAssertEqual(value, testDate) + } + + // | server | get | request path | text | string-convertible | required | getPathParameterAsText | + @available(*, deprecated) + func test_getPathParameterAsText_stringConvertible() throws { + let path: [String: String] = [ + "foo": "bar" + ] + let value: String = try converter.getPathParameterAsText( + in: path, + name: "foo", + as: String.self + ) + XCTAssertEqual(value, "bar") + } + + // | server | get | request query | text | string-convertible | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_stringConvertible() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo") + ] + let value: String? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: String.self + ) + XCTAssertEqual(value, "foo") + } + + // | server | get | request query | text | string-convertible | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_stringConvertible() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo") + ] + let value: String = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: String.self + ) + XCTAssertEqual(value, "foo") + } + + // | server | get | request query | text | array of string-convertibles | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_arrayOfStringConvertibles() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo"), + .init(name: "search", value: "bar"), + ] + let value: [String]? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [String].self + ) + XCTAssertEqual(value, ["foo", "bar"]) + } + + // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_arrayOfStringConvertibles() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo"), + .init(name: "search", value: "bar"), + ] + let value: [String] = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [String].self + ) + XCTAssertEqual(value, ["foo", "bar"]) + } + + // | server | get | request query | text | array of string-convertibles | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_arrayOfStringConvertibles_unexploded() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: "foo,bar") + ] + let value: [String] = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: false, + name: "search", + as: [String].self + ) + XCTAssertEqual(value, ["foo", "bar"]) + } + + // | server | get | request query | text | date | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_date() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString) + ] + let value: Date? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | server | get | request query | text | date | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_date() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString) + ] + let value: Date = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: Date.self + ) + XCTAssertEqual(value, testDate) + } + + // | server | get | request query | text | array of dates | optional | getOptionalQueryItemAsText | + @available(*, deprecated) + func test_getOptionalQueryItemAsText_arrayOfDates() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString), + .init(name: "search", value: testDateString), + ] + let value: [Date]? = try converter.getOptionalQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | server | get | request query | text | array of dates | required | getRequiredQueryItemAsText | + @available(*, deprecated) + func test_getRequiredQueryItemAsText_arrayOfDates() throws { + let query: [URLQueryItem] = [ + .init(name: "search", value: testDateString), + .init(name: "search", value: testDateString), + ] + let value: [Date] = try converter.getRequiredQueryItemAsText( + in: query, + style: nil, + explode: nil, + name: "search", + as: [Date].self + ) + XCTAssertEqual(value, [testDate, testDate]) + } + + // | server | get | request body | text | string-convertible | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + func test_getOptionalRequestBodyAsText_stringConvertible() throws { + let body: String? = try converter.getOptionalRequestBodyAsText( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } + + // | server | get | request body | text | string-convertible | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + func test_getRequiredRequestBodyAsText_stringConvertible() throws { + let body: String = try converter.getRequiredRequestBodyAsText( + String.self, + from: testStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testString) + } + + // | server | get | request body | text | date | optional | getOptionalRequestBodyAsText | + @available(*, deprecated) + func test_getOptionalRequestBodyAsText_date() throws { + let body: Date? = try converter.getOptionalRequestBodyAsText( + Date.self, + from: testDateStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testDate) + } + + // | server | get | request body | text | date | required | getRequiredRequestBodyAsText | + @available(*, deprecated) + func test_getRequiredRequestBodyAsText_date() throws { + let body: Date = try converter.getRequiredRequestBodyAsText( + Date.self, + from: testDateStringData, + transforming: { $0 } + ) + XCTAssertEqual(body, testDate) + } + + // | server | set | response body | text | string-convertible | required | setResponseBodyAsText | + @available(*, deprecated) + func test_setResponseBodyAsText_stringConvertible() throws { + var headers: [HeaderField] = [] + let data = try converter.setResponseBodyAsText( + testString, + headerFields: &headers, + contentType: "text/plain" + ) + XCTAssertEqual(data, testStringData) + XCTAssertEqual( + headers, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } + + // | server | set | response body | text | date | required | setResponseBodyAsText | + @available(*, deprecated) + func test_setResponseBodyAsText_date() throws { + var headers: [HeaderField] = [] + let data = try converter.setResponseBodyAsText( + testDate, + headerFields: &headers, + contentType: "text/plain" + ) + XCTAssertEqual(data, testDateStringData) + XCTAssertEqual( + headers, + [ + .init(name: "content-type", value: "text/plain") + ] + ) + } } diff --git a/Tests/OpenAPIRuntimeTests/Base/Test_StringConvertible.swift b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated_StringConvertible.swift similarity index 92% rename from Tests/OpenAPIRuntimeTests/Base/Test_StringConvertible.swift rename to Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated_StringConvertible.swift index 7eee5776..5e30740c 100644 --- a/Tests/OpenAPIRuntimeTests/Base/Test_StringConvertible.swift +++ b/Tests/OpenAPIRuntimeTests/Deprecated/Test_Deprecated_StringConvertible.swift @@ -14,8 +14,9 @@ import XCTest @_spi(Generated)@testable import OpenAPIRuntime -final class Test_StringConvertible: XCTestCase { +final class Test_Deprecated_StringConvertible: XCTestCase { + @available(*, deprecated) func testConformances() throws { let values: [(any _StringConvertible, String)] = [ ("hello" as String, "hello"), diff --git a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift index 496cca2b..155a45d7 100644 --- a/Tests/OpenAPIRuntimeTests/Test_Runtime.swift +++ b/Tests/OpenAPIRuntimeTests/Test_Runtime.swift @@ -54,10 +54,18 @@ class Test_Runtime: XCTestCase { "2023-01-18T10:04:11Z" } + var testDateEscapedString: String { + "2023-01-18T10%3A04%3A11Z" + } + var testDateStringData: Data { Data(testDateString.utf8) } + var testDateEscapedStringData: Data { + Data(testDateEscapedString.utf8) + } + var testString: String { "hello" } @@ -90,6 +98,14 @@ class Test_Runtime: XCTestCase { """# } + var testEnum: TestHabitat { + .water + } + + var testEnumString: String { + "water" + } + var testStructData: Data { Data(testStructString.utf8) } @@ -124,6 +140,12 @@ struct TestPet: Codable, Equatable { var name: String } +enum TestHabitat: String, Codable, Equatable { + case water + case land + case air +} + /// Injects an authentication header to every request. struct AuthenticationMiddleware: ClientMiddleware {