Skip to content

Commit bc8b663

Browse files
authored
Merge pull request #92 from mattpolzin/fixing-urls-for-non-foundation-coders
Fixing urls for non foundation coders
2 parents 201586d + 1230d9c commit bc8b663

11 files changed

+105
-72
lines changed

Sources/OpenAPIKit/Document/DocumentInfo.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ extension OpenAPI.Document.Info.License: Encodable {
125125
var container = encoder.container(keyedBy: CodingKeys.self)
126126

127127
try container.encode(name, forKey: .name)
128-
try container.encodeIfPresent(url, forKey: .url)
128+
try container.encodeIfPresent(url?.absoluteString, forKey: .url)
129129

130130
try encodeExtensions(to: &container)
131131
}
@@ -137,7 +137,7 @@ extension OpenAPI.Document.Info.License: Decodable {
137137

138138
name = try container.decode(String.self, forKey: .name)
139139

140-
url = try container.decodeIfPresent(URL.self, forKey: .url)
140+
url = try container.decodeURLAsStringIfPresent(forKey: .url)
141141

142142
vendorExtensions = try Self.extensions(from: decoder)
143143
}
@@ -190,7 +190,7 @@ extension OpenAPI.Document.Info.Contact: Encodable {
190190
var container = encoder.container(keyedBy: CodingKeys.self)
191191

192192
try container.encodeIfPresent(name, forKey: .name)
193-
try container.encodeIfPresent(url, forKey: .url)
193+
try container.encodeIfPresent(url?.absoluteString, forKey: .url)
194194
try container.encodeIfPresent(email, forKey: .email)
195195

196196
try encodeExtensions(to: &container)
@@ -202,7 +202,7 @@ extension OpenAPI.Document.Info.Contact: Decodable {
202202
let container = try decoder.container(keyedBy: CodingKeys.self)
203203

204204
name = try container.decodeIfPresent(String.self, forKey: .name)
205-
url = try container.decodeIfPresent(URL.self, forKey: .url)
205+
url = try container.decodeURLAsStringIfPresent(forKey: .url)
206206
email = try container.decodeIfPresent(String.self, forKey: .email)
207207

208208
vendorExtensions = try Self.extensions(from: decoder)
@@ -263,7 +263,7 @@ extension OpenAPI.Document.Info: Encodable {
263263

264264
try container.encode(title, forKey: .title)
265265
try container.encodeIfPresent(description, forKey: .description)
266-
try container.encodeIfPresent(termsOfService, forKey: .termsOfService)
266+
try container.encodeIfPresent(termsOfService?.absoluteString, forKey: .termsOfService)
267267
try container.encodeIfPresent(contact, forKey: .contact)
268268
try container.encodeIfPresent(license, forKey: .license)
269269
try container.encode(version, forKey: .version)
@@ -278,7 +278,7 @@ extension OpenAPI.Document.Info: Decodable {
278278

279279
title = try container.decode(String.self, forKey: .title)
280280
description = try container.decodeIfPresent(String.self, forKey: .description)
281-
termsOfService = try container.decodeIfPresent(URL.self, forKey: .termsOfService)
281+
termsOfService = try container.decodeURLAsStringIfPresent(forKey: .termsOfService)
282282
contact = try container.decodeIfPresent(Contact.self, forKey: .contact)
283283
license = try container.decodeIfPresent(License.self, forKey: .license)
284284
version = try container.decode(String.self, forKey: .version)

Sources/OpenAPIKit/ExternalDocumentation.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension OpenAPI.ExternalDocumentation: Encodable {
4141
var container = encoder.container(keyedBy: CodingKeys.self)
4242

4343
try container.encodeIfPresent(description, forKey: .description)
44-
try container.encode(url, forKey: .url)
44+
try container.encode(url.absoluteString, forKey: .url)
4545

4646
try encodeExtensions(to: &container)
4747
}
@@ -52,7 +52,7 @@ extension OpenAPI.ExternalDocumentation: Decodable {
5252
let container = try decoder.container(keyedBy: CodingKeys.self)
5353

5454
description = try container.decodeIfPresent(String.self, forKey: .description)
55-
url = try container.decode(URL.self, forKey: .url)
55+
url = try container.decodeURLAsString(forKey: .url)
5656

5757
vendorExtensions = try Self.extensions(from: decoder)
5858
}

Sources/OpenAPIKit/Security/OAuthFlows.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ extension OpenAPI.OAuthFlows.CommonFields: Encodable {
171171
public func encode(to encoder: Encoder) throws {
172172
var container = encoder.container(keyedBy: CodingKeys.self)
173173

174-
try container.encodeIfPresent(refreshUrl, forKey: .refreshUrl)
174+
try container.encodeIfPresent(refreshUrl?.absoluteString, forKey: .refreshUrl)
175175
try container.encode(scopes, forKey: .scopes)
176176
}
177177
}
@@ -180,7 +180,7 @@ extension OpenAPI.OAuthFlows.CommonFields: Decodable {
180180
public init(from decoder: Decoder) throws {
181181
let container = try decoder.container(keyedBy: CodingKeys.self)
182182

183-
refreshUrl = try container.decodeIfPresent(URL.self, forKey: .refreshUrl)
183+
refreshUrl = try container.decodeURLAsStringIfPresent(forKey: .refreshUrl)
184184
scopes = try container.decode(OrderedDictionary<OpenAPI.OAuthFlows.Scope, OpenAPI.OAuthFlows.ScopeDescription>.self, forKey: .scopes)
185185
}
186186
}
@@ -191,7 +191,7 @@ extension OpenAPI.OAuthFlows.Implicit: Encodable {
191191

192192
var container = encoder.container(keyedBy: CodingKeys.self)
193193

194-
try container.encode(authorizationUrl, forKey: .authorizationUrl)
194+
try container.encode(authorizationUrl.absoluteString, forKey: .authorizationUrl)
195195
}
196196
}
197197

@@ -201,7 +201,7 @@ extension OpenAPI.OAuthFlows.Implicit: Decodable {
201201

202202
let container = try decoder.container(keyedBy: CodingKeys.self)
203203

204-
authorizationUrl = try container.decode(URL.self, forKey: .authorizationUrl)
204+
authorizationUrl = try container.decodeURLAsString(forKey: .authorizationUrl)
205205
}
206206
}
207207

@@ -211,7 +211,7 @@ extension OpenAPI.OAuthFlows.Password: Encodable {
211211

212212
var container = encoder.container(keyedBy: CodingKeys.self)
213213

214-
try container.encode(tokenUrl, forKey: .tokenUrl)
214+
try container.encode(tokenUrl.absoluteString, forKey: .tokenUrl)
215215
}
216216
}
217217

@@ -221,7 +221,7 @@ extension OpenAPI.OAuthFlows.Password: Decodable {
221221

222222
let container = try decoder.container(keyedBy: CodingKeys.self)
223223

224-
tokenUrl = try container.decode(URL.self, forKey: .tokenUrl)
224+
tokenUrl = try container.decodeURLAsString(forKey: .tokenUrl)
225225
}
226226
}
227227

