Skip to content

Commit 24ac050

Browse files
authored
Merge pull request #1299 from bubski/codable-urlcomponents
2 parents f4e4c92 + 9c70c74 commit 24ac050

File tree

4 files changed

+184
-8
lines changed

4 files changed

+184
-8
lines changed

CoreFoundation/URL.subproj/CFURLComponents.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,11 @@ CF_EXPORT CFStringRef _CFURLComponentsCopyPath(CFURLComponentsRef components) {
556556
components->_pathComponentValid = true;
557557
}
558558
if (!components->_pathComponent) {
559-
result = CFSTR("");
559+
result = CFStringCreateCopy(kCFAllocatorSystemDefault, CFSTR(""));
560560
} else {
561561
result = _CFStringCreateByRemovingPercentEncoding(kCFAllocatorSystemDefault, components->_pathComponent);
562562
if (!result) {
563-
result = CFSTR("");
563+
result = CFStringCreateCopy(kCFAllocatorSystemDefault, CFSTR(""));
564564
}
565565
}
566566
__CFUnlock(&components->_lock);
@@ -745,7 +745,7 @@ CF_EXPORT CFStringRef _CFURLComponentsCopyPercentEncodedPath(CFURLComponentsRef
745745
result = components->_pathComponent ? CFRetain(components->_pathComponent) : NULL;
746746
__CFUnlock(&components->_lock);
747747

748-
if (!result) result = CFSTR("");
748+
if (!result) result = CFStringCreateCopy(kCFAllocatorSystemDefault, CFSTR(""));
749749

750750
return ( result );
751751
}

Foundation/URLComponents.swift

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ public struct URLComponents : ReferenceConvertible, Hashable, Equatable, _Mutabl
107107
/// 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).
108108
public var path: String {
109109
get {
110-
guard let result = _handle.map({ $0.path }) else { return "" }
111-
return result
110+
return _handle.map { $0.path } ?? ""
112111
}
113112
set {
114113
_applyMutation { $0.path = newValue }
@@ -161,8 +160,7 @@ public struct URLComponents : ReferenceConvertible, Hashable, Equatable, _Mutabl
161160
/// 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`).
162161
public var percentEncodedPath: String {
163162
get {
164-
guard let result = _handle.map({ $0.percentEncodedPath }) else { return "" }
165-
return result
163+
return _handle.map { $0.percentEncodedPath } ?? ""
166164
}
167165
set {
168166
_applyMutation { $0.percentEncodedPath = newValue }
@@ -456,3 +454,42 @@ extension URLQueryItem : _ObjectTypeBridgeable {
456454
return result!
457455
}
458456
}
457+
458+
extension URLComponents : Codable {
459+
private enum CodingKeys : Int, CodingKey {
460+
case scheme
461+
case user
462+
case password
463+
case host
464+
case port
465+
case path
466+
case query
467+
case fragment
468+
}
469+
470+
public init(from decoder: Decoder) throws {
471+
self.init()
472+
473+
let container = try decoder.container(keyedBy: CodingKeys.self)
474+
self.scheme = try container.decodeIfPresent(String.self, forKey: .scheme)
475+
self.user = try container.decodeIfPresent(String.self, forKey: .user)
476+
self.password = try container.decodeIfPresent(String.self, forKey: .password)
477+
self.host = try container.decodeIfPresent(String.self, forKey: .host)
478+
self.port = try container.decodeIfPresent(Int.self, forKey: .port)
479+
self.path = try container.decode(String.self, forKey: .path)
480+
self.query = try container.decodeIfPresent(String.self, forKey: .query)
481+
self.fragment = try container.decodeIfPresent(String.self, forKey: .fragment)
482+
}
483+
484+
public func encode(to encoder: Encoder) throws {
485+
var container = encoder.container(keyedBy: CodingKeys.self)
486+
try container.encodeIfPresent(self.scheme, forKey: .scheme)
487+
try container.encodeIfPresent(self.user, forKey: .user)
488+
try container.encodeIfPresent(self.password, forKey: .password)
489+
try container.encodeIfPresent(self.host, forKey: .host)
490+
try container.encodeIfPresent(self.port, forKey: .port)
491+
try container.encode(self.path, forKey: .path)
492+
try container.encodeIfPresent(self.query, forKey: .query)
493+
try container.encodeIfPresent(self.fragment, forKey: .fragment)
494+
}
495+
}

TestFoundation/TestCodable.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,103 @@ class TestCodable : XCTestCase {
472472
XCTFail("\(error)")
473473
}
474474
}
475+
476+
// MARK: - URLComponents
477+
lazy var urlComponentsValues: [URLComponents] = [
478+
URLComponents(),
479+
480+
URLComponents(string: "http://swift.org")!,
481+
URLComponents(string: "http://swift.org:80")!,
482+
URLComponents(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!,
483+
URLComponents(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!,
484+
485+
URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: false)!,
486+
URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: false)!,
487+
URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!, resolvingAgainstBaseURL: false)!,
488+
URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: false)!,
489+
URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: false)!,
490+
URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: false)!,
491+
URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: false)!,
492+
493+
URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: true)!,
494+
URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: true)!,
495+
URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!, resolvingAgainstBaseURL: true)!,
496+
URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: true)!,
497+
URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: true)!,
498+
URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: true)!,
499+
URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: true)!,
500+
501+
{
502+
var components = URLComponents()
503+
components.scheme = "https"
504+
return components
505+
}(),
506+
507+
{
508+
var components = URLComponents()
509+
components.user = "johnny"
510+
return components
511+
}(),
512+
513+
{
514+
var components = URLComponents()
515+
components.password = "apples"
516+
return components
517+
}(),
518+
519+
{
520+
var components = URLComponents()
521+
components.host = "0.0.0.0"
522+
return components
523+
}(),
524+
525+
{
526+
var components = URLComponents()
527+
components.port = 8080
528+
return components
529+
}(),
530+
531+
{
532+
var components = URLComponents()
533+
components.path = ".."
534+
return components
535+
}(),
536+
537+
{
538+
var components = URLComponents()
539+
components.query = "param1=hi&param2=there"
540+
return components
541+
}(),
542+
543+
{
544+
var components = URLComponents()
545+
components.fragment = "anchor"
546+
return components
547+
}(),
548+
549+
{
550+
var components = URLComponents()
551+
components.scheme = "ftp"
552+
components.user = "johnny"
553+
components.password = "apples"
554+
components.host = "0.0.0.0"
555+
components.port = 4242
556+
components.path = "/some/file"
557+
components.query = "utf8=✅"
558+
components.fragment = "anchor"
559+
return components
560+
}()
561+
]
562+
563+
func test_URLComponents_JSON() {
564+
for (components) in urlComponentsValues {
565+
do {
566+
try expectRoundTripEqualityThroughJSON(for: components)
567+
} catch let error {
568+
XCTFail("\(error)")
569+
}
570+
}
571+
}
475572
}
476573

