Compare commits

...

16 Commits

Author SHA1 Message Date
Mathew Polzin 109e15d741 Add convenience method for default decoding of attributes. add tests for custom decoding and encoding as well as custom coding keys. add documentation. 2018-12-27 18:18:34 -08:00
Mathew Polzin 72180f64ef clarify some things about computed attribute properties. 2018-12-27 16:27:10 -08:00
Mathew Polzin fc962f9a0d Lift the constraint that Attributes and Relationships are Codable for EntityProxies. 2018-12-24 07:05:35 -08:00
Mathew Polzin 52eb123166 update documentation 2018-12-22 13:57:38 -08:00
Mathew Polzin 4ef147ec45 Update linuxmain 2018-12-22 13:49:10 -08:00
Mathew Polzin 61074ecc69 Add methods that make it easy to copy an entity with a new ID or copy an unidentified entity and give it an ID 2018-12-22 13:41:34 -08:00
Mathew Polzin 4dbcff6023 Add another way to initialize a nullable attribute to the tests 2018-12-22 13:10:05 -08:00
Mathew Polzin d6e82fab55 get id and type tests into the encoded entity test function 2018-12-21 08:53:55 -08:00
Mathew Polzin 5fa9848f02 test some attribute encoding a bit further 2018-12-21 08:45:51 -08:00
Mathew Polzin f38399a1d6 Add a bit of entity structure testing 2018-12-21 07:50:32 -08:00
Mathew Polzin 28f664326d Added a couple of tests 2018-12-12 21:05:07 -08:00
Mathew Polzin 3b7ef4aeb9 Add access to easy optional access to entire data on Document.Body 2018-12-12 20:05:29 -08:00
Mathew Polzin bb1ed30e89 Rename Document.Body.primaryData to primaryResource 2018-12-12 20:04:14 -08:00
Mathew Polzin dfdc266645 remove a bunch of convenience initializers that appeared to be giving the compiler some strife 2018-12-10 22:45:28 -08:00
Mathew Polzin 585cad0a83 Added literal initialization for nullable Attributes (TestLib) 2018-12-10 21:50:30 -08:00
Mathew Polzin 89abdd4cca fix example code in Playground 2018-12-10 21:11:48 -08:00
23 changed files with 863 additions and 178 deletions
@@ -13,7 +13,7 @@ Please enjoy these examples, but allow me the forced casting and the lack of err
// MARK: - Literal Expressibility
// The JSONAPITestLib provides literal expressibility for key types to
// make creating tests easier
let dog = Dog(id: "1234", attributes: Dog.Attributes(name: "Buddy"), relationships: Dog.Relationships(owner: nil))
let dog = Dog(id: "1234", attributes: Dog.Attributes(name: "Buddy"), relationships: Dog.Relationships(owner: nil), meta: .none, links: .none)
// MARK: - JSON API structure checking
// The JSONAPITestLib provides a `check` function for each Entity type
@@ -13,30 +13,37 @@ let dogFromCode = try! Dog(name: "Buddy", owner: nil)
typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
let singleDogDocument = SingleDogDocument(body: SingleResourceBody(entity: dogFromCode))
let singleDogDocument = SingleDogDocument(apiDescription: .none, body: .init(entity: dogFromCode), includes: .none, meta: .none, links: .none)
let singleDogData = try! JSONEncoder().encode(singleDogDocument)
// MARK: - Parse a request or response body with one Dog in it
let dogResponse = try! JSONDecoder().decode(SingleDogDocument.self, from: singleDogData)
let dogFromData = dogResponse.body.primaryData?.value
let dogFromData = dogResponse.body.primaryResource?.value
let dogOwner: Person.Identifier? = dogFromData.flatMap { $0 ~> \.owner }
// MARKL - Parse a request or response body with one Dog in it using an alternative model
typealias AltSingleDogDocument = JSONAPI.Document<SingleResourceBody<AlternativeDog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
let altDogResponse = try! JSONDecoder().decode(AltSingleDogDocument.self, from: singleDogData)
let altDogFromData = altDogResponse.body.primaryResource?.value
let altDogHuman: Person.Identifier? = altDogFromData.flatMap { $0 ~> \.human }
// MARK: - Create a request or response with multiple people and dogs and houses included
let personIds = [Person.Identifier(), Person.Identifier()]
let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner: personIds[0]), Dog(name: "Travis", owner: personIds[1])]
let houses = [House(), House()]
let houses = [House(attributes: .none, relationships: .none, meta: .none, links: .none), House(attributes: .none, relationships: .none, meta: .none, links: .none)]
let people = try! [Person(id: personIds[0], name: ["Gary", "Doe"], favoriteColor: "Orange-Red", friends: [], dogs: [dogs[0], dogs[1]], home: houses[0]), Person(id: personIds[1], name: ["Elise", "Joy"], favoriteColor: "Red", friends: [], dogs: [dogs[2]], home: houses[1])]
typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoAPIDescription, UnknownJSONAPIError>
let includes = dogs.map { BatchPeopleDocument.Include($0) } + houses.map { BatchPeopleDocument.Include($0) }
let batchPeopleDocument = BatchPeopleDocument(body: .init(entities: people), includes: .init(values: includes))
let batchPeopleDocument = BatchPeopleDocument(apiDescription: .none, body: .init(entities: people), includes: .init(values: includes), meta: .none, links: .none)
let batchPeopleData = try! JSONEncoder().encode(batchPeopleDocument)
// MARK: - Parse a request or response body with multiple people in it and dogs and houses included
let peopleResponse = try! JSONDecoder().decode(BatchPeopleDocument.self, from: batchPeopleData)
let peopleFromData = peopleResponse.body.primaryData?.values
let peopleFromData = peopleResponse.body.primaryResource?.values
let dogsFromData = peopleResponse.body.includes?[Dog.self]
let housesFromData = peopleResponse.body.includes?[House.self]
+32 -4
View File
@@ -25,7 +25,7 @@ extension String: CreatableRawIdType {
// MARK: - typealiases for convenience
public typealias ExampleEntity<Description: EntityDescription> = Entity<Description, NoMetadata, NoLinks, String>
public typealias ToOne<E: OptionalRelatable> = ToOneRelationship<E, NoMetadata, NoLinks>
public typealias ToOne<E: Identifiable> = ToOneRelationship<E, NoMetadata, NoLinks>
public typealias ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLinks>
// MARK: - A few resource objects (entities)
@@ -64,7 +64,7 @@ public typealias Person = ExampleEntity<PersonDescription>
public extension Entity where Description == PersonDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
public init(id: Person.Id? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws {
self = try Person(id: id ?? Person.Id(), attributes: .init(name: .init(rawValue: name), favoriteColor: .init(rawValue: favoriteColor)), relationships: .init(friends: .init(entities: friends), dogs: .init(entities: dogs), home: .init(entity: home)))
self = try Person(id: id ?? Person.Id(), attributes: .init(name: .init(rawValue: name), favoriteColor: .init(rawValue: favoriteColor)), relationships: .init(friends: .init(entities: friends), dogs: .init(entities: dogs), home: .init(entity: home)), meta: .none, links: .none)
}
}
@@ -91,13 +91,41 @@ public enum DogDescription: EntityDescription {
public typealias Dog = ExampleEntity<DogDescription>
public enum AlternativeDogDescription: EntityDescription {
public static var type: String { return "dogs" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
public init(name: Attribute<String>) {
self.name = name
}
}
public struct Relationships: JSONAPI.Relationships {
public let human: ToOne<Person?>
public init(human: ToOne<Person?>) {
self.human = human
}
// define custom key mapping:
enum CodingKeys: String, CodingKey {
case human = "owner"
}
}
}
public typealias AlternativeDog = ExampleEntity<AlternativeDogDescription>
public extension Entity where Description == DogDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
public init(name: String, owner: Person?) throws {
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: DogDescription.Relationships(owner: .init(entity: owner)))
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: DogDescription.Relationships(owner: .init(entity: owner)), meta: .none, links: .none)
}
public init(name: String, owner: Person.Id) throws {
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: .init(owner: .init(id: owner)))
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: .init(owner: .init(id: owner)), meta: .none, links: .none)
}
}
+94 -2
View File
@@ -67,7 +67,7 @@ Note that Playground support for importing non-system Frameworks is still a bit
### JSONAPITestLib
#### Entity Validator
- [x] Disallow optional array in `Attribute` (should be empty array, not `null`).
- [x] Only allow `TransformedAttribute` and its derivatives within `Attributes` struct.
- [x] Only allow `TransformedAttribute` and its derivatives as stored properties within `Attributes` struct. Computed properties can still be any type because they do not get encoded or decoded.
- [x] Only allow `ToManyRelationship` and `ToOneRelationship` within `Relationships` struct.
### Potential Improvements
@@ -227,6 +227,11 @@ typealias Attributes = NoAttributes
let favoriteColor: String = person[\.favoriteColor]
```
NOTE: Because of support for computed properties that are not wrapped in `Attribute`, `TransformedAttribute`, or `ValidatedAttribute`, the compiler cannot always infer the type of thing you want back when using subscript attribute access. The following code is ambiguous about whether it should return a `String` or an `Attribute<String>`:
```
let favoriteColor = person[\.favoriteColor]
```
#### `Transformer`
Sometimes you need to use a type that does not encode or decode itself in the way you need to represent it as a serialized JSON object. For example, the Swift `Foundation` type `Date` can encode/decode itself to `Double` out of the box, but you might want to represent dates as ISO 8601 compliant `String`s instead. The Foundation library `JSONDecoder` has a setting to make this adjustment, but for the sake of an example, you could create a `Transformer`.
@@ -265,7 +270,7 @@ You can also creator `Validators` and `ValidatedAttribute`s. A `Validator` is ju
#### Computed `Attribute`
You can add computed properties to your `EntityDescription.Attributes` struct if you would like to expose attributes that are not explicitly represented by the JSON. These computed properties should still have the type `Attribute` because that way you can take advantage of the quick access provided by `Entity`'s subscript operator. Here's an example of how you might take the `Person[\.name]` attribute from the example above and create a `fullName` computed property.
You can add computed properties to your `EntityDescription.Attributes` struct if you would like to expose attributes that are not explicitly represented by the JSON. These computed properties do not have to be wrapped in `Attribute`, `ValidatedAttribute`, or `TransformedAttribute`. This allows computed attributes to be of types that are not `Codable`. Here's an example of how you might take the `Person[\.name]` attribute from the example above and create a `fullName` computed property.
```
public var fullName: Attribute<String> {
@@ -273,6 +278,23 @@ public var fullName: Attribute<String> {
}
```
### Copying `Entities`
`Entity` is a value type, so copying is its default behavior. There are two common mutations you might want to make when copying an `Entity`:
1. Assigning a new `Identifier` to the copy of an identified `Entity`.
2. Assigning a new `Identifier` to the copy of an unidentified `Entity`.
The above can be accomplished with code like the following:
```
// use case 1
let person1 = person.withNewIdentifier()
// use case 2
let newlyIdentifiedPerson1 = unidentifiedPerson.identified(byType: String.self)
let newlyIdentifiedPerson2 = unidentifiedPerson.identified(by: "2232")
```
### `JSONAPI.Document`
The entirety of a JSON API request or response is encoded or decoded from- or to a `Document`. As an example, a JSON API response containing one `Person` and no included entities could be decoded as follows:
@@ -377,5 +399,75 @@ extension String: CreatableRawIdType {
}
```
### Custom Attribute or Relationship Key Mapping
There is not anything special going on at the `JSONAPI.Attributes` and `JSONAPI.Relationships` levels, so you can easily provide custom key mappings by taking advantage of `Codable`'s `CodingKeys` pattern. Here are two models that will encode/decode equivalently but offer different naming in your codebase:
```
public enum EntityDescription1: JSONAPI.EntityDescription {
public static var type: String { return "entity" }
public struct Attributes: JSONAPI.Attributes {
public let coolProperty: Attribute<String>
}
public typealias Relationships = NoRelationships
}
public enum EntityDescription2: JSONAPI.EntityDescription {
public static var type: String { return "entity" }
public struct Attributes: JSONAPI.Attributes {
public let wholeOtherThing: Attribute<String>
enum CodingKeys: String, CodingKey {
case wholeOtherThing = "coolProperty"
}
}
}
```
### Custom Attribute Encode/Decode
You can safely provide your own encoding or decoding functions for your Attributes struct if you need to as long as you are careful that your encode operation correctly reverses your decode operation. Although this is generally not necessary, `AttributeType` provides a convenience method to make your decoding a bit less boilerplate ridden. This is what it looks like:
```
public enum EntityDescription1: JSONAPI.EntityDescription {
public static var type: String { return "entity" }
public struct Attributes: JSONAPI.Attributes {
public let property1: Attribute<String>
public let property2: Attribute<Int>
public let property3: Attribute<String>
public let weirdThing: Attribute<String>
enum CodingKeys: String, CodingKey {
case property1
case property2
case property3
}
}
public typealias Relationships = NoRelationships
}
extension EntityDescription1.Attributes {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
property1 = try .defaultDecoding(from: container, forKey: .property1)
property2 = try .defaultDecoding(from: container, forKey: .property2)
property3 = try .defaultDecoding(from: container, forKey: .property3)
weirdThing = "hello world"
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(property1, forKey: .property1)
try container.encode(property2, forKey: .property2)
try container.encode(property3, forKey: .property3)
}
}
```
# JSONAPITestLib
The `JSONAPI` framework is packaged with a test library to help you test your `JSONAPI` integration. The test library is called `JSONAPITestLib`. It provides literal expressibility for `Attribute`, `ToOneRelationship`, and `Id` in many situations so that you can easily write test `Entity` values into your unit tests. It also provides a `check()` function for each `Entity` type that can be used to catch problems with your `JSONAPI` structures that are not caught by Swift's type system. You can see the `JSONAPITestLib` in action in the Playground included with the `JSONAPI` repository.
+8 -2
View File
@@ -66,8 +66,13 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
guard case let .errors(errors, meta: _, links: _) = self else { return nil }
return errors
}
public var data: Data? {
guard case let .data(data) = self else { return nil }
return data
}
public var primaryData: PrimaryResourceBody? {
public var primaryResource: PrimaryResourceBody? {
guard case let .data(data) = self else { return nil }
return data.primary
}
@@ -110,7 +115,7 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
self.apiDescription = apiDescription
}
}
/*
extension Document where IncludeType == NoIncludes {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
self.init(apiDescription: apiDescription, body: body, includes: .none, meta: meta, links: links)
@@ -182,6 +187,7 @@ extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, Link
self.init(apiDescription: .none, body: body)
}
}
*/
extension Document {
private enum RootCodingKeys: String, CodingKey {
+9 -1
View File
@@ -11,7 +11,7 @@ public protocol AttributeType: Codable {
// MARK: TransformedAttribute
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: AttributeType where Transformer.From == RawValue {
private let rawValue: RawValue
let rawValue: RawValue
public let value: Transformer.To
@@ -84,3 +84,11 @@ extension TransformedAttribute {
try container.encode(rawValue)
}
}
// MARK: Attribute decoding and encoding defaults
extension AttributeType {
public static func defaultDecoding<Container: KeyedDecodingContainerProtocol>(from container: Container, forKey key: Container.Key) throws -> Self {
return try container.decode(Self.self, forKey: key)
}
}
+51 -12
View File
@@ -8,11 +8,11 @@
/// A JSON API structure within an Entity that contains
/// named properties of types `ToOneRelationship` and
/// `ToManyRelationship`.
public typealias Relationships = Codable & Equatable
public protocol Relationships: Codable & Equatable {}
/// A JSON API structure within an Entity that contains
/// properties of any types that are JSON encodable.
public typealias Attributes = Codable & Equatable
public protocol Attributes: Codable & Equatable {}
/// Can be used as `Relationships` Type for Entities that do not
/// have any Relationships.
@@ -32,21 +32,25 @@ public protocol JSONTyped {
static var type: String { get }
}
/// An `EntityProxyDescription` is an `EntityDescription`
/// without Codable conformance.
public protocol EntityProxyDescription: JSONTyped {
associatedtype Attributes: Equatable
associatedtype Relationships: Equatable
}
/// An `EntityDescription` describes a JSON API
/// Resource Object. The Resource Object
/// itself is encoded and decoded as an
/// `Entity`, which gets specialized on an
/// `EntityDescription`.
public protocol EntityDescription: JSONTyped {
associatedtype Attributes: JSONAPI.Attributes
associatedtype Relationships: JSONAPI.Relationships
}
public protocol EntityDescription: EntityProxyDescription where Attributes: JSONAPI.Attributes, Relationships: JSONAPI.Relationships {}
/// EntityProxy is a protocol that can be used to create
/// types that _act_ like Entities but cannot be encoded
/// or decoded as Entities.
public protocol EntityProxy: Equatable, JSONTyped {
associatedtype Description: EntityDescription
associatedtype Description: EntityProxyDescription
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
typealias Id = JSONAPI.Id<EntityRawIdType, Self>
@@ -75,7 +79,9 @@ extension EntityProxy {
/// EntityType is the protocol that Entity conforms to. This
/// protocol lets other types accept any Entity as a generic
/// specialization.
public protocol EntityType: EntityProxy, PrimaryResource {
public protocol EntityType: EntityProxy, PrimaryResource where Description: EntityDescription {
associatedtype Meta: JSONAPI.Meta
associatedtype Links: JSONAPI.Links
}
public protocol IdentifiableEntityType: EntityType, Relatable where EntityRawIdType: JSONAPI.RawIdType {}
@@ -85,6 +91,10 @@ public protocol IdentifiableEntityType: EntityType, Relatable where EntityRawIdT
/// "Resource Object."
/// See https://jsonapi.org/format/#document-resource-objects
public struct Entity<Description: JSONAPI.EntityDescription, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, EntityRawIdType: JSONAPI.MaybeRawId>: EntityType {
public typealias Meta = MetaType
public typealias Links = LinksType
/// The `Entity`'s Id. This can be of type `Unidentified` if
/// the entity is being created clientside and the
/// server is being asked to create a unique Id. Otherwise,
@@ -143,6 +153,7 @@ extension Entity where EntityRawIdType == Unidentified {
}
}
/*
extension Entity where Description.Attributes == NoAttributes {
public init(id: Entity.Id, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.init(id: id, attributes: NoAttributes(), relationships: relationships, meta: meta, links: links)
@@ -370,15 +381,21 @@ extension Entity where MetaType == NoMetadata, LinksType == NoLinks, EntityRawId
self.init(attributes: attributes, relationships: relationships, meta: .none, links: .none)
}
}
*/
// MARK: Pointer for Relationships use.
public extension Entity where EntityRawIdType: JSONAPI.RawIdType {
/// An Entity.Pointer is a `ToOneRelationship` with no metadata or links.
/// This is just a convenient way to reference an Entity given that
/// This is just a convenient way to reference an Entity so that
/// other Entities' Relationships can be built up from it.
public typealias Pointer = ToOneRelationship<Entity, NoMetadata, NoLinks>
/// Entity.Pointers is a `ToManyRelationship` with no metadata or links.
/// This is just a convenient way to reference a bunch of Entities so
/// that other Entities' Relationships can be built up from them.
public typealias Pointers = ToManyRelationship<Entity, NoMetadata, NoLinks>
/// Get a pointer to this entity that can be used as a
/// relationship to another entity.
public var pointer: Pointer {
@@ -390,6 +407,27 @@ public extension Entity where EntityRawIdType: JSONAPI.RawIdType {
}
}
// MARK: Identifying Unidentified Entities
public extension Entity where EntityRawIdType == Unidentified {
/// Create a new Entity from this one with a newly created
/// unique Id of the given type.
public func identified<RawIdType: CreatableRawIdType>(byType: RawIdType.Type) -> Entity<Description, MetaType, LinksType, RawIdType> {
return .init(attributes: attributes, relationships: relationships, meta: meta, links: links)
}
/// Create a new Entity from this one with the given Id.
public func identified<RawIdType: JSONAPI.RawIdType>(by id: RawIdType) -> Entity<Description, MetaType, LinksType, RawIdType> {
return .init(id: Entity<Description, MetaType, LinksType, RawIdType>.Identifier(rawValue: id), attributes: attributes, relationships: relationships, meta: meta, links: links)
}
}
public extension Entity where EntityRawIdType: CreatableRawIdType {
/// Create a copy of this Entity with a new unique Id.
public func withNewIdentifier() -> Entity {
return Entity(attributes: attributes, relationships: relationships, meta: meta, links: links)
}
}
// MARK: Attribute Access
public extension EntityProxy {
/// Access the attribute at the given keypath. This just
@@ -518,9 +556,10 @@ public extension Entity {
let maybeUnidentified = Unidentified() as? EntityRawIdType
id = try maybeUnidentified.map { Entity.Id(rawValue: $0) } ?? container.decode(Entity.Id.self, forKey: .id)
attributes = try (NoAttributes() as? Description.Attributes) ?? container.decode(Description.Attributes.self, forKey: .attributes)
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)
meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta)
@@ -39,6 +39,14 @@ extension TransformedAttribute: ExpressibleByFloatLiteral where RawValue: Expres
}
}
extension Optional: ExpressibleByFloatLiteral where Wrapped: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Wrapped.FloatLiteralType
public init(floatLiteral value: FloatLiteralType) {
self = .some(Wrapped(floatLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByBooleanLiteral where RawValue: ExpressibleByBooleanLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias BooleanLiteralType = RawValue.BooleanLiteralType
@@ -47,6 +55,14 @@ extension TransformedAttribute: ExpressibleByBooleanLiteral where RawValue: Expr
}
}
extension Optional: ExpressibleByBooleanLiteral where Wrapped: ExpressibleByBooleanLiteral {
public typealias BooleanLiteralType = Wrapped.BooleanLiteralType
public init(booleanLiteral value: BooleanLiteralType) {
self = .some(Wrapped(booleanLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByIntegerLiteral where RawValue: ExpressibleByIntegerLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias IntegerLiteralType = RawValue.IntegerLiteralType
@@ -55,6 +71,14 @@ extension TransformedAttribute: ExpressibleByIntegerLiteral where RawValue: Expr
}
}
extension Optional: ExpressibleByIntegerLiteral where Wrapped: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Wrapped.IntegerLiteralType
public init(integerLiteral value: IntegerLiteralType) {
self = .some(Wrapped(integerLiteral: value))
}
}
// regretably, array and dictionary literals are not so easy because Dictionaries and Arrays
// cannot be turned back into variadic arguments to pass onto the RawValue type's constructor.
@@ -79,6 +103,16 @@ extension TransformedAttribute: ExpressibleByDictionaryLiteral where RawValue: D
}
}
extension Optional: DictionaryType where Wrapped: DictionaryType {
public typealias Key = Wrapped.Key
public typealias Value = Wrapped.Value
public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value) {
self = try .some(Wrapped(keysAndValues, uniquingKeysWith: combine))
}
}
public protocol ArrayType {
associatedtype Element
@@ -94,3 +128,11 @@ extension TransformedAttribute: ExpressibleByArrayLiteral where RawValue: ArrayT
self.init(value: RawValue(elements))
}
}
extension Optional: ArrayType where Wrapped: ArrayType {
public typealias Element = Wrapped.Element
public init<S>(_ s: S) where Element == S.Element, S : Sequence {
self = .some(Wrapped(s))
}
}
@@ -11,7 +11,7 @@ import JSONAPITestLib
class Attribute_FunctorTests: XCTestCase {
func test_mapGuaranteed() {
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)))
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)), relationships: .none, meta: .none, links: .none)
XCTAssertNotNil(entity)
@@ -19,7 +19,7 @@ class Attribute_FunctorTests: XCTestCase {
}
func test_mapOptionalSuccess() {
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)))
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)), relationships: .none, meta: .none, links: .none)
XCTAssertNotNil(entity)
@@ -27,7 +27,7 @@ class Attribute_FunctorTests: XCTestCase {
}
func test_mapOptionalFailure() {
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.5)))
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.5)), relationships: .none, meta: .none, links: .none)
XCTAssertNotNil(entity)
@@ -30,6 +30,18 @@ class AttributeTests: XCTestCase {
XCTAssertNoThrow(try TransformedAttribute<String, TestTransformer>(transformedValue: 10))
}
func test_EncodedPrimitives() {
testEncodedPrimitive(attribute: Attribute<Int>(value: 10))
testEncodedPrimitive(attribute: Attribute<Bool>(value: false))
testEncodedPrimitive(attribute: Attribute<Double>(value: 10.2))
testEncodedPrimitive(attribute: try! TransformedAttribute<Int, IntToString>(rawValue: 10))
testEncodedPrimitive(attribute: try! TransformedAttribute<Int, IntToInt>(rawValue: 10))
testEncodedPrimitive(attribute: try! TransformedAttribute<Int, IntToDouble>(rawValue: 10))
testEncodedPrimitive(attribute: try! TransformedAttribute<String, TestTransformer>(rawValue: "10"))
testEncodedPrimitive(attribute: try! TransformedAttribute<String?, OptionalToString<String>>(rawValue: "10"))
}
func test_NullableIsNullIfNil() {
struct Wrapper: Codable {
let dummy: Attribute<String?>
@@ -68,4 +80,28 @@ extension AttributeTests {
return String(value)
}
}
enum IntToString: Transformer {
public static func transform(_ from: Int) -> String {
return String(from)
}
}
enum IntToInt: Transformer {
public static func transform(_ from: Int) -> Int {
return from + 100
}
}
enum IntToDouble: Transformer {
public static func transform(_ from: Int) -> Double {
return Double(from)
}
}
enum OptionalToString<T>: Transformer {
public static func transform(_ from: T?) -> String {
return String(describing: from)
}
}
}
@@ -0,0 +1,107 @@
//
// CustomAttributesTests.swift
// JSONAPITests
//
// Created by Mathew Polzin on 12/27/18.
//
import XCTest
@testable import JSONAPI
import JSONAPITestLib
class CustomAttributesTests: XCTestCase {
func test_customDecode() {
let entity = decoded(type: CustomAttributeEntity.self, data: customAttributeEntityData)
XCTAssertEqual(entity[\.firstName], "Cool")
XCTAssertEqual(entity[\.name], "Cool Name")
XCTAssertNoThrow(try CustomAttributeEntity.check(entity))
}
func test_customEncode() {
test_DecodeEncodeEquality(type: CustomAttributeEntity.self,
data: customAttributeEntityData)
}
func test_customKeysDecode() {
let entity = decoded(type: CustomKeysEntity.self, data: customAttributeEntityData)
XCTAssertEqual(entity[\.firstNameSilly], "Cool")
XCTAssertEqual(entity[\.lastNameSilly], "Name")
XCTAssertNoThrow(try CustomKeysEntity.check(entity))
}
func test_customKeysEncode() {
test_DecodeEncodeEquality(type: CustomKeysEntity.self,
data: customAttributeEntityData)
}
}
// MARK: - Test Types
extension CustomAttributesTests {
enum CustomAttributeEntityDescription: EntityDescription {
public static var type: String { return "test1" }
public struct Attributes: JSONAPI.Attributes {
let firstName: Attribute<String>
public let name: Attribute<String>
private enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
}
public typealias Relationships = NoRelationships
}
typealias CustomAttributeEntity = BasicEntity<CustomAttributeEntityDescription>
enum CustomKeysEntityDescription: EntityDescription {
public static var type: String { return "test1" }
public struct Attributes: JSONAPI.Attributes {
public let firstNameSilly: Attribute<String>
public let lastNameSilly: Attribute<String>
enum CodingKeys: String, CodingKey {
case firstNameSilly = "firstName"
case lastNameSilly = "lastName"
}
}
public typealias Relationships = NoRelationships
}
typealias CustomKeysEntity = BasicEntity<CustomKeysEntityDescription>
}
extension CustomAttributesTests.CustomAttributeEntityDescription.Attributes {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try .defaultDecoding(from: container, forKey: .firstName)
let lastName = try container.decode(String.self, forKey: .lastName)
name = firstName.map { "\($0) \(lastName)" }
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
let lastName = String(name.value.split(separator: " ")[1])
try container.encode(lastName, forKey: .lastName)
}
}
// MARK: - Test Data
private let customAttributeEntityData = """
{
"type": "test1",
"id": "1",
"attributes": {
"firstName": "Cool",
"lastName": "Name"
}
}
""".data(using: .utf8)!
+76 -76
View File
@@ -16,9 +16,9 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertNil(document.body.primaryData?.value)
XCTAssertNil(document.body.primaryResource?.value)
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.links, NoLinks())
XCTAssertEqual(document.apiDescription, .none)
@@ -35,9 +35,9 @@ class DocumentTests: XCTestCase {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertNil(document.body.primaryData?.value)
XCTAssertNil(document.body.primaryResource?.value)
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.links, NoLinks())
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -73,7 +73,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -98,7 +98,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -123,7 +123,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.meta)
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -146,7 +146,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.meta)
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -171,7 +171,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -196,7 +196,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -222,7 +222,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -246,7 +246,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -271,7 +271,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -299,7 +299,7 @@ extension DocumentTests {
XCTAssertTrue(document.body.isError)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -327,7 +327,7 @@ extension DocumentTests {
data: error_document_with_links)
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -353,7 +353,7 @@ extension DocumentTests {
data: error_document_with_links_with_api_description)
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -380,7 +380,7 @@ extension DocumentTests {
data: error_document_no_metadata)
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
guard case let .errors(errors) = document.body else {
@@ -403,7 +403,7 @@ extension DocumentTests {
data: error_document_no_metadata_with_api_description)
XCTAssertTrue(document.body.isError)
XCTAssertNil(document.body.primaryData)
XCTAssertNil(document.body.primaryResource)
XCTAssertNil(document.body.includes)
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -523,8 +523,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
}
@@ -540,8 +540,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -558,8 +558,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
}
@@ -575,8 +575,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value?.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value?.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -593,8 +593,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
}
@@ -610,8 +610,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.apiDescription.version, "1.0")
@@ -628,8 +628,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
@@ -650,8 +650,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, NoMetadata())
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
@@ -673,8 +673,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
@@ -695,8 +695,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
@@ -726,8 +726,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 1)
XCTAssertEqual(document.body.includes?[Author.self].count, 1)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -744,8 +744,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 1)
XCTAssertEqual(document.body.includes?[Author.self].count, 1)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -763,8 +763,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 1)
XCTAssertEqual(document.body.includes?[Author.self].count, 1)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -782,8 +782,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.includes?.count, 1)
XCTAssertEqual(document.body.includes?[Author.self].count, 1)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -802,8 +802,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
XCTAssertEqual(document.body.links?.link.meta, NoMetadata())
@@ -825,8 +825,8 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.value.id.rawValue, "1")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
XCTAssertEqual(document.body.meta, TestPageMetadata(total: 70, limit: 40, offset: 10))
XCTAssertEqual(document.body.links?.link.url, "https://website.com")
XCTAssertEqual(document.body.links?.link.meta, NoMetadata())
@@ -847,11 +847,11 @@ extension DocumentTests {
// MARK: Poly PrimaryResource Tests
extension DocumentTests {
func test_singleDocument_PolyPrimaryResource() {
let article = Article(id: Id(rawValue: "1"), relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))))
let article = Article(id: Id(rawValue: "1"), attributes: .none, relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))), meta: .none, links: .none)
let document = decoded(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.self, data: single_document_no_includes)
XCTAssertEqual(document.body.primaryData?.value[Article.self], article)
XCTAssertNil(document.body.primaryData?.value[Author.self])
XCTAssertEqual(document.body.primaryResource?.value[Article.self], article)
XCTAssertNil(document.body.primaryResource?.value[Author.self])
}
func test_singleDocument_PolyPrimaryResource_encode() {
@@ -859,11 +859,11 @@ extension DocumentTests {
}
func test_singleDocument_PolyPrimaryResourceWithAPIDescription() {
let article = Article(id: Id(rawValue: "1"), relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))))
let article = Article(id: Id(rawValue: "1"), attributes: .none, relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))), meta: .none, links: .none)
let document = decoded(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, TestAPIDescription, UnknownJSONAPIError>.self, data: single_document_no_includes_with_api_description)
XCTAssertEqual(document.body.primaryData?.value[Article.self], article)
XCTAssertNil(document.body.primaryData?.value[Author.self])
XCTAssertEqual(document.body.primaryResource?.value[Article.self], article)
XCTAssertNil(document.body.primaryResource?.value[Author.self])
XCTAssertEqual(document.apiDescription.version, "1.0")
}
@@ -880,11 +880,11 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.values.count, 3)
XCTAssertEqual(document.body.primaryData?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryData?.values[2].id.rawValue, "3")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.values.count, 3)
XCTAssertEqual(document.body.primaryResource?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryResource?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryResource?.values[2].id.rawValue, "3")
XCTAssertEqual(document.body.includes?.count, 0)
}
@@ -899,11 +899,11 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.values.count, 3)
XCTAssertEqual(document.body.primaryData?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryData?.values[2].id.rawValue, "3")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.values.count, 3)
XCTAssertEqual(document.body.primaryResource?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryResource?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryResource?.values[2].id.rawValue, "3")
XCTAssertEqual(document.body.includes?.count, 0)
XCTAssertEqual(document.apiDescription.version, "1.0")
}
@@ -919,11 +919,11 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.values.count, 3)
XCTAssertEqual(document.body.primaryData?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryData?.values[2].id.rawValue, "3")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.values.count, 3)
XCTAssertEqual(document.body.primaryResource?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryResource?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryResource?.values[2].id.rawValue, "3")
XCTAssertEqual(document.body.includes?.count, 3)
XCTAssertEqual(document.body.includes?[Author.self].count, 3)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
@@ -942,11 +942,11 @@ extension DocumentTests {
XCTAssertFalse(document.body.isError)
XCTAssertNil(document.body.errors)
XCTAssertNotNil(document.body.primaryData)
XCTAssertEqual(document.body.primaryData?.values.count, 3)
XCTAssertEqual(document.body.primaryData?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryData?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryData?.values[2].id.rawValue, "3")
XCTAssertNotNil(document.body.primaryResource)
XCTAssertEqual(document.body.primaryResource?.values.count, 3)
XCTAssertEqual(document.body.primaryResource?.values[0].id.rawValue, "1")
XCTAssertEqual(document.body.primaryResource?.values[1].id.rawValue, "2")
XCTAssertEqual(document.body.primaryResource?.values[2].id.rawValue, "3")
XCTAssertEqual(document.body.includes?.count, 3)
XCTAssertEqual(document.body.includes?[Author.self].count, 3)
XCTAssertEqual(document.body.includes?[Author.self][0].id.rawValue, "33")
+124 -35
View File
@@ -12,39 +12,45 @@ import JSONAPITestLib
class EntityTests: XCTestCase {
func test_relationship_access() {
let entity1 = TestEntity1()
let entity2 = TestEntity2(relationships: .init(other: entity1.pointer))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(other: entity1.pointer), meta: .none, links: .none)
XCTAssertEqual(entity2.relationships.other, entity1.pointer)
}
func test_relationship_operator_access() {
let entity1 = TestEntity1()
let entity2 = TestEntity2(relationships: .init(other: entity1.pointer))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(other: entity1.pointer), meta: .none, links: .none)
XCTAssertEqual(entity2 ~> \.other, entity1.id)
}
func test_optional_relationship_operator_access() {
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity = TestEntity9(attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: .init(entity: entity1, meta: .none, links: .none), optionalNullableOne: nil, optionalMany: .init(entities: [entity1, entity1], meta: .none, links: .none)), meta: .none, links: .none)
XCTAssertEqual(entity ~> \.optionalOne, entity1.id)
}
func test_toMany_relationship_operator_access() {
let entity1 = TestEntity1()
let entity2 = TestEntity1()
let entity4 = TestEntity1()
let entity3 = TestEntity3(relationships: .init(others: .init(pointers: [entity1.pointer, entity2.pointer, entity4.pointer])))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity4 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity3 = TestEntity3(attributes: .none, relationships: .init(others: .init(pointers: [entity1.pointer, entity2.pointer, entity4.pointer])), meta: .none, links: .none)
XCTAssertEqual(entity3 ~> \.others, [entity1.id, entity2.id, entity4.id])
}
func test_optionalToMany_relationship_opeartor_access() {
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity = TestEntity9(attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: nil, optionalNullableOne: nil, optionalMany: .init(entities: [entity1, entity1], meta: .none, links: .none)), meta: .none, links: .none)
XCTAssertEqual(entity ~> \.optionalMany, [entity1.id, entity1.id])
}
func test_relationshipIds() {
let entity1 = TestEntity1()
let entity2 = TestEntity2(relationships: .init(other: entity1.pointer))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(other: entity1.pointer), meta: .none, links: .none)
XCTAssertEqual(entity2.relationships.other.id, entity1.id)
}
@@ -59,32 +65,69 @@ class EntityTests: XCTestCase {
XCTAssertEqual(pointer.links.link1.url, "ok")
}
func test_unidentifiedEntityAttributeAccess() {
let entity = UnidentifiedTestEntity(attributes: .init(me: "hello"), relationships: .none, meta: .none, links: .none)
XCTAssertEqual(entity[\.me], "hello")
}
func test_initialization() {
let entity1 = TestEntity1(id: .init(rawValue: "wow"))
let entity2 = TestEntity2(id: .init(rawValue: "cool"), relationships: .init(other: .init(entity: entity1)))
let _ = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)))
let _ = TestEntity2(id: .init(rawValue: "cool"), relationships: .init(other: .init(entity: entity1)), meta: .none)
let _ = TestEntity3(id: .init(rawValue: "3"), relationships: .init(others: .init(ids: [.init(rawValue: "10"), .init(rawValue: "20"), entity1.id])))
let _ = TestEntity3(id: .init(rawValue: "3"), relationships: .init(others: .none))
let _ = TestEntity4(id: .init(rawValue: "4"), attributes: .init(word: .init(value: "hello"), number: .init(value: 10), array: .init(value: [10.2, 10.3])), relationships: .init(other: entity2.pointer))
let _ = TestEntity5(id: .init(rawValue: "5"), attributes: .init(floater: .init(value: 10.2)))
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)))
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")))
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil))))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: entity1.pointer, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: .init(entities: [], meta: .none, links: .none)))
let entity1 = TestEntity1(id: .init(rawValue: "wow"), attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)), meta: .none, links: .none)
let _ = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)), meta: .none, links: .none)
let _ = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)), meta: .none, links: .none)
let _ = TestEntity3(id: .init(rawValue: "3"), attributes: .none, relationships: .init(others: .init(ids: [.init(rawValue: "10"), .init(rawValue: "20"), entity1.id])), meta: .none, links: .none)
let _ = TestEntity3(id: .init(rawValue: "3"), attributes: .none, relationships: .init(others: .none), meta: .none, links: .none)
let _ = TestEntity4(id: .init(rawValue: "4"), attributes: .init(word: .init(value: "hello"), number: .init(value: 10), array: .init(value: [10.2, 10.3])), relationships: .init(other: entity2.pointer), meta: .none, links: .none)
let _ = TestEntity5(id: .init(rawValue: "5"), attributes: .init(floater: .init(value: 10.2)), relationships: .none, meta: .none, links: .none)
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)), relationships: .none, meta: .none, links: .none)
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")), relationships: .none, meta: .none, links: .none)
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil)), relationships: .none, meta: .none, links: .none))
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(id: nil), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: entity1.pointer, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: .init(entities: [], meta: .none, links: .none)), meta: .none, links: .none)
let e10id1 = TestEntity10.Identifier(rawValue: "hello")
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
let _ = TestEntity10(id: .init(rawValue: "10"), relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])))
XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11))))
let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")))
let _ = UnidentifiedTestEntityWithMeta(attributes: .init(me: .init(value: "hello")), meta: .init(x: "world", y: nil))
let _ = UnidentifiedTestEntityWithLinks(attributes: .init(me: .init(value: "hello")), links: .init(link1: .init(url: "hmmm")))
let _ = TestEntity10(id: .init(rawValue: "10"), attributes: .none, relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])), meta: .none, links: .none)
XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11)), relationships: .none, meta: .none, links: .none))
let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .none)
let _ = UnidentifiedTestEntityWithMeta(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .init(x: "world", y: nil), links: .none)
let _ = UnidentifiedTestEntityWithLinks(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .init(link1: .init(url: "hmmm")))
}
}
// MARK: - Identifying entity copies
extension EntityTests {
func test_copyIdentifiedByType() {
let unidentifiedEntity = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .none)
let identifiedCopy = unidentifiedEntity.identified(byType: String.self)
XCTAssertEqual(unidentifiedEntity.attributes, identifiedCopy.attributes)
XCTAssertEqual(unidentifiedEntity.relationships, identifiedCopy.relationships)
}
func test_copyIdentifiedByValue() {
let unidentifiedEntity = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .none)
let identifiedCopy = unidentifiedEntity.identified(by: "hello")
XCTAssertEqual(unidentifiedEntity.attributes, identifiedCopy.attributes)
XCTAssertEqual(unidentifiedEntity.relationships, identifiedCopy.relationships)
XCTAssertEqual(identifiedCopy.id, "hello")
}
func test_copyWithNewId() {
let identifiedEntity = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let identifiedCopy = identifiedEntity.withNewIdentifier()
XCTAssertNotEqual(identifiedEntity.id, identifiedCopy.id)
}
}
@@ -98,6 +141,8 @@ extension EntityTests {
XCTAssert(type(of: entity.relationships) == NoRelationships.self)
XCTAssert(type(of: entity.attributes) == NoAttributes.self)
XCTAssertNoThrow(try TestEntity1.check(entity))
testEncoded(entity: entity)
}
func test_EntityNoRelationshipsNoAttributes_encode() {
@@ -113,6 +158,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.floater], 123.321)
XCTAssertNoThrow(try TestEntity5.check(entity))
testEncoded(entity: entity)
}
func test_EntityNoRelationshipsSomeAttributes_encode() {
@@ -128,6 +175,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.others).map { $0.rawValue }, ["364B3B69-4DF1-467F-B52E-B0C9E44F666E"])
XCTAssertNoThrow(try TestEntity3.check(entity))
testEncoded(entity: entity)
}
func test_EntitySomeRelationshipsNoAttributes_encode() {
@@ -143,6 +192,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.number], 992299)
XCTAssertEqual((entity ~> \.other).rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
XCTAssertNoThrow(try TestEntity4.check(entity))
testEncoded(entity: entity)
}
func test_EntitySomeRelationshipsSomeAttributes_encode() {
@@ -162,6 +213,8 @@ extension EntityTests {
XCTAssertNil(entity[\.maybeHere])
XCTAssertEqual(entity[\.maybeNull], "World")
XCTAssertNoThrow(try TestEntity6.check(entity))
testEncoded(entity: entity)
}
func test_entityOneOmittedAttribute_encode() {
@@ -177,6 +230,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.maybeHere], "World")
XCTAssertNil(entity[\.maybeNull])
XCTAssertNoThrow(try TestEntity6.check(entity))
testEncoded(entity: entity)
}
func test_entityOneNullAttribute_encode() {
@@ -192,6 +247,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.maybeHere], "World")
XCTAssertEqual(entity[\.maybeNull], "!")
XCTAssertNoThrow(try TestEntity6.check(entity))
testEncoded(entity: entity)
}
func test_entityAllAttribute_encode() {
@@ -208,7 +265,7 @@ extension EntityTests {
XCTAssertNil(entity[\.maybeNull])
XCTAssertNoThrow(try TestEntity6.check(entity))
print(encodable: entity)
testEncoded(entity: entity)
}
func test_entityOneNullAndOneOmittedAttribute_encode() {
@@ -229,7 +286,7 @@ extension EntityTests {
XCTAssertNil(entity[\.maybeHereMaybeNull])
XCTAssertNoThrow(try TestEntity7.check(entity))
print(encodable: entity)
testEncoded(entity: entity)
}
func test_NullOptionalNullableAttribute_encode() {
@@ -244,6 +301,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.here], "Hello")
XCTAssertEqual(entity[\.maybeHereMaybeNull], "World")
XCTAssertNoThrow(try TestEntity7.check(entity))
testEncoded(entity: entity)
}
func test_NonNullOptionalNullableAttribute_encode() {
@@ -265,6 +324,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.doubleFromInt], 22.0)
XCTAssertEqual(entity[\.nullToString], "nil")
XCTAssertNoThrow(try TestEntity8.check(entity))
testEncoded(entity: entity)
}
func test_IntToString_encode() {
@@ -299,6 +360,8 @@ extension EntityTests {
XCTAssertNil(entity ~> \.optionalOne)
XCTAssertEqual((entity ~> \.optionalNullableOne)?.rawValue, "1229")
XCTAssertNoThrow(try TestEntity9.check(entity))
testEncoded(entity: entity)
}
func test_nullableRelationshipNotNullOrOmitted_encode() {
@@ -314,6 +377,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.one).rawValue, "4459")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
testEncoded(entity: entity)
}
func test_nullableRelationshipNotNull_encode() {
@@ -329,6 +394,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.one).rawValue, "4459")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
testEncoded(entity: entity)
}
func test_optionalNullableRelationshipNulled_encode() {
@@ -344,6 +411,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.one).rawValue, "4452")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
testEncoded(entity: entity)
}
func test_nullableRelationshipIsNull_encode() {
@@ -360,6 +429,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.optionalMany)?[0].rawValue, "332223")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
testEncoded(entity: entity)
}
func test_optionalToManyIsNotOmitted_encode() {
@@ -377,6 +448,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.selfRef).rawValue, "1")
XCTAssertNoThrow(try TestEntity10.check(entity))
testEncoded(entity: entity)
}
func test_RleationshipsOfSameType_encode() {
@@ -395,6 +468,8 @@ extension EntityTests {
XCTAssertNil(entity[\.me])
XCTAssertEqual(entity.id, .unidentified)
XCTAssertNoThrow(try UnidentifiedTestEntity.check(entity))
testEncoded(entity: entity)
}
func test_UnidentifiedEntity_encode() {
@@ -409,6 +484,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.me], "unknown")
XCTAssertEqual(entity.id, .unidentified)
XCTAssertNoThrow(try UnidentifiedTestEntity.check(entity))
testEncoded(entity: entity)
}
func test_UnidentifiedEntityWithAttributes_encode() {
@@ -429,6 +506,8 @@ extension EntityTests {
XCTAssertEqual(entity.meta.x, "world")
XCTAssertEqual(entity.meta.y, 5)
XCTAssertNoThrow(try UnidentifiedTestEntityWithMeta.check(entity))
testEncoded(entity: entity)
}
func test_UnidentifiedEntityWithAttributesAndMeta_encode() {
@@ -444,6 +523,8 @@ extension EntityTests {
XCTAssertEqual(entity.id, .unidentified)
XCTAssertEqual(entity.links.link1, .init(url: "https://image.com/image.png"))
XCTAssertNoThrow(try UnidentifiedTestEntityWithLinks.check(entity))
testEncoded(entity: entity)
}
func test_UnidentifiedEntityWithAttributesAndLinks_encode() {
@@ -461,6 +542,8 @@ extension EntityTests {
XCTAssertEqual(entity.meta.y, 5)
XCTAssertEqual(entity.links.link1, .init(url: "https://image.com/image.png"))
XCTAssertNoThrow(try UnidentifiedTestEntityWithMetaAndLinks.check(entity))
testEncoded(entity: entity)
}
func test_UnidentifiedEntityWithAttributesAndMetaAndLinks_encode() {
@@ -478,6 +561,8 @@ extension EntityTests {
XCTAssertEqual(entity.meta.x, "world")
XCTAssertEqual(entity.meta.y, 5)
XCTAssertNoThrow(try TestEntity4WithMeta.check(entity))
testEncoded(entity: entity)
}
func test_EntitySomeRelationshipsSomeAttributesWithMeta_encode() {
@@ -494,6 +579,8 @@ extension EntityTests {
XCTAssertEqual((entity ~> \.other).rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
XCTAssertEqual(entity.links.link1, .init(url: "https://image.com/image.png"))
XCTAssertNoThrow(try TestEntity4WithLinks.check(entity))
testEncoded(entity: entity)
}
func test_EntitySomeRelationshipsSomeAttributesWithLinks_encode() {
@@ -512,6 +599,8 @@ extension EntityTests {
XCTAssertEqual(entity.meta.y, 5)
XCTAssertEqual(entity.links.link1, .init(url: "https://image.com/image.png"))
XCTAssertNoThrow(try TestEntity4WithMetaAndLinks.check(entity))
testEncoded(entity: entity)
}
func test_EntitySomeRelationshipsSomeAttributesWithMetaAndLinks_encode() {
@@ -15,28 +15,121 @@ class Attribute_LiteralTests: XCTestCase {
XCTAssertEqual(Attribute<String>(value: "hello"), "hello")
}
func test_NullableStringLiteral() {
XCTAssertEqual(Attribute<String?>(value: "world"), "world")
}
func test_OptionalStringLiteral() {
let x: Attribute<String>? = .init(value: "ok")
XCTAssertEqual(x, "ok")
}
func test_NullableOptionalStringLiteral() {
let x: Attribute<String?>? = .init(value: "hello")
XCTAssertEqual(x, "hello")
}
func test_BooleanLiteral() {
XCTAssertEqual(Attribute<Bool>(value: false), false)
}
func test_NullableBooleanLiteral() {
XCTAssertEqual(Attribute<Bool?>(value: true), true)
}
func test_OptionalBooleanLiteral() {
let x: Attribute<Bool>? = .init(value: false)
XCTAssertEqual(x, false)
}
func test_NullableOptionalBooleanLiteral() {
let x: Attribute<Bool?>? = .init(value: true)
XCTAssertEqual(x, true)
}
func test_IntegerLiteral() {
XCTAssertEqual(Attribute<Int>(value: 12), 12)
}
func test_NullableIntegerLiteral() {
XCTAssertEqual(Attribute<Int?>(value: 13), 13)
}
func test_OptionalIntegerLiteral() {
let x: Attribute<Int>? = .init(value: 14)
XCTAssertEqual(x, 14)
}
func test_NullableOptionalIntegerLiteral() {
let x: Attribute<Int?>? = .init(value: 15)
XCTAssertEqual(x, 15)
}
func test_FloatLiteral() {
XCTAssertEqual(Attribute<Float>(value: 1.2), 1.2)
XCTAssertEqual(Attribute<Double>(value: 1.2), 1.2)
}
func test_NullableFloatLiteral() {
XCTAssertEqual(Attribute<Float?>(value: 2.3), 2.3)
XCTAssertEqual(Attribute<Double?>(value: 3.4), 3.4)
}
func test_OptionalFloatLiteral() {
let x: Attribute<Float>? = .init(value: 2.3)
let y: Attribute<Double>? = .init(value: 3.4)
XCTAssertEqual(x, 2.3)
XCTAssertEqual(y, 3.4)
}
func test_NullableOptionalFloatLiteral() {
let x: Attribute<Float?>? = .init(value: 2.3)
let y: Attribute<Double?>? = .init(value: 3.4)
XCTAssertEqual(x, 2.3)
XCTAssertEqual(y, 3.4)
}
func test_ArrayLiteral() {
XCTAssertEqual(Attribute<[String]>(value: ["hello", "world"]), ["hello", "world"])
}
func test_NullableArrayLiteral() {
XCTAssertEqual(Attribute<[String]?>(value: ["hello", "world"]), ["hello", "world"])
}
func test_OptionalArrayLiteral() {
let x: Attribute<[String]>? = .init(value: ["hello", "world"])
XCTAssertEqual(x, ["hello", "world"])
}
func test_NullableOptionalArrayLiteral() {
let x: Attribute<[String]?>? = .init(value: ["hello", "world"])
XCTAssertEqual(x, ["hello", "world"])
}
func test_DictionaryLiteral() {
XCTAssertEqual(Attribute<[String : Int]>(value: ["hello": 1]), ["hello": 1])
}
func test_NullableDictionaryLiteral() {
XCTAssertEqual(Attribute<[String: Int]?>(value: ["hello": 1]), ["hello": 1])
}
func test_OptionalDictionaryLiteral() {
let x: Attribute<[String: Int]>? = .init(value: ["hello": 1])
XCTAssertEqual(x, ["hello": 1])
}
func test_NullableOptionalDictionaryLiteral() {
let x: Attribute<[String: Int]?>? = .init(value: ["hello": 1])
XCTAssertEqual(x, ["hello": 1])
}
func test_NilLiteral() {
XCTAssertEqual(Attribute<String?>(value: nil), nil)
}
func test_OptionalNilLiteral() {
let _: Attribute<String?>? = nil
}
}
@@ -13,27 +13,27 @@ import JSONAPITestLib
// in this file.
class EntityCheckTests: XCTestCase {
func test_failsWithEnumAttributes() {
let entity = EnumAttributesEntity(attributes: .hello)
let entity = EnumAttributesEntity(attributes: .hello, relationships: .none, meta: .none, links: .none)
XCTAssertThrowsError(try EnumAttributesEntity.check(entity))
}
func test_failsWithEnumRelationships() {
let entity = EnumRelationshipsEntity(relationships: .hello)
let entity = EnumRelationshipsEntity(attributes: .none, relationships: .hello, meta: .none, links: .none)
XCTAssertThrowsError(try EnumRelationshipsEntity.check(entity))
}
func test_failsWithBadAttribute() {
let entity = BadAttributeEntity(attributes: .init(x: "ok", y: "not ok"))
let entity = BadAttributeEntity(attributes: .init(x: "ok", y: "not ok"), relationships: .none, meta: .none, links: .none)
XCTAssertThrowsError(try BadAttributeEntity.check(entity))
}
func test_failsWithBadRelationship() {
let entity = BadRelationshipEntity(relationships: .init(x: OkEntity().pointer, y: OkEntity().id))
let entity = BadRelationshipEntity(attributes: .none, relationships: .init(x: OkEntity(attributes: .none, relationships: .none, meta: .none, links: .none).pointer, y: OkEntity(attributes: .none, relationships: .none, meta: .none, links: .none).id), meta: .none, links: .none)
XCTAssertThrowsError(try BadRelationshipEntity.check(entity))
}
func test_failsWithOptionalArrayAttribute() {
let entity = OptionalArrayAttributeEntity(attributes: .init(x: ["hello"], y: nil))
let entity = OptionalArrayAttributeEntity(attributes: .init(x: ["hello"], y: nil), relationships: .none, meta: .none, links: .none)
XCTAssertThrowsError(try OptionalArrayAttributeEntity.check(entity))
}
}
@@ -61,7 +61,7 @@ extension EntityCheckTests {
enum EnumAttributesDescription: EntityDescription {
public static var type: String { return "hello" }
public enum Attributes: Codable, Equatable {
public enum Attributes: JSONAPI.Attributes {
case hello
public init(from decoder: Decoder) throws {
@@ -82,7 +82,7 @@ extension EntityCheckTests {
public typealias Attributes = NoAttributes
public enum Relationships: Codable, Equatable {
public enum Relationships: JSONAPI.Relationships {
case hello
public init(from decoder: Decoder) throws {
@@ -13,7 +13,7 @@ class NonJSONAPIRelatableTests: XCTestCase {
let e1 = NonJSONAPIEntity(id: .init(rawValue: "hello"))
let e2 = NonJSONAPIEntity(id: .init(rawValue: "world"))
let entity = TestEntity(relationships: .init(one: .init(id: e1.id), many: .init(ids: [e1.id, e2.id])))
let entity = TestEntity(attributes: .none, relationships: .init(one: .init(id: e1.id), many: .init(ids: [e1.id, e2.id])), meta: .none, links: .none)
XCTAssertEqual(entity ~> \.one, e1.id)
XCTAssertEqual(entity ~> \.many, [e1.id, e2.id])
@@ -25,7 +25,7 @@ class NonJSONAPIRelatableTests: XCTestCase {
let e1 = NonJSONAPIEntity(id: .init(rawValue: "hello"))
let e2 = NonJSONAPIEntity(id: .init(rawValue: "world"))
let entity = TestEntity2(relationships: .init(nullableOne: .init(id: e1.id), nullableMaybeOne: .init(id: e2.id), maybeOne: .init(id: e2.id), maybeMany: .init(ids: [e2.id, e1.id])))
let entity = TestEntity2(attributes: .none, relationships: .init(nullableOne: .init(id: e1.id), nullableMaybeOne: .init(id: e2.id), maybeOne: .init(id: e2.id), maybeMany: .init(ids: [e2.id, e1.id])), meta: .none, links: .none)
XCTAssertEqual((entity ~> \.nullableOne)?.rawValue, "hello")
XCTAssertEqual((entity ~> \.nullableMaybeOne)?.rawValue, "world")
@@ -35,8 +35,8 @@ class NonJSONAPIRelatableTests: XCTestCase {
func test_initialization2_all_relationships_missing() {
let entity = TestEntity2(relationships: .init(nullableOne: .init(id: nil), nullableMaybeOne: .init(id: nil), maybeOne: nil, maybeMany: nil))
let entity2 = TestEntity2(relationships: .init(nullableOne: .init(id: nil), nullableMaybeOne: nil, maybeOne: nil, maybeMany: nil))
let entity = TestEntity2(attributes: .none, relationships: .init(nullableOne: .init(id: nil), nullableMaybeOne: .init(id: nil), maybeOne: nil, maybeMany: nil), meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(nullableOne: .init(id: nil), nullableMaybeOne: nil, maybeOne: nil, maybeMany: nil), meta: .none, links: .none)
XCTAssertNil((entity ~> \.nullableOne))
XCTAssertNil((entity ~> \.nullableMaybeOne))
+11 -4
View File
@@ -23,6 +23,7 @@ public class PolyProxyTests: XCTestCase {
XCTAssertEqual(polyUserA[\.name], "Ken Moore")
XCTAssertEqual(polyUserA.id, "1")
XCTAssertEqual(polyUserA.relationships, .none)
XCTAssertEqual(polyUserA[\.x], .init(x: "y"))
}
func test_UserAAndBEncodeEquality() {
@@ -57,6 +58,7 @@ public class PolyProxyTests: XCTestCase {
XCTAssertEqual(polyUserB[\.name], "Ken Less")
XCTAssertEqual(polyUserB.id, "2")
XCTAssertEqual(polyUserB.relationships, .none)
XCTAssertEqual(polyUserB[\.x], .init(x: "y"))
}
}
@@ -111,9 +113,9 @@ extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == Po
public var attributes: SharedUserDescription.Attributes {
switch self {
case .a(let a):
return .init(name: .init(value: "\(a[\.firstName]) \(a[\.lastName])"))
return .init(name: .init(value: "\(a[\.firstName]) \(a[\.lastName])"), x: .init(x: "y"))
case .b(let b):
return .init(name: .init(value: b[\.name].joined(separator: " ")))
return .init(name: .init(value: b[\.name].joined(separator: " ")), x: .init(x: "y"))
}
}
@@ -121,14 +123,19 @@ extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == Po
return .none
}
public enum SharedUserDescription: EntityDescription {
public enum SharedUserDescription: EntityProxyDescription {
public static var type: String { return A.type }
public struct Attributes: JSONAPI.Attributes {
public struct Attributes: Equatable {
let name: Attribute<String>
let x: SomeRandomThingThatIsNotCodable
}
public typealias Relationships = NoRelationships
public struct SomeRandomThingThatIsNotCodable: Equatable {
let x: String
}
}
public typealias Description = SharedUserDescription
+7 -7
View File
@@ -15,13 +15,13 @@ class PolyTests: XCTestCase {
}
func test_init_Poly1() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly1(entity)
XCTAssertEqual(poly.a, entity)
}
func test_init_Poly2() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly2<TestEntity5, TestEntity>(entity)
XCTAssertEqual(poly.a, entity)
XCTAssertNil(poly.b)
@@ -32,7 +32,7 @@ class PolyTests: XCTestCase {
}
func test_init_Poly3() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly3<TestEntity5, TestEntity, TestEntity2>(entity)
XCTAssertEqual(poly.a, entity)
XCTAssertNil(poly.b)
@@ -50,7 +50,7 @@ class PolyTests: XCTestCase {
}
func test_init_Poly4() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly4<TestEntity5, TestEntity, TestEntity2, TestEntity3>(entity)
XCTAssertEqual(poly.a, entity)
XCTAssertNil(poly.b)
@@ -77,7 +77,7 @@ class PolyTests: XCTestCase {
}
func test_init_Poly5() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly5<TestEntity5, TestEntity, TestEntity2, TestEntity3, TestEntity4>(entity)
XCTAssertEqual(poly.a, entity)
XCTAssertNil(poly.b)
@@ -115,7 +115,7 @@ class PolyTests: XCTestCase {
}
func test_init_Poly6() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly6<TestEntity5, TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6>(entity)
XCTAssertEqual(poly.a, entity)
XCTAssertNil(poly.b)
@@ -166,7 +166,7 @@ class PolyTests: XCTestCase {
}
func test_init_Poly7() {
let entity = TestEntity5()
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
let poly = Poly7<TestEntity5, TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7>(entity)
XCTAssertEqual(poly.a, entity)
XCTAssertNil(poly.b)
@@ -11,10 +11,10 @@ import JSONAPI
class RelationshipTests: XCTestCase {
func test_initToManyWithEntities() {
let entity1 = TestEntity1()
let entity2 = TestEntity1()
let entity3 = TestEntity1()
let entity4 = TestEntity1()
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity3 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity4 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let relationship = ToManyRelationship<TestEntity1, NoMetadata, NoLinks>(entities: [entity1, entity2, entity3, entity4])
XCTAssertEqual(relationship.ids.count, 4)
@@ -22,10 +22,10 @@ class RelationshipTests: XCTestCase {
}
func test_initToManyWithRelationships() {
let entity1 = TestEntity1()
let entity2 = TestEntity1()
let entity3 = TestEntity1()
let entity4 = TestEntity1()
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity3 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity4 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let relationship = ToManyRelationship<TestEntity1, NoMetadata, NoLinks>(pointers: [entity1.pointer, entity2.pointer, entity3.pointer, entity4.pointer])
XCTAssertEqual(relationship.ids.count, 4)
@@ -151,7 +151,7 @@ extension RelationshipTests {
}
func test_ToOneNullableIsEqualToNonNullableIfNotNil() {
let entity = TestEntity1()
let entity = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let relationship1 = ToOneNonNullable(entity: entity)
let relationship2 = ToOneNullable(entity: entity)
@@ -12,8 +12,8 @@ class ResourceBodyTests: XCTestCase {
func test_initializers() {
let articles = [
Article(attributes: .init(title: "hello")),
Article(attributes: .init(title: "world"))
Article(attributes: .init(title: "hello"), relationships: .none, meta: .none, links: .none),
Article(attributes: .init(title: "world"), relationships: .none, meta: .none, links: .none)
]
let _ = SingleResourceBody(entity: articles[0])
let _ = ManyResourceBody(entities: articles)
@@ -25,7 +25,7 @@ class ResourceBodyTests: XCTestCase {
data: single_resource_body)
XCTAssertEqual(body.value, Article(id: Id<String, Article>(rawValue: "1"),
attributes: ArticleType.Attributes(title: try! .init(rawValue: "JSON:API paints my bikeshed!"))))
attributes: ArticleType.Attributes(title: try! .init(rawValue: "JSON:API paints my bikeshed!")), relationships: .none, meta: .none, links: .none))
}
func test_singleResourceBody_encode() {
@@ -38,9 +38,9 @@ class ResourceBodyTests: XCTestCase {
data: many_resource_body)
XCTAssertEqual(body.values, [
Article(id: .init(rawValue: "1"), attributes: try! .init(title: .init(rawValue: "JSON:API paints my bikeshed!"))),
Article(id: .init(rawValue: "2"), attributes: try! .init(title: .init(rawValue: "Sick"))),
Article(id: .init(rawValue: "3"), attributes: try! .init(title: .init(rawValue: "Hello World")))
Article(id: .init(rawValue: "1"), attributes: try! .init(title: .init(rawValue: "JSON:API paints my bikeshed!")), relationships: .none, meta: .none, links: .none),
Article(id: .init(rawValue: "2"), attributes: try! .init(title: .init(rawValue: "Sick")), relationships: .none, meta: .none, links: .none),
Article(id: .init(rawValue: "3"), attributes: try! .init(title: .init(rawValue: "Hello World")), relationships: .none, meta: .none, links: .none)
])
}

Some files were not shown because too many files have changed in this diff Show More