From fc78958f76f7c12e060cb8a6d5620a834e9c3151 Mon Sep 17 00:00:00 2001 From: Janko Luin Date: Tue, 2 Jul 2019 16:49:47 +0200 Subject: [PATCH 01/13] Allow omitting `relationships` if all are optional When all relationships are optional, the `relationships` key is also optional and not required in the structure. I'm not super happy with importing Foundation and creating new objects any time a key is missing, but ultimately none of my attempts at conditional generics worked out for me. --- Sources/JSONAPI/Resource/ResourceObject.swift | 7 ++-- Tests/JSONAPITests/Entity/EntityTests.swift | 32 +++++++++++++++++++ .../Entity/stubs/EntityStubs.swift | 10 ++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Sources/JSONAPI/Resource/ResourceObject.swift b/Sources/JSONAPI/Resource/ResourceObject.swift index cdbdd7e..e53c1f4 100644 --- a/Sources/JSONAPI/Resource/ResourceObject.swift +++ b/Sources/JSONAPI/Resource/ResourceObject.swift @@ -5,6 +5,8 @@ // Created by Mathew Polzin on 7/24/18. // +import Foundation + /// A JSON API structure within an ResourceObject that contains /// named properties of types `ToOneRelationship` and /// `ToManyRelationship`. @@ -582,7 +584,6 @@ public extension ResourceObject { } init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self) let type = try container.decode(String.self, forKey: .type) @@ -597,7 +598,9 @@ public extension ResourceObject { attributes = try (NoAttributes() as? Description.Attributes) ?? container.decode(Description.Attributes.self, forKey: .attributes) - relationships = try (NoRelationships() as? Description.Relationships) ?? container.decode(Description.Relationships.self, forKey: .relationships) + relationships = try (NoRelationships() as? Description.Relationships) + ?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships) + ?? JSONDecoder().decode(Description.Relationships.self, from: "{}".data(using: .utf8)!) meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta) diff --git a/Tests/JSONAPITests/Entity/EntityTests.swift b/Tests/JSONAPITests/Entity/EntityTests.swift index 18e313e..ae44904 100644 --- a/Tests/JSONAPITests/Entity/EntityTests.swift +++ b/Tests/JSONAPITests/Entity/EntityTests.swift @@ -403,6 +403,16 @@ extension EntityTests { data: entity_optional_nullable_nulled_relationship) } + func test_optionalNullableRelationshipOmitted() { + let entity = decoded(type: TestEntity12.self, + data: entity_all_relationships_optional_and_omitted) + + XCTAssertNil(entity ~> \.optionalOne) + XCTAssertNil(entity ~> \.optionalNullableOne) + XCTAssertNil(entity ~> \.optionalMany) + XCTAssertNoThrow(try TestEntity12.check(entity)) + } + func test_nullableRelationshipIsNull() { let entity = decoded(type: TestEntity9.self, data: entity_nulled_relationship) @@ -806,6 +816,28 @@ extension EntityTests { typealias TestEntity11 = BasicEntity + enum TestEntityType12: ResourceObjectDescription { + public static var jsonType: String { return "twelfth_test_entities" } + + typealias Attributes = NoAttributes + + public struct Relationships: JSONAPI.Relationships { + public init() { + optionalOne = nil + optionalNullableOne = nil + optionalMany = nil + } + + let optionalOne: ToOneRelationship? + + let optionalNullableOne: ToOneRelationship? + + let optionalMany: ToManyRelationship? + } + } + + typealias TestEntity12 = BasicEntity + enum UnidentifiedTestEntityType: ResourceObjectDescription { public static var jsonType: String { return "unidentified_test_entities" } diff --git a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift index 1384c11..58343ce 100644 --- a/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift +++ b/Tests/JSONAPITests/Entity/stubs/EntityStubs.swift @@ -383,6 +383,16 @@ let entity_valid_validated_attribute = """ } """.data(using: .utf8)! +let entity_all_relationships_optional_and_omitted = """ +{ + "id": "1", + "type": "twelfth_test_entities", + "attributes": { + "number": 10 + } +} +""".data(using: .utf8)! + let entity_unidentified = """ { "type": "unidentified_test_entities", From e9b9a2bd7840fbb2933914248cfdbe9976a60e52 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 2 Jul 2019 17:36:54 -0700 Subject: [PATCH 02/13] Add Empty Object Decoder to be used in upcoming release. --- Sources/JSONAPI/EmptyObjectDecoder.swift | 215 +++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 Sources/JSONAPI/EmptyObjectDecoder.swift diff --git a/Sources/JSONAPI/EmptyObjectDecoder.swift b/Sources/JSONAPI/EmptyObjectDecoder.swift new file mode 100644 index 0000000..f5fd699 --- /dev/null +++ b/Sources/JSONAPI/EmptyObjectDecoder.swift @@ -0,0 +1,215 @@ +// +// EmptyObjectDecoder.swift +// JSONAPI +// +// Created by Mathew Polzin on 7/2/19. +// + +/// `EmptyObjectDecoder` exists internally for the sole purpose of +/// allowing certain fallback logic paths to attempt to create `Decodable` +/// types from empty containers (specifically in a way that is agnostic +/// of any given encoding). In other words, this serves the same purpose +/// as `JSONDecoder().decode(Thing.self, from: "{}".data(using: .utf8)!)` +/// without needing to use a third party or `Foundation` library decoder. +struct EmptyObjectDecoder: Decoder { + + var codingPath: [CodingKey] = [] + + var userInfo: [CodingUserInfoKey : Any] = [:] + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + return KeyedDecodingContainer(EmptyKeyedContainer()) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + return EmptyUnkeyedContainer() + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + throw EmptyObjectDecodingError.emptyObjectCannotBeSingleValue + } +} + +enum EmptyObjectDecodingError: Swift.Error { + case emptyObjectCannotBeSingleValue + case emptyObjectCannotBeUnkeyedValues + case emptyObjectCannotHaveKeyedValues + case emptyObjectCannotHaveNestedContainers + case emptyObjectCannotHaveSuper +} + +struct EmptyUnkeyedContainer: UnkeyedDecodingContainer { + var codingPath: [CodingKey] { return [] } + + var count: Int? { return 0 } + + var isAtEnd: Bool { return true } + + var currentIndex: Int { return 0 } + + mutating func decodeNil() throws -> Bool { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Bool.Type) throws -> Bool { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: String.Type) throws -> String { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Double.Type) throws -> Double { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Float.Type) throws -> Float { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Int.Type) throws -> Int { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Int8.Type) throws -> Int8 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Int16.Type) throws -> Int16 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Int32.Type) throws -> Int32 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: Int64.Type) throws -> Int64 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: UInt.Type) throws -> UInt { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func decode(_ type: T.Type) throws -> T where T : Decodable { + throw EmptyObjectDecodingError.emptyObjectCannotBeUnkeyedValues + } + + mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + throw EmptyObjectDecodingError.emptyObjectCannotHaveNestedContainers + } + + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + throw EmptyObjectDecodingError.emptyObjectCannotHaveNestedContainers + } + + mutating func superDecoder() throws -> Decoder { + throw EmptyObjectDecodingError.emptyObjectCannotHaveSuper + } +} + +struct EmptyKeyedContainer: KeyedDecodingContainerProtocol { + var codingPath: [CodingKey] { return [] } + + var allKeys: [Key] { return [] } + + func contains(_ key: Key) -> Bool { + return false + } + + func decodeNil(forKey key: Key) throws -> Bool { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + throw EmptyObjectDecodingError.emptyObjectCannotHaveKeyedValues + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + throw EmptyObjectDecodingError.emptyObjectCannotHaveNestedContainers + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + throw EmptyObjectDecodingError.emptyObjectCannotHaveNestedContainers + } + + func superDecoder() throws -> Decoder { + throw EmptyObjectDecodingError.emptyObjectCannotHaveSuper + } + + func superDecoder(forKey key: Key) throws -> Decoder { + throw EmptyObjectDecodingError.emptyObjectCannotHaveSuper + } +} From e4481c9e4f1f25ea35306c0b07a136ec3bdb0c2e Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 2 Jul 2019 18:05:22 -0700 Subject: [PATCH 03/13] noticed the README had some typos I missed when I changed Entity to ResourceObject. --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b518724..56e4e13 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ In this documentation, in order to draw attention to the difference between the ### `JSONAPI.ResourceObjectDescription` -An `ResourceObjectDescription` is the `JSONAPI` framework's representation of what the **SPEC** calls a *Resource Object*. You might create the following `ResourceObjectDescription` to represent a person in a network of friends: +A `ResourceObjectDescription` is the `JSONAPI` framework's representation of what the **SPEC** calls a *Resource Object*. You might create the following `ResourceObjectDescription` to represent a person in a network of friends: ```swift enum PersonDescription: IdentifiedResourceObjectDescription { @@ -220,7 +220,7 @@ enum PersonDescription: IdentifiedResourceObjectDescription { } ``` -The requirements of an `ResourceObjectDescription` are: +The requirements of a `ResourceObjectDescription` are: 1. A static `var` "jsonType" that matches the JSON type; The **SPEC** requires every *Resource Object* to have a "type". 2. A `struct` of `Attributes` **- OR -** `typealias Attributes = NoAttributes` 3. A `struct` of `Relationships` **- OR -** `typealias Relationships = NoRelationships` @@ -259,11 +259,11 @@ This readme doesn't go into detail on the **SPEC**, but the following *Resource ### `JSONAPI.ResourceObject` -Once you have an `ResourceObjectDescription`, you _create_, _encode_, and _decode_ `ResourceObjects` that "fit the description". If you have a `CreatableRawIdType` (see the section on `RawIdType`s below) then you can create new `ResourceObjects` that will automatically be given unique Ids, but even without a `CreatableRawIdType` you can encode, decode and work with resource objects. +Once you have a `ResourceObjectDescription`, you _create_, _encode_, and _decode_ `ResourceObjects` that "fit the description". If you have a `CreatableRawIdType` (see the section on `RawIdType`s below) then you can create new `ResourceObjects` that will automatically be given unique Ids, but even without a `CreatableRawIdType` you can encode, decode and work with resource objects. The `ResourceObject` and `ResourceObjectDescription` together with a `JSONAPI.Meta` type and a `JSONAPI.Links` type embody the rules and properties of a JSON API *Resource Object*. -An `ResourceObject` needs to be specialized on four generic types. The first is the `ResourceObjectDescription` described above. The others are a `Meta`, `Links`, and `MaybeRawId`. +A `ResourceObject` needs to be specialized on four generic types. The first is the `ResourceObjectDescription` described above. The others are a `Meta`, `Links`, and `MaybeRawId`. #### `Meta` @@ -275,7 +275,7 @@ The third generic specialization on `ResourceObject` is `Links`. This is describ #### `MaybeRawId` -The last generic specialization on `ResourceObject` is `MaybeRawId`. This is either a `RawIdType` that can be used to uniquely identify `ResourceObjects` or it is `Unidentified` which is used to indicate an `ResourceObject` does not have an `Id` (which is useful when a client is requesting that the server create an `ResourceObject` and assign it a new `Id`). +The last generic specialization on `ResourceObject` is `MaybeRawId`. This is either a `RawIdType` that can be used to uniquely identify `ResourceObjects` or it is `Unidentified` which is used to indicate a `ResourceObject` does not have an `Id` (which is useful when a client is requesting that the server create a `ResourceObject` and assign it a new `Id`). ##### `RawIdType` @@ -283,7 +283,7 @@ The raw type of `Id` to use for the `ResourceObject`. The actual `Id` of the `Re Having the `ResourceObject` type associated with the `Id` makes it easy to store all of your resource objects in a hash broken out by `ResourceObject` type; You can pass `Ids` around and always know where to look for the `ResourceObject` to which the `Id` refers. This encapsulation provides some type safety because the Ids of two `ResourceObjects` with the "raw ID" of `"1"` but different types will not compare as equal. -A `RawIdType` is the underlying type that uniquely identifies an `ResourceObject`. This is often a `String` or a `UUID`. +A `RawIdType` is the underlying type that uniquely identifies a `ResourceObject`. This is often a `String` or a `UUID`. #### Convenient `typealiases` @@ -305,7 +305,7 @@ Note that I am assuming an unidentified person is a "new" person. I suspect that ### `JSONAPI.Relationships` -There are two types of `Relationships`: `ToOneRelationship` and `ToManyRelationship`. An `ResourceObjectDescription`'s `Relationships` type can contain any number of `Relationship` properties of either of these types. Do not store anything other than `Relationship` properties in the `Relationships` struct of an `ResourceObjectDescription`. +There are two types of `Relationships`: `ToOneRelationship` and `ToManyRelationship`. A `ResourceObjectDescription`'s `Relationships` type can contain any number of `Relationship` properties of either of these types. Do not store anything other than `Relationship` properties in the `Relationships` struct of a `ResourceObjectDescription`. In addition to identifying resource objects by Id and type, `Relationships` can contain `Meta` or `Links` that follow the same rules as [`Meta`](#jsonapimeta) and [`Links`](#jsonapilinks) elsewhere in the JSON API Document. @@ -314,7 +314,7 @@ To describe a relationship that may be omitted (i.e. the key is not even present let nullableRelative: ToOneRelationship ``` -An resource object that does not have relationships can be described by adding the following to an `ResourceObjectDescription`: +A `ResourceObject` that does not have relationships can be described by adding the following to a `ResourceObjectDescription`: ```swift typealias Relationships = NoRelationships ``` @@ -326,7 +326,7 @@ let friendIds: [Person.Identifier] = person ~> \.friends ### `JSONAPI.Attributes` -The `Attributes` of an `ResourceObjectDescription` can contain any JSON encodable/decodable types as long as they are wrapped in an `Attribute`, `ValidatedAttribute`, or `TransformedAttribute` `struct`. +The `Attributes` of a `ResourceObjectDescription` can contain any JSON encodable/decodable types as long as they are wrapped in an `Attribute`, `ValidatedAttribute`, or `TransformedAttribute` `struct`. To describe an attribute that may be omitted (i.e. the key might not even be in the JSON object), you make the entire `Attribute` optional: ```swift @@ -338,7 +338,7 @@ To describe an attribute that is expected to exist but might have a `null` value let nullableAttribute: Attribute ``` -An resource object that does not have attributes can be described by adding the following to an `ResourceObjectDescription`: +A resource object that does not have attributes can be described by adding the following to an `ResourceObjectDescription`: ```swift typealias Attributes = NoAttributes ``` @@ -396,8 +396,8 @@ public var fullName: Attribute { If your computed property is wrapped in a `AttributeType` then you can still use the default subscript operator to access it (as would be the case with the `person[\.fullName]` example above). However, if you add a property to the `Attributes` `struct` that is not wrapped in an `AttributeType`, you must either access it from its full path (`person.attributes.newThing`) or with the "direct" subscript accessor (`person[direct: \.newThing]`). This keeps the subscript access unambiguous enough for the compiler to be helpful prior to explicitly casting, comparing, or storing the result. -### Copying `ResourceObjects` -`ResourceObject` is a value type, so copying is its default behavior. There are two common mutations you might want to make when copying an `ResourceObject`: +### Copying/Mutating `ResourceObjects` +`ResourceObject` is a value type, so copying is its default behavior. There are two common mutations you might want to make when copying a `ResourceObject`: 1. Assigning a new `Identifier` to the copy of an identified `ResourceObject`. 2. Assigning a new `Identifier` to the copy of an unidentified `ResourceObject`. @@ -590,7 +590,7 @@ extension ResourceObjectDescription1.Attributes { ### Meta-Attributes This advanced feature may not ever be useful, but if you find yourself in the situation of dealing with an API that does not 100% follow the **SPEC** then you might find meta-attributes are just the thing to make your resource objects more natural to work with. -Suppose, for example, you are presented with the unfortunate situation where a piece of information you need is only available as part of the `Id` of an resource object. Perhaps a user's `Id` is formatted "{integer}-{createdAt}" where "createdAt" is the unix timestamp when the user account was created. The following `UserDescription` will expose what you need as an attribute. Realistically, the following example code is still terrible for its error handling. Using a `Result` type and/or invariants would clean things up substantially. +Suppose, for example, you are presented with the unfortunate situation where a piece of information you need is only available as part of the `Id` of a resource object. Perhaps a user's `Id` is formatted "{integer}-{createdAt}" where "createdAt" is the unix timestamp when the user account was created. The following `UserDescription` will expose what you need as an attribute. Realistically, the following example code is still terrible for its error handling. Using a `Result` type and/or invariants would clean things up substantially. ```swift enum UserDescription: ResourceObjectDescription { From c75912ab796699af63dfc733ba67c316080bccec Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 2 Jul 2019 18:09:06 -0700 Subject: [PATCH 04/13] switch Poly versioning to 'upToNextMajor' and updated the resolved version. --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index fac7812..4a251e7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/mattpolzin/Poly.git", "state": { "branch": null, - "revision": "d24d4c1214dd05f89eb1182a46592856dd0a0645", - "version": "2.0.0" + "revision": "38051821d7ef49e590e26e819a2fe447e50be9ff", + "version": "2.0.1" } } ] diff --git a/Package.swift b/Package.swift index 0789b25..11fea30 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( targets: ["JSONAPITesting"]) ], dependencies: [ - .package(url: "https://github.com/mattpolzin/Poly.git", from: "2.0.0"), + .package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.0.0")), ], targets: [ .target( From e820f34253fa982c3dba1a3fba7207c8c1c560c7 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 2 Jul 2019 18:12:29 -0700 Subject: [PATCH 05/13] change podspec version in anticipation of next release --- JSONAPI.podspec | 2 +- Tests/JSONAPITests/Entity/EntityTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/JSONAPI.podspec b/JSONAPI.podspec index 36a800c..0d8e633 100644 --- a/JSONAPI.podspec +++ b/JSONAPI.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "JSONAPI" - spec.version = "0.30.0" + spec.version = "0.31.0" spec.summary = "Swift Codable JSON API framework." # This description is used to generate tags and improve search results. diff --git a/Tests/JSONAPITests/Entity/EntityTests.swift b/Tests/JSONAPITests/Entity/EntityTests.swift index 18e313e..0adfaf5 100644 --- a/Tests/JSONAPITests/Entity/EntityTests.swift +++ b/Tests/JSONAPITests/Entity/EntityTests.swift @@ -31,7 +31,7 @@ class EntityTests: XCTestCase { XCTAssertEqual(entity ~> \.optionalOne, entity1.id) } - + func test_toMany_relationship_operator_access() { let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none) let entity2 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none) From b7ce3d226e19591f4a5938fce158cb5a2ab33a69 Mon Sep 17 00:00:00 2001 From: Janko Luin Date: Wed, 3 Jul 2019 08:32:44 +0200 Subject: [PATCH 06/13] Apply EmptyObjectDecoder rather than empty JSON Co-Authored-By: Mathew Polzin --- Sources/JSONAPI/Resource/ResourceObject.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/JSONAPI/Resource/ResourceObject.swift b/Sources/JSONAPI/Resource/ResourceObject.swift index e53c1f4..83cef3e 100644 --- a/Sources/JSONAPI/Resource/ResourceObject.swift +++ b/Sources/JSONAPI/Resource/ResourceObject.swift @@ -5,7 +5,6 @@ // Created by Mathew Polzin on 7/24/18. // -import Foundation /// A JSON API structure within an ResourceObject that contains /// named properties of types `ToOneRelationship` and @@ -600,7 +599,7 @@ public extension ResourceObject { relationships = try (NoRelationships() as? Description.Relationships) ?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships) - ?? JSONDecoder().decode(Description.Relationships.self, from: "{}".data(using: .utf8)!) + ?? Description.Relationships(from: EmptyObjectDecoder()) meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta) From 099d17aa5803a64d62904f265144b795c38b6db5 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Wed, 3 Jul 2019 07:28:31 -0700 Subject: [PATCH 07/13] Update README.md Removing check boxes for `Arbitrary` conformance and `OpenAPI` integration. Both of these "features" are maintained in separate libraries. --- README.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/README.md b/README.md index 56e4e13..ea5a6ee 100644 --- a/README.md +++ b/README.md @@ -108,75 +108,44 @@ Note that Playground support for importing non-system Frameworks is still a bit #### Document - `data` - [x] Encoding/Decoding - - [x] Arbitrary - - [x] OpenAPI - `included` - [x] Encoding/Decoding - - [x] Arbitrary - - [x] OpenAPI - `errors` - [x] Encoding/Decoding - - [x] Arbitrary - - [ ] OpenAPI - `meta` - [x] Encoding/Decoding - - [x] Arbitrary - - [ ] OpenAPI - `jsonapi` (i.e. API Information) - [x] Encoding/Decoding - - [x] Arbitrary - - [ ] OpenAPI - `links` - [x] Encoding/Decoding - - [x] Arbitrary - - [ ] OpenAPI #### Resource Object - `id` - [x] Encoding/Decoding - - [x] Arbitrary - - [x] OpenAPI - `type` - [x] Encoding/Decoding - - [x] OpenAPI - `attributes` - [x] Encoding/Decoding - - [x] OpenAPI - `relationships` - [x] Encoding/Decoding - - [x] OpenAPI - `links` - [x] Encoding/Decoding - - [x] Arbitrary - - [ ] OpenAPI - `meta` - [x] Encoding/Decoding - - [x] Arbitrary - - [ ] OpenAPI #### Relationship Object - `data` - [x] Encoding/Decoding - - [x] Arbitrary - - [x] OpenAPI - `links` - [x] Encoding/Decoding - - [ ] Arbitrary - - [ ] OpenAPI - `meta` - [x] Encoding/Decoding - - [ ] Arbitrary - - [ ] OpenAPI #### Links Object - `href` - [x] Encoding/Decoding - - [ ] Arbitrary - - [ ] OpenAPI - `meta` - [x] Encoding/Decoding - - [ ] Arbitrary - - [ ] OpenAPI ### Misc - [x] Support transforms on `Attributes` values (e.g. to support different representations of `Date`) @@ -194,7 +163,6 @@ Note that Playground support for importing non-system Frameworks is still a bit - [ ] (Maybe) Use `KeyPath` to specify `Includes` thus creating type safety around the relationship between a primary resource type and the types of included resources. - [ ] (Maybe) Replace `SingleResourceBody` and `ManyResourceBody` with support at the `Document` level to just interpret `PrimaryResource`, `PrimaryResource?`, or `[PrimaryResource]` as the same decoding/encoding strategies. - [ ] Support sideposting. JSONAPI spec might become opinionated in the future (https://github.com/json-api/json-api/pull/1197, https://github.com/json-api/json-api/issues/1215, https://github.com/json-api/json-api/issues/1216) but there is also an existing implementation to consider (https://jsonapi-suite.github.io/jsonapi_suite/ruby/writes/nested-writes). At this time, any sidepost implementation would be an awesome tertiary library to be used alongside the primary JSONAPI library. Maybe `JSONAPISideloading`. -- [ ] Property-based testing (using `SwiftCheck`). - [ ] Error or warning if an included resource object is not related to a primary resource object or another included resource object (Turned off or at least not throwing by default). ## Usage From e99abd26b3ef7631aa7027c7d31800a61dc7e50b Mon Sep 17 00:00:00 2001 From: Chris Ballinger Date: Tue, 16 Jul 2019 16:16:32 -0700 Subject: [PATCH 08/13] Remove Poly from podspec frameworks --- JSONAPI.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/JSONAPI.podspec b/JSONAPI.podspec index 0d8e633..0b9b899 100644 --- a/JSONAPI.podspec +++ b/JSONAPI.podspec @@ -119,7 +119,6 @@ See the JSON API Spec here: https://jsonapi.org/format/ # the lib prefix of their name. # - spec.framework = "Poly" # spec.frameworks = "SomeFramework", "AnotherFramework" # spec.library = "iconv" From 715db33138b773234e07ec13581d683e1d911349 Mon Sep 17 00:00:00 2001 From: Chris Ballinger Date: Tue, 16 Jul 2019 17:08:59 -0700 Subject: [PATCH 09/13] Do not include testing source files in podspec --- JSONAPI.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONAPI.podspec b/JSONAPI.podspec index 0d8e633..f6d53d9 100644 --- a/JSONAPI.podspec +++ b/JSONAPI.podspec @@ -93,7 +93,7 @@ See the JSON API Spec here: https://jsonapi.org/format/ # Not including the public_header_files will make all headers public. # - spec.source_files = "Sources", "Sources/**/*.{swift}" + spec.source_files = "Sources/JSONAPI/**/*.{swift}" # spec.exclude_files = "Classes/Exclude" # spec.public_header_files = "Classes/**/*.h" From 61fa56e84bc163a9fb343ebbba73b1f0d8db845f Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 16 Jul 2019 22:19:50 -0700 Subject: [PATCH 10/13] bump podspec version after two cocoapods fixes. --- JSONAPI.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONAPI.podspec b/JSONAPI.podspec index e61e3d5..d884b6c 100644 --- a/JSONAPI.podspec +++ b/JSONAPI.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "JSONAPI" - spec.version = "0.31.0" + spec.version = "0.31.1" spec.summary = "Swift Codable JSON API framework." # This description is used to generate tags and improve search results. From c996e7447c89c7108d3ba64d17e01fc51aa6be34 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 23 Jul 2019 21:11:59 -0700 Subject: [PATCH 11/13] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ea5a6ee..1bfe81e 100644 --- a/README.md +++ b/README.md @@ -462,6 +462,8 @@ A `Meta` struct is totally open-ended. It is described by the **SPEC** as a plac You can specify `NoMetadata` if the part of the document being described should not contain any `Meta`. +If you need to support metadata with structure that is not pre-determined, consider an "Any Codable" type such as that found at https://github.com/Flight-School/AnyCodable. + ### `JSONAPI.Links` A `Links` struct must contain only `Link` properties. Each `Link` property can either be a `URL` or a `URL` and some `Meta`. Each part of the document has some suggested common `Links` to include but generally any link can be included. From 7b5b17918c075ec3b5eecf402c8c3219313aaa29 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Wed, 24 Jul 2019 19:43:11 -0700 Subject: [PATCH 12/13] Update linuxmain, slightly update wording and indentation on example at bottom of README. --- .../Contents.swift | 143 +++++++++--------- README.md | 130 ++++++++-------- .../JSONAPITestingTests/XCTestManifests.swift | 30 ++-- Tests/JSONAPITests/XCTestManifests.swift | 101 +++++++++---- Tests/LinuxMain.swift | 4 +- 5 files changed, 233 insertions(+), 175 deletions(-) diff --git a/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift index 2e54355..c8cbea1 100644 --- a/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/Full Client & Server Example.xcplaygroundpage/Contents.swift @@ -4,65 +4,66 @@ import Poly // MARK: - Preamble (setup) -// We make String a CreatableRawIdType. This is actually done in +// Make String a CreatableRawIdType. This is actually done in // this Playground's Entities.swift file, so it is commented out here. /* -var GlobalStringId: Int = 0 -extension String: CreatableRawIdType { - public static func unique() -> String { - GlobalStringId += 1 - return String(GlobalStringId) - } -} -*/ + var globalStringId: Int = 0 + extension String: CreatableRawIdType { + public static func unique() -> String { + globalStringId += 1 + return String(globalStringId) + } + } + */ -// We create a typealias given that we do not expect JSON:API Resource +// Create a typealias because we do not expect JSON:API Resource // Objects for this particular API to have Metadata or Links associated // with them. We also expect them to have String Identifiers. typealias JSONEntity = JSONAPI.ResourceObject -// Similarly, we create a typealias for unidentified entities. JSON:API +// Similarly, create a typealias for unidentified entities. JSON:API // only allows unidentified entities (i.e. no "id" field) for client // requests that create new entities. In these situations, the server // is expected to assign the new entity a unique ID. typealias UnidentifiedJSONEntity = JSONAPI.ResourceObject -// We create typealiases given that we do not expect JSON:API Relationships -// for this particular API to have Metadata or Links associated -// with them. +// Create relationship typealiases because we do not expect +// JSON:API Relationships for this particular API to have +// Metadata or Links associated with them. typealias ToOneRelationship = JSONAPI.ToOneRelationship typealias ToManyRelationship = JSONAPI.ToManyRelationship -// We create a typealias for a Document given that we do not expect +// Create a typealias for a Document because we do not expect // JSON:API Documents for this particular API to have Metadata, Links, -// useful Errors, or a JSON:API Object (i.e. APIDescription). +// useful Errors, or an APIDescription (The *SPEC* calls this +// "API Description" the "JSON:API Object"). typealias Document = JSONAPI.Document // MARK: Entity Definitions enum AuthorDescription: ResourceObjectDescription { - public static var jsonType: String { return "authors" } + public static var jsonType: String { return "authors" } - public struct Attributes: JSONAPI.Attributes { - public let name: Attribute - } + public struct Attributes: JSONAPI.Attributes { + public let name: Attribute + } - public typealias Relationships = NoRelationships + public typealias Relationships = NoRelationships } typealias Author = JSONEntity enum ArticleDescription: ResourceObjectDescription { - public static var jsonType: String { return "articles" } + public static var jsonType: String { return "articles" } - public struct Attributes: JSONAPI.Attributes { - public let title: Attribute - public let abstract: Attribute - } + public struct Attributes: JSONAPI.Attributes { + public let title: Attribute + public let abstract: Attribute + } - public struct Relationships: JSONAPI.Relationships { - public let author: ToOneRelationship - } + public struct Relationships: JSONAPI.Relationships { + public let author: ToOneRelationship + } } typealias Article = JSONEntity @@ -83,38 +84,38 @@ typealias SingleArticleDocument = Document, NoInclud // that creates a document. Note that this document is the entirety // of a JSON:API response body. func articleDocument(includeAuthor: Bool) -> Either { - // Let's pretend all of this is coming from a database: + // Let's pretend all of this is coming from a database: - let authorId = Author.Identifier(rawValue: "1234") + let authorId = Author.Identifier(rawValue: "1234") - let article = Article(id: .init(rawValue: "5678"), - attributes: .init(title: .init(value: "JSON:API in Swift"), - abstract: .init(value: "Not yet written")), - relationships: .init(author: .init(id: authorId)), - meta: .none, - links: .none) + let article = Article(id: .init(rawValue: "5678"), + attributes: .init(title: .init(value: "JSON:API in Swift"), + abstract: .init(value: "Not yet written")), + relationships: .init(author: .init(id: authorId)), + meta: .none, + links: .none) - let document = SingleArticleDocument(apiDescription: .none, - body: .init(resourceObject: article), - includes: .none, - meta: .none, - links: .none) + let document = SingleArticleDocument(apiDescription: .none, + body: .init(resourceObject: article), + includes: .none, + meta: .none, + links: .none) - switch includeAuthor { - case false: - return .a(document) + switch includeAuthor { + case false: + return .init(document) - case true: - let author = Author(id: authorId, - attributes: .init(name: .init(value: "Janice Bluff")), - relationships: .none, - meta: .none, - links: .none) + case true: + let author = Author(id: authorId, + attributes: .init(name: .init(value: "Janice Bluff")), + relationships: .none, + meta: .none, + links: .none) - let includes: Includes = .init(values: [.init(author)]) + let includes: Includes = .init(values: [.init(author)]) - return .b(document.including(.init(values: [.init(author)]))) - } + return .init(document.including(.init(values: [.init(author)]))) + } } let encoder = JSONEncoder() @@ -124,8 +125,8 @@ encoder.outputFormatting = .prettyPrinted let responseBody = articleDocument(includeAuthor: true) let responseData = try! encoder.encode(responseBody) -// Next step would be encoding and setting as the HTTP body of a response. -// we will just print it out instead: +// Next step would be setting the HTTP body of a response. +// We will just print it out instead: print("-----") print(String(data: responseData, encoding: .utf8)!) @@ -139,31 +140,31 @@ print(String(data: otherResponseData, encoding: .utf8)!) // MARK: - Client Pseudo-example enum NetworkError: Swift.Error { - case serverError - case quantityMismatch + case serverError + case quantityMismatch } // Skipping over all the API stuff, here's a chunk of code that will // decode a document. We will assume we have made a request for a // single article including the author. func docode(articleResponseData: Data) throws -> (article: Article, author: Author) { - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase - let articleDocument = try decoder.decode(SingleArticleDocumentWithIncludes.self, from: articleResponseData) + let articleDocument = try decoder.decode(SingleArticleDocumentWithIncludes.self, from: articleResponseData) - switch articleDocument.body { - case .data(let data): - let authors = data.includes[Author.self] + switch articleDocument.body { + case .data(let data): + let authors = data.includes[Author.self] - guard authors.count == 1 else { - throw NetworkError.quantityMismatch - } + guard authors.count == 1 else { + throw NetworkError.quantityMismatch + } - return (article: data.primary.value, author: authors[0]) - case .errors(let errors, meta: _, links: _): - throw NetworkError.serverError - } + return (article: data.primary.value, author: authors[0]) + case .errors(let errors, meta: _, links: _): + throw NetworkError.serverError + } } let response = try! docode(articleResponseData: responseData) diff --git a/README.md b/README.md index 1bfe81e..9567cb4 100644 --- a/README.md +++ b/README.md @@ -640,38 +640,39 @@ The following serves as a sort of pseudo-example. It skips server/client impleme ### Preamble (Setup shared by server and client) ```swift -// We make String a CreatableRawIdType. -var GlobalStringId: Int = 0 +// Make String a CreatableRawIdType. +var globalStringId: Int = 0 extension String: CreatableRawIdType { public static func unique() -> String { - GlobalStringId += 1 - return String(GlobalStringId) + globalStringId += 1 + return String(globalStringId) } } -// We create a typealias given that we do not expect JSON:API Resource +// Create a typealias because we do not expect JSON:API Resource // Objects for this particular API to have Metadata or Links associated // with them. We also expect them to have String Identifiers. -typealias JSONResourceObject = JSONAPI.ResourceObject +typealias JSONEntity = JSONAPI.ResourceObject -// Similarly, we create a typealias for unidentified resource objects. JSON:API -// only allows unidentified resource objects (i.e. no "id" field) for client -// requests that create new resource objects. In these situations, the server -// is expected to assign the new resource object a unique ID. -typealias UnidentifiedJSONResourceObject = JSONAPI.ResourceObject +// Similarly, create a typealias for unidentified entities. JSON:API +// only allows unidentified entities (i.e. no "id" field) for client +// requests that create new entities. In these situations, the server +// is expected to assign the new entity a unique ID. +typealias UnidentifiedJSONEntity = JSONAPI.ResourceObject -// We create typealiases given that we do not expect JSON:API Relationships -// for this particular API to have Metadata or Links associated -// with them. -typealias ToOneRelationship = JSONAPI.ToOneRelationship -typealias ToManyRelationship = JSONAPI.ToManyRelationship +// Create relationship typealiases because we do not expect +// JSON:API Relationships for this particular API to have +// Metadata or Links associated with them. +typealias ToOneRelationship = JSONAPI.ToOneRelationship +typealias ToManyRelationship = JSONAPI.ToManyRelationship -// We create a typealias for a Document given that we do not expect +// Create a typealias for a Document because we do not expect // JSON:API Documents for this particular API to have Metadata, Links, -// useful Errors, or a JSON:API Object (i.e. APIDescription). +// useful Errors, or an APIDescription (The *SPEC* calls this +// "API Description" the "JSON:API Object"). typealias Document = JSONAPI.Document -// MARK: ResourceObject Definitions +// MARK: Entity Definitions enum AuthorDescription: ResourceObjectDescription { public static var jsonType: String { return "authors" } @@ -683,7 +684,7 @@ enum AuthorDescription: ResourceObjectDescription { public typealias Relationships = NoRelationships } -typealias Author = JSONResourceObject +typealias Author = JSONEntity enum ArticleDescription: ResourceObjectDescription { public static var jsonType: String { return "articles" } @@ -698,7 +699,7 @@ enum ArticleDescription: ResourceObjectDescription { } } -typealias Article = JSONResourceObject +typealias Article = JSONEntity // MARK: Document Definitions @@ -707,47 +708,48 @@ typealias Article = JSONResourceObject typealias SingleArticleDocumentWithIncludes = Document, Include1> // ... and a typealias to represent a document containing one Article and -// not including any related resource objects. +// not including any related entities. typealias SingleArticleDocument = Document, NoIncludes> ``` + ### Server Pseudo-example ```swift // Skipping over all the API and database stuff, here's a chunk of code // that creates a document. Note that this document is the entirety // of a JSON:API response body. func articleDocument(includeAuthor: Bool) -> Either { - // Let's pretend all of this is coming from a database: + // Let's pretend all of this is coming from a database: - let authorId = Author.Identifier(rawValue: "1234") + let authorId = Author.Identifier(rawValue: "1234") - let article = Article(id: .init(rawValue: "5678"), - attributes: .init(title: .init(value: "JSON:API in Swift"), - abstract: .init(value: "Not yet written")), - relationships: .init(author: .init(id: authorId)), - meta: .none, - links: .none) + let article = Article(id: .init(rawValue: "5678"), + attributes: .init(title: .init(value: "JSON:API in Swift"), + abstract: .init(value: "Not yet written")), + relationships: .init(author: .init(id: authorId)), + meta: .none, + links: .none) - let document = SingleArticleDocument(apiDescription: .none, - body: .init(resourceObject: article), - includes: .none, - meta: .none, - links: .none) + let document = SingleArticleDocument(apiDescription: .none, + body: .init(resourceObject: article), + includes: .none, + meta: .none, + links: .none) - switch includeAuthor { - case false: - return .a(document) + switch includeAuthor { + case false: + return .init(document) - case true: - let author = Author(id: authorId, - attributes: .init(name: .init(value: "Janice Bluff")), - relationships: .none, - meta: .none, - links: .none) + case true: + let author = Author(id: authorId, + attributes: .init(name: .init(value: "Janice Bluff")), + relationships: .none, + meta: .none, + links: .none) - let includes: Includes = .init(values: [.init(author)]) + let includes: Includes = .init(values: [.init(author)]) - return .b(document.including(.init(values: [.init(author)]))) - } + return .init(document.including(.init(values: [.init(author)]))) + } } let encoder = JSONEncoder() @@ -757,8 +759,8 @@ encoder.outputFormatting = .prettyPrinted let responseBody = articleDocument(includeAuthor: true) let responseData = try! encoder.encode(responseBody) -// Next step would be encoding and setting as the HTTP body of a response. -// we will just print it out instead: +// Next step would be setting the HTTP body of a response. +// We will just print it out instead: print("-----") print(String(data: responseData, encoding: .utf8)!) @@ -773,31 +775,31 @@ print(String(data: otherResponseData, encoding: .utf8)!) ### Client Pseudo-example ```swift enum NetworkError: Swift.Error { - case serverError - case quantityMismatch + case serverError + case quantityMismatch } // Skipping over all the API stuff, here's a chunk of code that will // decode a document. We will assume we have made a request for a // single article including the author. func docode(articleResponseData: Data) throws -> (article: Article, author: Author) { - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase - let articleDocument = try decoder.decode(SingleArticleDocumentWithIncludes.self, from: articleResponseData) + let articleDocument = try decoder.decode(SingleArticleDocumentWithIncludes.self, from: articleResponseData) - switch articleDocument.body { - case .data(let data): - let authors = data.includes[Author.self] + switch articleDocument.body { + case .data(let data): + let authors = data.includes[Author.self] - guard authors.count == 1 else { - throw NetworkError.quantityMismatch - } + guard authors.count == 1 else { + throw NetworkError.quantityMismatch + } - return (article: data.primary.value, author: authors[0]) - case .errors(let errors, meta: _, links: _): - throw NetworkError.serverError - } + return (article: data.primary.value, author: authors[0]) + case .errors(let errors, meta: _, links: _): + throw NetworkError.serverError + } } let response = try! docode(articleResponseData: responseData) diff --git a/Tests/JSONAPITestingTests/XCTestManifests.swift b/Tests/JSONAPITestingTests/XCTestManifests.swift index 3da4318..c916df3 100644 --- a/Tests/JSONAPITestingTests/XCTestManifests.swift +++ b/Tests/JSONAPITestingTests/XCTestManifests.swift @@ -1,7 +1,11 @@ +#if !canImport(ObjectiveC) import XCTest extension Attribute_LiteralTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__Attribute_LiteralTests = [ ("test_ArrayLiteral", test_ArrayLiteral), ("test_BooleanLiteral", test_BooleanLiteral), ("test_DictionaryLiteral", test_DictionaryLiteral), @@ -32,7 +36,10 @@ extension Attribute_LiteralTests { } extension EntityCheckTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__EntityCheckTests = [ ("test_failsWithBadAttribute", test_failsWithBadAttribute), ("test_failsWithBadRelationship", test_failsWithBadRelationship), ("test_failsWithEnumAttributes", test_failsWithEnumAttributes), @@ -42,27 +49,32 @@ extension EntityCheckTests { } extension Id_LiteralTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__Id_LiteralTests = [ ("test_IntegerLiteral", test_IntegerLiteral), ("test_StringLiteral", test_StringLiteral), ] } extension Relationship_LiteralTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__Relationship_LiteralTests = [ ("test_ArrayLiteral", test_ArrayLiteral), ("test_NilLiteral", test_NilLiteral), ("test_StringLiteral", test_StringLiteral), ] } -#if !os(macOS) public func __allTests() -> [XCTestCaseEntry] { return [ - testCase(Attribute_LiteralTests.__allTests), - testCase(EntityCheckTests.__allTests), - testCase(Id_LiteralTests.__allTests), - testCase(Relationship_LiteralTests.__allTests), + testCase(Attribute_LiteralTests.__allTests__Attribute_LiteralTests), + testCase(EntityCheckTests.__allTests__EntityCheckTests), + testCase(Id_LiteralTests.__allTests__Id_LiteralTests), + testCase(Relationship_LiteralTests.__allTests__Relationship_LiteralTests), ] } #endif diff --git a/Tests/JSONAPITests/XCTestManifests.swift b/Tests/JSONAPITests/XCTestManifests.swift index 32b0c0a..a7b9e90 100644 --- a/Tests/JSONAPITests/XCTestManifests.swift +++ b/Tests/JSONAPITests/XCTestManifests.swift @@ -1,7 +1,11 @@ +#if !canImport(ObjectiveC) import XCTest extension APIDescriptionTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__APIDescriptionTests = [ ("test_empty", test_empty), ("test_failsMissingMeta", test_failsMissingMeta), ("test_NoDescriptionString", test_NoDescriptionString), @@ -12,7 +16,10 @@ extension APIDescriptionTests { } extension AttributeTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__AttributeTests = [ ("test_AttributeConstructor", test_AttributeConstructor), ("test_EncodedPrimitives", test_EncodedPrimitives), ("test_NullableIsEqualToNonNullableIfNotNil", test_NullableIsEqualToNonNullableIfNotNil), @@ -24,7 +31,10 @@ extension AttributeTests { } extension Attribute_FunctorTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__Attribute_FunctorTests = [ ("test_mapGuaranteed", test_mapGuaranteed), ("test_mapOptionalFailure", test_mapOptionalFailure), ("test_mapOptionalSuccess", test_mapOptionalSuccess), @@ -32,7 +42,10 @@ extension Attribute_FunctorTests { } extension ComputedPropertiesTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ComputedPropertiesTests = [ ("test_ComputedAttributeAccess", test_ComputedAttributeAccess), ("test_ComputedNonAttributeAccess", test_ComputedNonAttributeAccess), ("test_ComputedRelationshipAccess", test_ComputedRelationshipAccess), @@ -42,7 +55,10 @@ extension ComputedPropertiesTests { } extension CustomAttributesTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__CustomAttributesTests = [ ("test_customDecode", test_customDecode), ("test_customEncode", test_customEncode), ("test_customKeysDecode", test_customKeysDecode), @@ -51,7 +67,10 @@ extension CustomAttributesTests { } extension DocumentTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__DocumentTests = [ ("test_errorDocumentFailsWithNoAPIDescription", test_errorDocumentFailsWithNoAPIDescription), ("test_errorDocumentNoMeta", test_errorDocumentNoMeta), ("test_errorDocumentNoMeta_encode", test_errorDocumentNoMeta_encode), @@ -154,7 +173,10 @@ extension DocumentTests { } extension EntityTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__EntityTests = [ ("test_copyIdentifiedByType", test_copyIdentifiedByType), ("test_copyIdentifiedByValue", test_copyIdentifiedByValue), ("test_copyWithNewId", test_copyWithNewId), @@ -202,6 +224,7 @@ extension EntityTests { ("test_optional_relationship_operator_access", test_optional_relationship_operator_access), ("test_optionalNullableRelationshipNulled", test_optionalNullableRelationshipNulled), ("test_optionalNullableRelationshipNulled_encode", test_optionalNullableRelationshipNulled_encode), + ("test_optionalNullableRelationshipOmitted", test_optionalNullableRelationshipOmitted), ("test_optionalToMany_relationship_opeartor_access", test_optionalToMany_relationship_opeartor_access), ("test_optionalToManyIsNotOmitted", test_optionalToManyIsNotOmitted), ("test_optionalToManyIsNotOmitted_encode", test_optionalToManyIsNotOmitted_encode), @@ -227,7 +250,10 @@ extension EntityTests { } extension IncludedTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__IncludedTests = [ ("test_appending", test_appending), ("test_EightDifferentIncludes", test_EightDifferentIncludes), ("test_EightDifferentIncludes_encode", test_EightDifferentIncludes_encode), @@ -256,7 +282,10 @@ extension IncludedTests { } extension LinksTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__LinksTests = [ ("test_linkFailsIfMetaNotFound", test_linkFailsIfMetaNotFound), ("test_linkWithMetadata", test_linkWithMetadata), ("test_linkWithMetadata_encode", test_linkWithMetadata_encode), @@ -270,7 +299,10 @@ extension LinksTests { } extension NonJSONAPIRelatableTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__NonJSONAPIRelatableTests = [ ("test_initialization1", test_initialization1), ("test_initialization2_all_relationships_missing", test_initialization2_all_relationships_missing), ("test_initialization2_all_relationships_there", test_initialization2_all_relationships_there), @@ -278,7 +310,10 @@ extension NonJSONAPIRelatableTests { } extension PolyProxyTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__PolyProxyTests = [ ("test_AsymmetricEncodeDecodeUserA", test_AsymmetricEncodeDecodeUserA), ("test_AsymmetricEncodeDecodeUserB", test_AsymmetricEncodeDecodeUserB), ("test_generalReasonableness", test_generalReasonableness), @@ -289,7 +324,10 @@ extension PolyProxyTests { } extension PolyTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__PolyTests = [ ("test_init_Poly0", test_init_Poly0), ("test_init_Poly1", test_init_Poly1), ("test_init_Poly2", test_init_Poly2), @@ -324,7 +362,10 @@ extension PolyTests { } extension RelationshipTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__RelationshipTests = [ ("test_initToManyWithEntities", test_initToManyWithEntities), ("test_initToManyWithRelationships", test_initToManyWithRelationships), ("test_ToManyRelationship", test_ToManyRelationship), @@ -351,7 +392,10 @@ extension RelationshipTests { } extension ResourceBodyTests { - static let __allTests = [ + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ResourceBodyTests = [ ("test_initializers", test_initializers), ("test_manyResourceBody", test_manyResourceBody), ("test_manyResourceBody_encode", test_manyResourceBody_encode), @@ -363,23 +407,22 @@ extension ResourceBodyTests { ] } -#if !os(macOS) public func __allTests() -> [XCTestCaseEntry] { return [ - testCase(APIDescriptionTests.__allTests), - testCase(AttributeTests.__allTests), - testCase(Attribute_FunctorTests.__allTests), - testCase(ComputedPropertiesTests.__allTests), - testCase(CustomAttributesTests.__allTests), - testCase(DocumentTests.__allTests), - testCase(EntityTests.__allTests), - testCase(IncludedTests.__allTests), - testCase(LinksTests.__allTests), - testCase(NonJSONAPIRelatableTests.__allTests), - testCase(PolyProxyTests.__allTests), - testCase(PolyTests.__allTests), - testCase(RelationshipTests.__allTests), - testCase(ResourceBodyTests.__allTests), + testCase(APIDescriptionTests.__allTests__APIDescriptionTests), + testCase(AttributeTests.__allTests__AttributeTests), + testCase(Attribute_FunctorTests.__allTests__Attribute_FunctorTests), + testCase(ComputedPropertiesTests.__allTests__ComputedPropertiesTests), + testCase(CustomAttributesTests.__allTests__CustomAttributesTests), + testCase(DocumentTests.__allTests__DocumentTests), + testCase(EntityTests.__allTests__EntityTests), + testCase(IncludedTests.__allTests__IncludedTests), + testCase(LinksTests.__allTests__LinksTests), + testCase(NonJSONAPIRelatableTests.__allTests__NonJSONAPIRelatableTests), + testCase(PolyProxyTests.__allTests__PolyProxyTests), + testCase(PolyTests.__allTests__PolyTests), + testCase(RelationshipTests.__allTests__RelationshipTests), + testCase(ResourceBodyTests.__allTests__ResourceBodyTests), ] } #endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 18367a8..450cf0f 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,10 +1,10 @@ import XCTest -import JSONAPITests import JSONAPITestingTests +import JSONAPITests var tests = [XCTestCaseEntry]() -tests += JSONAPITests.__allTests() tests += JSONAPITestingTests.__allTests() +tests += JSONAPITests.__allTests() XCTMain(tests) From 880894d026133bc954a0d56fc749943b55907af1 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Thu, 25 Jul 2019 07:38:21 -0700 Subject: [PATCH 13/13] small fix for example in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9567cb4..993e727 100644 --- a/README.md +++ b/README.md @@ -510,6 +510,8 @@ public enum ResourceObjectDescription2: JSONAPI.ResourceObjectDescription { case wholeOtherThing = "coolProperty" } } + + public typealias Relationships = NoRelationships } ```