477574
extension TestCodable {
@@ -494,6 +591,7 @@ extension TestCodable {
494591
("test_Calendar_JSON", test_Calendar_JSON),
495592
("test_DateComponents_JSON", test_DateComponents_JSON),
496593
("test_Measurement_JSON", test_Measurement_JSON),
594+
("test_URLComponents_JSON", test_URLComponents_JSON),
497595
]
498596
}
499597
}

TestFoundation/TestURL.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,9 @@ class TestURLComponents : XCTestCase {
523523
("test_port", test_portSetter),
524524
("test_url", test_url),
525525
("test_copy", test_copy),
526-
("test_createURLWithComponents", test_createURLWithComponents)
526+
("test_createURLWithComponents", test_createURLWithComponents),
527+
("test_path", test_path),
528+
("test_percentEncodedPath", test_percentEncodedPath),
527529
]
528530
}
529531

@@ -617,4 +619,43 @@ class TestURLComponents : XCTestCase {
617619
XCTAssertEqual(urlComponents.queryItems?.count, 4)
618620
}
619621

622+
func test_path() {
623+
let c1 = URLComponents()
624+
XCTAssertEqual(c1.path, "")
625+
626+
let c2 = URLComponents(string: "http://swift.org")
627+
XCTAssertEqual(c2?.path, "")
628+
629+
let c3 = URLComponents(string: "http://swift.org/")
630+
XCTAssertEqual(c3?.path, "/")
631+
632+
let c4 = URLComponents(string: "http://swift.org/foo/bar")
633+
XCTAssertEqual(c4?.path, "/foo/bar")
634+
635+
let c5 = URLComponents(string: "http://swift.org:80/foo/bar")
636+
XCTAssertEqual(c5?.path, "/foo/bar")
637+
638+
let c6 = URLComponents(string: "http://swift.org:80/foo/b%20r")
639+
XCTAssertEqual(c6?.path, "/foo/b r")
640+
}
641+
642+
func test_percentEncodedPath() {
643+
let c1 = URLComponents()
644+
XCTAssertEqual(c1.percentEncodedPath, "")
645+
646+
let c2 = URLComponents(string: "http://swift.org")
647+
XCTAssertEqual(c2?.percentEncodedPath, "")
648+
649+
let c3 = URLComponents(string: "http://swift.org/")
650+
XCTAssertEqual(c3?.percentEncodedPath, "/")
651+
652+
let c4 = URLComponents(string: "http://swift.org/foo/bar")
653+
XCTAssertEqual(c4?.percentEncodedPath, "/foo/bar")
654+
655+
let c5 = URLComponents(string: "http://swift.org:80/foo/bar")
656+
XCTAssertEqual(c5?.percentEncodedPath, "/foo/bar")
657+
658+
let c6 = URLComponents(string: "http://swift.org:80/foo/b%20r")
659+
XCTAssertEqual(c6?.percentEncodedPath, "/foo/b%20r")
660+
}
620661
}

0 commit comments

Comments
 (0)