Skip to content

Codable conformance for URLComponents #1299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CoreFoundation/URL.subproj/CFURLComponents.c
Original file line number Diff line number Diff line change
Expand Up @@ -556,11 +556,11 @@ CF_EXPORT CFStringRef _CFURLComponentsCopyPath(CFURLComponentsRef components) {
components->_pathComponentValid = true;
}
if (!components->_pathComponent) {
result = CFSTR("");
result = CFStringCreateCopy(kCFAllocatorSystemDefault, CFSTR(""));
} else {
result = _CFStringCreateByRemovingPercentEncoding(kCFAllocatorSystemDefault, components->_pathComponent);
if (!result) {
result = CFSTR("");
result = CFStringCreateCopy(kCFAllocatorSystemDefault, CFSTR(""));
}
}
__CFUnlock(&components->_lock);
Expand Down Expand Up @@ -745,7 +745,7 @@ CF_EXPORT CFStringRef _CFURLComponentsCopyPercentEncodedPath(CFURLComponentsRef
result = components->_pathComponent ? CFRetain(components->_pathComponent) : NULL;
__CFUnlock(&components->_lock);

if (!result) result = CFSTR("");
if (!result) result = CFStringCreateCopy(kCFAllocatorSystemDefault, CFSTR(""));

return ( result );
}
Expand Down
45 changes: 41 additions & 4 deletions Foundation/URLComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ public struct URLComponents : ReferenceConvertible, Hashable, Equatable, _Mutabl
/// The getter for this property removes any percent encoding this component may have (if the component allows percent encoding). Setting this property assumes the subcomponent or component string is not percent encoded and will add percent encoding (if the component allows percent encoding).
public var path: String {
get {
guard let result = _handle.map({ $0.path }) else { return "" }
return result
return _handle.map { $0.path } ?? ""
}
set {
_applyMutation { $0.path = newValue }
Expand Down Expand Up @@ -161,8 +160,7 @@ public struct URLComponents : ReferenceConvertible, Hashable, Equatable, _Mutabl
/// The getter for this property retains any percent encoding this component may have. Setting this properties assumes the component string is already correctly percent encoded. Attempting to set an incorrectly percent encoded string will cause a `fatalError`. Although ';' is a legal path character, it is recommended that it be percent-encoded for best compatibility with `URL` (`String.addingPercentEncoding(withAllowedCharacters:)` will percent-encode any ';' characters if you pass `CharacterSet.urlPathAllowed`).
public var percentEncodedPath: String {
get {
guard let result = _handle.map({ $0.percentEncodedPath }) else { return "" }
return result
return _handle.map { $0.percentEncodedPath } ?? ""
}
set {
_applyMutation { $0.percentEncodedPath = newValue }
Expand Down Expand Up @@ -456,3 +454,42 @@ extension URLQueryItem : _ObjectTypeBridgeable {
return result!
}
}

extension URLComponents : Codable {
private enum CodingKeys : Int, CodingKey {
case scheme
case user
case password
case host
case port
case path
case query
case fragment
}

public init(from decoder: Decoder) throws {
self.init()

let container = try decoder.container(keyedBy: CodingKeys.self)
self.scheme = try container.decodeIfPresent(String.self, forKey: .scheme)
self.user = try container.decodeIfPresent(String.self, forKey: .user)
self.password = try container.decodeIfPresent(String.self, forKey: .password)
self.host = try container.decodeIfPresent(String.self, forKey: .host)
self.port = try container.decodeIfPresent(Int.self, forKey: .port)
self.path = try container.decode(String.self, forKey: .path)
self.query = try container.decodeIfPresent(String.self, forKey: .query)
self.fragment = try container.decodeIfPresent(String.self, forKey: .fragment)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.scheme, forKey: .scheme)
try container.encodeIfPresent(self.user, forKey: .user)
try container.encodeIfPresent(self.password, forKey: .password)
try container.encodeIfPresent(self.host, forKey: .host)
try container.encodeIfPresent(self.port, forKey: .port)
try container.encode(self.path, forKey: .path)
try container.encodeIfPresent(self.query, forKey: .query)
try container.encodeIfPresent(self.fragment, forKey: .fragment)
}
}
98 changes: 98 additions & 0 deletions TestFoundation/TestCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,103 @@ class TestCodable : XCTestCase {
XCTFail("\(error)")
}
}

// MARK: - URLComponents
lazy var urlComponentsValues: [URLComponents] = [
URLComponents(),

URLComponents(string: "http://swift.org")!,
URLComponents(string: "http://swift.org:80")!,
URLComponents(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!,
URLComponents(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!,

URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: false)!,
URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: false)!,
URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!, resolvingAgainstBaseURL: false)!,
URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: false)!,
URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: false)!,
URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: false)!,
URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: false)!,

URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: true)!,
URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: true)!,
URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!, resolvingAgainstBaseURL: true)!,
URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: true)!,
URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: true)!,
URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: true)!,
URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: true)!,

{
var components = URLComponents()
components.scheme = "https"
return components
}(),

{
var components = URLComponents()
components.user = "johnny"
return components
}(),

{
var components = URLComponents()
components.password = "apples"
return components
}(),

{
var components = URLComponents()
components.host = "0.0.0.0"
return components
}(),

{
var components = URLComponents()
components.port = 8080
return components
}(),

{
var components = URLComponents()
components.path = ".."
return components
}(),

{
var components = URLComponents()
components.query = "param1=hi&param2=there"
return components
}(),

{
var components = URLComponents()
components.fragment = "anchor"
return components
}(),

{
var components = URLComponents()
components.scheme = "ftp"
components.user = "johnny"
components.password = "apples"
components.host = "0.0.0.0"
components.port = 4242
components.path = "/some/file"
components.query = "utf8=✅"
components.fragment = "anchor"
return components
}()
]

func test_URLComponents_JSON() {
for (components) in urlComponentsValues {
do {
try expectRoundTripEqualityThroughJSON(for: components)
} catch let error {
XCTFail("\(error)")
}
}
}
}

extension TestCodable {
Expand All @@ -494,6 +591,7 @@ extension TestCodable {
("test_Calendar_JSON", test_Calendar_JSON),
("test_DateComponents_JSON", test_DateComponents_JSON),
("test_Measurement_JSON", test_Measurement_JSON),
("test_URLComponents_JSON", test_URLComponents_JSON),
]
}
}
43 changes: 42 additions & 1 deletion TestFoundation/TestURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,9 @@ class TestURLComponents : XCTestCase {
("test_port", test_portSetter),
("test_url", test_url),
("test_copy", test_copy),
("test_createURLWithComponents", test_createURLWithComponents)
("test_createURLWithComponents", test_createURLWithComponents),
("test_path", test_path),
("test_percentEncodedPath", test_percentEncodedPath),
]
}

Expand Down Expand Up @@ -617,4 +619,43 @@ class TestURLComponents : XCTestCase {
XCTAssertEqual(urlComponents.queryItems?.count, 4)
}

func test_path() {
let c1 = URLComponents()
XCTAssertEqual(c1.path, "")

let c2 = URLComponents(string: "http://swift.org")
XCTAssertEqual(c2?.path, "")

let c3 = URLComponents(string: "http://swift.org/")
XCTAssertEqual(c3?.path, "/")

let c4 = URLComponents(string: "http://swift.org/foo/bar")
XCTAssertEqual(c4?.path, "/foo/bar")

let c5 = URLComponents(string: "http://swift.org:80/foo/bar")
XCTAssertEqual(c5?.path, "/foo/bar")

let c6 = URLComponents(string: "http://swift.org:80/foo/b%20r")
XCTAssertEqual(c6?.path, "/foo/b r")
}

func test_percentEncodedPath() {
let c1 = URLComponents()
XCTAssertEqual(c1.percentEncodedPath, "")

let c2 = URLComponents(string: "http://swift.org")
XCTAssertEqual(c2?.percentEncodedPath, "")

let c3 = URLComponents(string: "http://swift.org/")
XCTAssertEqual(c3?.percentEncodedPath, "/")

let c4 = URLComponents(string: "http://swift.org/foo/bar")
XCTAssertEqual(c4?.percentEncodedPath, "/foo/bar")

let c5 = URLComponents(string: "http://swift.org:80/foo/bar")
XCTAssertEqual(c5?.percentEncodedPath, "/foo/bar")

let c6 = URLComponents(string: "http://swift.org:80/foo/b%20r")
XCTAssertEqual(c6?.percentEncodedPath, "/foo/b%20r")
}
}