@@ -231,7 +231,7 @@ extension OpenAPI.OAuthFlows.ClientCredentials: Encodable {
231231

232232
var container = encoder.container(keyedBy: CodingKeys.self)
233233

234-
try container.encode(tokenUrl, forKey: .tokenUrl)
234+
try container.encode(tokenUrl.absoluteString, forKey: .tokenUrl)
235235
}
236236
}
237237

@@ -241,7 +241,7 @@ extension OpenAPI.OAuthFlows.ClientCredentials: Decodable {
241241

242242
let container = try decoder.container(keyedBy: CodingKeys.self)
243243

244-
tokenUrl = try container.decode(URL.self, forKey: .tokenUrl)
244+
tokenUrl = try container.decodeURLAsString(forKey: .tokenUrl)
245245
}
246246
}
247247

@@ -251,8 +251,8 @@ extension OpenAPI.OAuthFlows.AuthorizationCode: Encodable {
251251

252252
var container = encoder.container(keyedBy: CodingKeys.self)
253253

254-
try container.encode(tokenUrl, forKey: .tokenUrl)
255-
try container.encode(authorizationUrl, forKey: .authorizationUrl)
254+
try container.encode(tokenUrl.absoluteString, forKey: .tokenUrl)
255+
try container.encode(authorizationUrl.absoluteString, forKey: .authorizationUrl)
256256
}
257257
}
258258

@@ -262,7 +262,7 @@ extension OpenAPI.OAuthFlows.AuthorizationCode: Decodable {
262262

263263
let container = try decoder.container(keyedBy: CodingKeys.self)
264264

265-
tokenUrl = try container.decode(URL.self, forKey: .tokenUrl)
266-
authorizationUrl = try container.decode(URL.self, forKey: .authorizationUrl)
265+
tokenUrl = try container.decodeURLAsString(forKey: .tokenUrl)
266+
authorizationUrl = try container.decodeURLAsString(forKey: .authorizationUrl)
267267
}
268268
}

