diff --git a/Sources/JSONAPI/Meta/Links.swift b/Sources/JSONAPI/Meta/Links.swift new file mode 100644 index 0000000..8c91f16 --- /dev/null +++ b/Sources/JSONAPI/Meta/Links.swift @@ -0,0 +1,51 @@ +// +// Links.swift +// JSONAPI +// +// Created by Mathew Polzin on 11/24/18. +// + +/// A Links structure should contain nothing but JSONAPI.Link properties. +public protocol Links: Codable, Equatable {} + +/// Use NoLinks where no links should belong to a JSON API component +public struct NoLinks: Links {} + +public protocol URL: Codable, Equatable {} + +public struct Link: Equatable, Codable { + public let url: URL + public let meta: Meta +} + +public extension Link { + private enum CodingKeys: String, CodingKey { + case href + case meta + } + + init(from decoder: Decoder) throws { + guard Meta.self == NoMetadata.self, + let noMeta = NoMetadata() as? Meta else { + let container = try decoder.container(keyedBy: CodingKeys.self) + meta = try container.decode(Meta.self, forKey: .meta) + url = try container.decode(URL.self, forKey: .href) + return + } + let container = try decoder.singleValueContainer() + url = try container.decode(URL.self) + meta = noMeta + } + + func encode(to encoder: Encoder) throws { + guard Meta.self == NoMetadata.self else { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(url, forKey: .href) + try container.encode(meta, forKey: .meta) + return + } + var container = encoder.singleValueContainer() + + try container.encode(url) + } +} diff --git a/Sources/JSONAPI/Meta/Meta.swift b/Sources/JSONAPI/Meta/Meta.swift index f653edc..318c438 100644 --- a/Sources/JSONAPI/Meta/Meta.swift +++ b/Sources/JSONAPI/Meta/Meta.swift @@ -15,6 +15,10 @@ public protocol Meta: Codable, Equatable { } +// We make Optional a Meta if it wraps a Meta so that Metadata can be specified as +// nullable. +extension Optional: Meta where Wrapped: Meta {} + public struct NoMetadata: Meta { public static var none: NoMetadata { return NoMetadata() } diff --git a/Tests/JSONAPITests/Links/LinksTests.swift b/Tests/JSONAPITests/Links/LinksTests.swift new file mode 100644 index 0000000..2357014 --- /dev/null +++ b/Tests/JSONAPITests/Links/LinksTests.swift @@ -0,0 +1,70 @@ +// +// LinksTests.swift +// JSONAPITests +// +// Created by Mathew Polzin on 11/24/18. +// + +import XCTest +import JSONAPI + +class LinksTests: XCTestCase { + func test_linkWithNoMeta() { + let links = decoded(type: LinksTests.Links.self, data: link_without_meta) + + XCTAssertEqual(links.link.url, "https://website.com/path/file") + XCTAssertEqual(links.link.meta, NoMetadata()) + } + + func test_linkWithNoMeta_encode() { + test_DecodeEncodeEquality(type: LinksTests.Links.self, data: link_without_meta) + } + + func test_linkWithNullMetadata() { + let link = decoded(type: Link.self, data: link_with_null_meta) + + XCTAssertEqual(link.url, "https://website.com/path/file") + XCTAssertNil(link.meta) + } + + func test_linkWithNullMetadata_encode() { + test_DecodeEncodeEquality(type: Link.self, data: link_with_null_meta) + } + + func test_linkWithMetadata() { + let link = decoded(type: Link.self, data: link_with_metadata) + + XCTAssertEqual(link.url, "https://website.com/path/file") + XCTAssertEqual(link.meta, Metadata(hello: "world")) + + let link2 = decoded(type: Link.self, data: link_with_metadata) + + XCTAssertEqual(link2.url, "https://website.com/path/file") + XCTAssertEqual(link2.meta, Metadata(hello: "world")) + } + + func test_linkWithMetadata_encode() { + test_DecodeEncodeEquality(type: Link.self, data: link_with_metadata) + + test_DecodeEncodeEquality(type: Link.self, data: link_with_metadata) + } + + func test_linkFailsIfMetaNotFound() { + XCTAssertThrowsError(try JSONDecoder().decode(Link.self, from: link_with_null_meta)) + } +} + +// MARK: - Test types +extension LinksTests { + typealias URL = String + + struct Links: JSONAPI.Links { + let link: Link + } + + struct Metadata: JSONAPI.Meta { + let hello: String + } +} + +extension String: JSONAPI.URL {} diff --git a/Tests/JSONAPITests/Links/stub/LinkStubs.swift b/Tests/JSONAPITests/Links/stub/LinkStubs.swift new file mode 100644 index 0000000..6c0563f --- /dev/null +++ b/Tests/JSONAPITests/Links/stub/LinkStubs.swift @@ -0,0 +1,28 @@ +// +// LinkStubs.swift +// JSONAPITests +// +// Created by Mathew Polzin on 11/24/18. +// + +let link_without_meta = """ +{ + "link": "https://website.com/path/file" +} +""".data(using: .utf8)! + +let link_with_null_meta = """ +{ + "href": "https://website.com/path/file", + "meta": null +} +""".data(using: .utf8)! + +let link_with_metadata = """ +{ + "href": "https://website.com/path/file", + "meta": { + "hello": "world" + } +} +""".data(using: .utf8)! diff --git a/Tests/JSONAPITests/XCTestManifests.swift b/Tests/JSONAPITests/XCTestManifests.swift index ec0754b..16d5b3d 100644 --- a/Tests/JSONAPITests/XCTestManifests.swift +++ b/Tests/JSONAPITests/XCTestManifests.swift @@ -100,6 +100,18 @@ extension IncludedTests { ] } +extension LinksTests { + static let __allTests = [ + ("test_linkFailsIfMetaNotFound", test_linkFailsIfMetaNotFound), + ("test_linkWithMetadata", test_linkWithMetadata), + ("test_linkWithMetadata_encode", test_linkWithMetadata_encode), + ("test_linkWithNoMeta", test_linkWithNoMeta), + ("test_linkWithNoMeta_encode", test_linkWithNoMeta_encode), + ("test_linkWithNullMetadata", test_linkWithNullMetadata), + ("test_linkWithNullMetadata_encode", test_linkWithNullMetadata_encode), + ] +} + extension PolyTests { static let __allTests = [ ("test_init_Poly0", test_init_Poly0), @@ -156,6 +168,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(DocumentTests.__allTests), testCase(EntityTests.__allTests), testCase(IncludedTests.__allTests), + testCase(LinksTests.__allTests), testCase(PolyTests.__allTests), testCase(RelationshipTests.__allTests), testCase(ResourceBodyTests.__allTests),