Sources/OpenAPIKit/Security/SecurityScheme.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ extension OpenAPI.SecurityScheme: Encodable {
103103
try container.encodeIfPresent(bearerFormat, forKey: .bearerFormat)
104104
case .openIdConnect(openIdConnectUrl: let url):
105105
try container.encode(SecurityType.Name.openIdConnect, forKey: .type)
106-
try container.encode(url, forKey: .openIdConnectUrl)
106+
try container.encode(url.absoluteString, forKey: .openIdConnectUrl)
107107
case .oauth2(flows: let flows):
108108
try container.encode(SecurityType.Name.oauth2, forKey: .type)
109109
try container.encode(flows, forKey: .flows)
@@ -140,7 +140,7 @@ extension OpenAPI.SecurityScheme: Decodable {
140140
)
141141
case .openIdConnect:
142142
type = .openIdConnect(
143-
openIdConnectUrl: try container.decode(URL.self, forKey: .openIdConnectUrl)
143+
openIdConnectUrl: try container.decodeURLAsString(forKey: .openIdConnectUrl)
144144
)
145145
}
146146

Sources/OpenAPIKit/Server.swift

+13-13
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,11 @@ extension OpenAPI.Server {
7070
}
7171

7272
// MARK: - Codable
73-
extension OpenAPI.Server: Decodable {
74-
public init(from decoder: Decoder) throws {
75-
let container = try decoder.container(keyedBy: CodingKeys.self)
76-
77-
url = try container.decode(URL.self, forKey: .url)
78-
description = try container.decodeIfPresent(String.self, forKey: .description)
79-
variables = try container.decodeIfPresent(OrderedDictionary<String, Variable>.self, forKey: .variables) ?? [:]
80-
81-
vendorExtensions = try Self.extensions(from: decoder)
82-
}
83-
}
84-
8573
extension OpenAPI.Server: Encodable {
8674
public func encode(to encoder: Encoder) throws {
8775
var container = encoder.container(keyedBy: CodingKeys.self)
8876

89-
try container.encode(url, forKey: .url)
77+
try container.encode(url.absoluteString, forKey: .url)
9078

9179
try container.encodeIfPresent(description, forKey: .description)
9280

@@ -98,6 +86,18 @@ extension OpenAPI.Server: Encodable {
9886
}
9987
}
10088

89+
extension OpenAPI.Server: Decodable {
90+
public init(from decoder: Decoder) throws {
91+
let container = try decoder.container(keyedBy: CodingKeys.self)
92+
93+
url = try container.decodeURLAsString(forKey: .url)
94+
description = try container.decodeIfPresent(String.self, forKey: .description)
95+
variables = try container.decodeIfPresent(OrderedDictionary<String, Variable>.self, forKey: .variables) ?? [:]
96+
97+
vendorExtensions = try Self.extensions(from: decoder)
98+
}
99+
}
100+
101101
extension OpenAPI.Server {
102102
internal enum CodingKeys: ExtendableCodingKey {
103103
case url
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// Container+DecodeURLAsString.swift
3+
//
4+
//
5+
// Created by Mathew Polzin on 7/5/20.
6+
//
7+
8+
import Foundation
9+
10+
extension KeyedDecodingContainerProtocol {
11+
internal func decodeURLAsString(forKey key: Self.Key) throws -> URL {
12+
let string = try decode(String.self, forKey: key)
13+
guard let url = URL(string: string) else {
14+
throw InconsistencyError(
15+
subjectName: key.stringValue,
16+
details: "If specified, must be a valid URL",
17+
codingPath: codingPath
18+
)
19+
}
20+
return url
21+
}
22+
23+
internal func decodeURLAsStringIfPresent(forKey key: Self.Key) throws -> URL? {
24+
guard let string = try decodeIfPresent(String.self, forKey: key) else {
25+
return nil
26+
}
27+
28+
guard let url = URL(string: string) else {
29+
throw InconsistencyError(
30+
subjectName: key.stringValue,
31+
details: "If specified, must be a valid URL",
32+
codingPath: codingPath
33+
)
34+
}
35+
return url
36+
}
37+
}

Sources/OpenAPIKit/XML.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension OpenAPI.XML: Encodable {
5050
var container = encoder.container(keyedBy: CodingKeys.self)
5151

5252
try container.encodeIfPresent(name, forKey: .name)
53-
try container.encodeIfPresent(namespace, forKey: .namespace)
53+
try container.encodeIfPresent(namespace?.absoluteString, forKey: .namespace)
5454
try container.encodeIfPresent(prefix, forKey: .prefix)
5555
if attribute {
5656
try container.encode(true, forKey: .attribute)
@@ -66,7 +66,7 @@ extension OpenAPI.XML: Decodable {
6666
let container = try decoder.container(keyedBy: CodingKeys.self)
6767

6868
name = try container.decodeIfPresent(String.self, forKey: .name)
69-
namespace = try container.decodeIfPresent(URL.self, forKey: .namespace)
69+
namespace = try container.decodeURLAsStringIfPresent(forKey: .namespace)
7070
prefix = try container.decodeIfPresent(String.self, forKey: .prefix)
7171
attribute = try container.decodeIfPresent(Bool.self, forKey: .attribute) ?? false
7272
wrapped = try container.decodeIfPresent(Bool.self, forKey: .wrapped) ?? false

Tests/OpenAPIKitCompatibilitySuite/GoogleBooksAPITests.swift

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ final class GoogleBooksAPICampatibilityTests: XCTestCase {
5555
// contact name is Google
5656
XCTAssertEqual(apiDoc.info.contact?.name, "Google")
5757

58+
// contact URL was parsed as google.com
59+
XCTAssertEqual(apiDoc.info.contact?.url, URL(string: "https://google.com")!)
60+
5861
// no contact email is provided
5962
XCTAssert(apiDoc.info.contact?.email?.isEmpty ?? true)
6063

Tests/OpenAPIKitTests/Document/DocumentInfoTests.swift

+14
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,20 @@ extension DocumentInfoTests {
387387
)
388388
}
389389

390+
func test_info_withTOS_decode_fails() {
391+
let infoData =
392+
"""
393+
{
394+
"termsOfService" : "#$%^&*",
395+
"title" : "title",
396+
"version" : "1.0"
397+
}
398+
""".data(using: .utf8)!
399+
XCTAssertThrowsError( try testDecoder.decode(OpenAPI.Document.Info.self, from: infoData)) { error in
400+
XCTAssertEqual(OpenAPI.Error(from: error).localizedDescription, "Inconsistency encountered when parsing `termsOfService`: If specified, must be a valid URL.")
401+
}
402+
}
403+
390404
func test_info_withContact_encode() throws {
391405
let info = OpenAPI.Document.Info(
392406
title: "title",

Tests/OpenAPIKitTests/ExternalDocumentationTests.swift

+14-2
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,27 @@ extension ExternalDocumentationTests {
106106
)
107107
}
108108

109-
func test_onlyUrl_decode() {
109+
func test_onlyUrl_decode() throws {
110110
let externalDocsData =
111111
"""
112112
{
113113
"url" : "http:\\/\\/google.com"
114114
}
115115
""".data(using: .utf8)!
116-
let externalDocs = try! testDecoder.decode(OpenAPI.ExternalDocumentation.self, from: externalDocsData)
116+
let externalDocs = try testDecoder.decode(OpenAPI.ExternalDocumentation.self, from: externalDocsData)
117117

118118
XCTAssertEqual(externalDocs, OpenAPI.ExternalDocumentation(url: URL(string: "http://google.com")!))
119119
}
120+
121+
func test_onlyUrl_decode_fails() {
122+
let externalDocsData =
123+
"""
124+
{
125+
"url" : "#$^&*^#$^"
126+
}
127+
""".data(using: .utf8)!
128+
XCTAssertThrowsError(try testDecoder.decode(OpenAPI.ExternalDocumentation.self, from: externalDocsData)) { error in
129+
XCTAssertEqual(OpenAPI.Error(from: error).localizedDescription, "Inconsistency encountered when parsing `url`: If specified, must be a valid URL.")
130+
}
131+
}
120132
}

Tests/OpenAPIKitTests/Validator/ValidatorTests.swift

-33
Original file line numberDiff line numberDiff line change
@@ -351,39 +351,6 @@ final class ValidatorTests: XCTestCase {
351351
}
352352
}
353353

354-
func test_failsParentURL() {
355-
let server = OpenAPI.Server(
356-
url: URL(string: "https://google.com")!,
357-
description: "hello world",
358-
variables: [:],
359-
vendorExtensions: [
360-
"x-string": "hello",
361-
"x-int": 2244,
362-
"x-double": 10.5,
363-
"x-dict": [ "string": "world"],
364-
"x-array": AnyCodable(["hello", nil, "world"])
365-
]
366-
)
367-
368-
let document = OpenAPI.Document(
369-
info: .init(title: "hello", version: "1.0"),
370-
servers: [server],
371-
paths: [:],
372-
components: .noComponents
373-
)
374-
375-
let validator = Validator()
376-
.validating { (context: ValidationContext<URL>) in
377-
[ ValidationError(reason: "just because", at: context.codingPath) ]
378-
}
379-
380-
XCTAssertThrowsError(try document.validate(using: validator)) { error in
381-
let error = error as? ValidationErrors
382-
XCTAssertEqual(error?.values.count, 1)
383-
XCTAssertEqual(error?.values.first?.reason, "just because")
384-
}
385-
}
386-
387354
func test_failsNestedString() {
388355
let server = OpenAPI.Server(
389356
url: URL(string: "https://google.com")!,

0 commit comments

Comments
 (0)