mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be2530f41e |
@@ -19,14 +19,7 @@ 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.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 }
|
||||
let dogFromData = dogResponse.body.primaryData?.value
|
||||
|
||||
// MARK: - Create a request or response with multiple people and dogs and houses included
|
||||
let personIds = [Person.Identifier(), Person.Identifier()]
|
||||
@@ -43,7 +36,7 @@ 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.primaryResource?.values
|
||||
let peopleFromData = peopleResponse.body.primaryData?.values
|
||||
let dogsFromData = peopleResponse.body.includes?[Dog.self]
|
||||
let housesFromData = peopleResponse.body.includes?[House.self]
|
||||
|
||||
|
||||
@@ -91,34 +91,6 @@ 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)), meta: .none, links: .none)
|
||||
|
||||
@@ -227,11 +227,6 @@ 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`.
|
||||
@@ -270,7 +265,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 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.
|
||||
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.
|
||||
|
||||
```
|
||||
public var fullName: Attribute<String> {
|
||||
@@ -399,75 +394,5 @@ 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 = .init(value: "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.
|
||||
|
||||
@@ -189,26 +189,6 @@ extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, Link
|
||||
}
|
||||
*/
|
||||
|
||||
extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody {
|
||||
public func merging(_ other: Document.Body.Data,
|
||||
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
|
||||
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
|
||||
return Document.Body.Data(primary: primary.appending(other.primary),
|
||||
includes: includes.appending(other.includes),
|
||||
meta: metaMerge(meta, other.meta),
|
||||
links: linksMerge(links, other.links))
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
|
||||
return merging(other,
|
||||
combiningMetaWith: { _, _ in .none },
|
||||
combiningLinksWith: { _, _ in .none })
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Codable
|
||||
extension Document {
|
||||
private enum RootCodingKeys: String, CodingKey {
|
||||
case data
|
||||
|
||||
@@ -50,16 +50,6 @@ public struct Includes<I: Include>: Codable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes {
|
||||
public func appending(_ other: Includes<I>) -> Includes {
|
||||
return Includes(values: values + other.values)
|
||||
}
|
||||
}
|
||||
|
||||
public func +<I: Include>(_ left: Includes<I>, _ right: Includes<I>) -> Includes<I> {
|
||||
return left.appending(right)
|
||||
}
|
||||
|
||||
extension Includes: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Includes(\(String(describing: values))"
|
||||
@@ -133,17 +123,3 @@ extension Includes where I: _Poly7 {
|
||||
}
|
||||
|
||||
// MARK: - 8 includes
|
||||
public typealias Include8 = Poly8
|
||||
extension Includes where I: _Poly8 {
|
||||
public subscript(_ lookup: I.H.Type) -> [I.H] {
|
||||
return values.compactMap { $0.h }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 9 includes
|
||||
public typealias Include9 = Poly9
|
||||
extension Includes where I: _Poly9 {
|
||||
public subscript(_ lookup: I.I.Type) -> [I.I] {
|
||||
return values.compactMap { $0.i }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,6 @@ extension Optional: MaybePrimaryResource where Wrapped: PrimaryResource {}
|
||||
public protocol ResourceBody: Codable, Equatable {
|
||||
}
|
||||
|
||||
public protocol AppendableResourceBody: ResourceBody {
|
||||
func appending(_ other: Self) -> Self
|
||||
}
|
||||
|
||||
public func +<R: AppendableResourceBody>(_ left: R, right: R) -> R {
|
||||
return left.appending(right)
|
||||
}
|
||||
|
||||
public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: ResourceBody {
|
||||
public let value: Entity
|
||||
|
||||
@@ -35,16 +27,12 @@ public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: Resource
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: AppendableResourceBody {
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: ResourceBody {
|
||||
public let values: [Entity]
|
||||
|
||||
public init(entities: [Entity]) {
|
||||
values = entities
|
||||
}
|
||||
|
||||
public func appending(_ other: ManyResourceBody) -> ManyResourceBody {
|
||||
return ManyResourceBody(entities: values + other.values)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use NoResourceBody to indicate you expect a JSON API document to not
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// ParsingInvariant.swift
|
||||
// JSONAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 12/24/18.
|
||||
//
|
||||
|
||||
/// An Error produced when an invariant fails.
|
||||
public enum InvariantError: Swift.Error {
|
||||
case failure(description: String)
|
||||
}
|
||||
|
||||
/// Set a reaction to a particular type of invariant failure.
|
||||
/// `Warning` and `Quiet` will take a default strategy for handling
|
||||
/// the invariant and parsing will not fail. `Error` will cause parsing
|
||||
/// to fail due to the given invariant failing.
|
||||
public protocol IReaction {
|
||||
/// Perform the given Invariant strategy.
|
||||
static func trigger(invariant: InvariantType.Type) throws
|
||||
}
|
||||
|
||||
public enum Invariant {
|
||||
/// Used to indicate an invariant failure shoud be considered an error.
|
||||
/// Parsing will fail if an Error invariant fails.
|
||||
public enum Error: IReaction {
|
||||
public static func trigger(invariant: InvariantType.Type) throws {
|
||||
throw InvariantError.failure(description: String(describing: invariant))
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to indicate an invariant failure should be considered a warning.
|
||||
/// The invariant description will be printed to the console but parsing
|
||||
/// will not fail. A default strategy for addressing the invariant will
|
||||
/// be taken. See the given invariant's documentation for details.
|
||||
public enum Warning: IReaction {
|
||||
public static func trigger(invariant: InvariantType.Type) throws {
|
||||
print("**WARN** \(String(describing: invariant))")
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to indicate an invariant failures should be considered inconsequential.
|
||||
/// Parsing will continue and a default strategy for addressing the invariant
|
||||
/// will be taken. See the given invariant's documentation for details.
|
||||
public enum Quiet: IReaction {
|
||||
public static func trigger(invariant: InvariantType.Type) throws {}
|
||||
}
|
||||
|
||||
/// A strategy pairs an invariant type with the desired reaction.
|
||||
public enum Strategy<IType: InvariantType, IReaction: JSONAPI.IReaction>: IStrategy {}
|
||||
}
|
||||
|
||||
public protocol InvariantType {}
|
||||
|
||||
public protocol IStrategy {
|
||||
associatedtype IType: InvariantType
|
||||
associatedtype IReaction: JSONAPI.IReaction
|
||||
}
|
||||
|
||||
// TODO: Does this invariant make sense? I can't actually know whether arbitrary
|
||||
// Id types are "empty" so I think I started down this path without having fully
|
||||
// thought through it. Perhaps there are still other obvious invariants to add
|
||||
// though.
|
||||
public protocol RelationshipNonEmptyIdInvariant: IStrategy where IType == Invariant.Relationships.NonEmptyId {}
|
||||
|
||||
extension Invariant {
|
||||
public enum Relationships {
|
||||
|
||||
/// The Empty Id Invariant holds as long as a relationships's Id
|
||||
/// is not "empty"
|
||||
public enum NonEmptyId: InvariantType {}
|
||||
|
||||
public enum Strategies<NonEmptyId: RelationshipNonEmptyIdInvariant> {}
|
||||
}
|
||||
}
|
||||
|
||||
// Would be used like `ToOneRelationship<OtherEntity, Invariant.Relationships.Strategies<Invariant.Strategy<Invariant.Relationships.NonEmptyId, Invariant.Error>>>`
|
||||
@@ -84,11 +84,3 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
/// A JSON API structure within an Entity that contains
|
||||
/// named properties of types `ToOneRelationship` and
|
||||
/// `ToManyRelationship`.
|
||||
public protocol Relationships: Codable & Equatable {}
|
||||
public typealias Relationships = Codable & Equatable
|
||||
|
||||
/// A JSON API structure within an Entity that contains
|
||||
/// properties of any types that are JSON encodable.
|
||||
public protocol Attributes: Codable & Equatable {}
|
||||
public typealias Attributes = Codable & Equatable
|
||||
|
||||
/// Can be used as `Relationships` Type for Entities that do not
|
||||
/// have any Relationships.
|
||||
@@ -556,10 +556,9 @@ 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)
|
||||
|
||||
@@ -799,360 +799,3 @@ extension Poly7: CustomStringConvertible {
|
||||
return "Poly(\(str))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 8 types
|
||||
public protocol _Poly8: _Poly7 {
|
||||
associatedtype H: EntityType
|
||||
var h: H? { get }
|
||||
|
||||
init(_ h: H)
|
||||
}
|
||||
|
||||
public extension _Poly8 {
|
||||
subscript(_ lookup: H.Type) -> H? {
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
public enum Poly8<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType, G: EntityType, H: EntityType>: _Poly8 {
|
||||
case a(A)
|
||||
case b(B)
|
||||
case c(C)
|
||||
case d(D)
|
||||
case e(E)
|
||||
case f(F)
|
||||
case g(G)
|
||||
case h(H)
|
||||
|
||||
public var a: A? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ a: A) {
|
||||
self = .a(a)
|
||||
}
|
||||
|
||||
public var b: B? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ b: B) {
|
||||
self = .b(b)
|
||||
}
|
||||
|
||||
public var c: C? {
|
||||
guard case let .c(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ c: C) {
|
||||
self = .c(c)
|
||||
}
|
||||
|
||||
public var d: D? {
|
||||
guard case let .d(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ d: D) {
|
||||
self = .d(d)
|
||||
}
|
||||
|
||||
public var e: E? {
|
||||
guard case let .e(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ e: E) {
|
||||
self = .e(e)
|
||||
}
|
||||
|
||||
public var f: F? {
|
||||
guard case let .f(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ f: F) {
|
||||
self = .f(f)
|
||||
}
|
||||
|
||||
public var g: G? {
|
||||
guard case let .g(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ g: G) {
|
||||
self = .g(g)
|
||||
}
|
||||
|
||||
public var h: H? {
|
||||
guard case let .h(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ h: H) {
|
||||
self = .h(h)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Poly8.a($0) },
|
||||
try decode(B.self, from: container).map { Poly8.b($0) },
|
||||
try decode(C.self, from: container).map { Poly8.c($0) },
|
||||
try decode(D.self, from: container).map { Poly8.d($0) },
|
||||
try decode(E.self, from: container).map { Poly8.e($0) },
|
||||
try decode(F.self, from: container).map { Poly8.f($0) },
|
||||
try decode(G.self, from: container).map { Poly8.g($0) },
|
||||
try decode(H.self, from: container).map { Poly8.h($0) }]
|
||||
|
||||
let maybeVal: Poly8<A, B, C, D, E, F, G, H>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Poly8<A, B, C, D, E, F, G, H>.self, .init(codingPath: decoder.codingPath,
|
||||
debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
switch self {
|
||||
case .a(let a):
|
||||
try container.encode(a)
|
||||
case .b(let b):
|
||||
try container.encode(b)
|
||||
case .c(let c):
|
||||
try container.encode(c)
|
||||
case .d(let d):
|
||||
try container.encode(d)
|
||||
case .e(let e):
|
||||
try container.encode(e)
|
||||
case .f(let f):
|
||||
try container.encode(f)
|
||||
case .g(let g):
|
||||
try container.encode(g)
|
||||
case .h(let h):
|
||||
try container.encode(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Poly8: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let str: String
|
||||
switch self {
|
||||
case .a(let a):
|
||||
str = String(describing: a)
|
||||
case .b(let b):
|
||||
str = String(describing: b)
|
||||
case .c(let c):
|
||||
str = String(describing: c)
|
||||
case .d(let d):
|
||||
str = String(describing: d)
|
||||
case .e(let e):
|
||||
str = String(describing: e)
|
||||
case .f(let f):
|
||||
str = String(describing: f)
|
||||
case .g(let g):
|
||||
str = String(describing: g)
|
||||
case .h(let h):
|
||||
str = String(describing: h)
|
||||
}
|
||||
|
||||
return "Poly(\(str))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 9 types
|
||||
public protocol _Poly9: _Poly8 {
|
||||
associatedtype I: EntityType
|
||||
var i: I? { get }
|
||||
|
||||
init(_ i: I)
|
||||
}
|
||||
|
||||
public extension _Poly9 {
|
||||
subscript(_ lookup: I.Type) -> I? {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
public enum Poly9<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType, G: EntityType, H: EntityType, I: EntityType>: _Poly9 {
|
||||
case a(A)
|
||||
case b(B)
|
||||
case c(C)
|
||||
case d(D)
|
||||
case e(E)
|
||||
case f(F)
|
||||
case g(G)
|
||||
case h(H)
|
||||
case i(I)
|
||||
|
||||
public var a: A? {
|
||||
guard case let .a(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ a: A) {
|
||||
self = .a(a)
|
||||
}
|
||||
|
||||
public var b: B? {
|
||||
guard case let .b(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ b: B) {
|
||||
self = .b(b)
|
||||
}
|
||||
|
||||
public var c: C? {
|
||||
guard case let .c(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ c: C) {
|
||||
self = .c(c)
|
||||
}
|
||||
|
||||
public var d: D? {
|
||||
guard case let .d(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ d: D) {
|
||||
self = .d(d)
|
||||
}
|
||||
|
||||
public var e: E? {
|
||||
guard case let .e(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ e: E) {
|
||||
self = .e(e)
|
||||
}
|
||||
|
||||
public var f: F? {
|
||||
guard case let .f(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ f: F) {
|
||||
self = .f(f)
|
||||
}
|
||||
|
||||
public var g: G? {
|
||||
guard case let .g(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ g: G) {
|
||||
self = .g(g)
|
||||
}
|
||||
|
||||
public var h: H? {
|
||||
guard case let .h(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ h: H) {
|
||||
self = .h(h)
|
||||
}
|
||||
|
||||
public var i: I? {
|
||||
guard case let .i(ret) = self else { return nil }
|
||||
return ret
|
||||
}
|
||||
|
||||
public init(_ i: I) {
|
||||
self = .i(i)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let attempts = [
|
||||
try decode(A.self, from: container).map { Poly9.a($0) },
|
||||
try decode(B.self, from: container).map { Poly9.b($0) },
|
||||
try decode(C.self, from: container).map { Poly9.c($0) },
|
||||
try decode(D.self, from: container).map { Poly9.d($0) },
|
||||
try decode(E.self, from: container).map { Poly9.e($0) },
|
||||
try decode(F.self, from: container).map { Poly9.f($0) },
|
||||
try decode(G.self, from: container).map { Poly9.g($0) },
|
||||
try decode(H.self, from: container).map { Poly9.h($0) },
|
||||
try decode(I.self, from: container).map { Poly9.i($0) }]
|
||||
|
||||
let maybeVal: Poly9<A, B, C, D, E, F, G, H, I>? = attempts
|
||||
.compactMap { $0.value }
|
||||
.first
|
||||
|
||||
guard let val = maybeVal else {
|
||||
throw EncodingError.invalidValue(Poly9<A, B, C, D, E, F, G, H, I>.self, .init(codingPath: decoder.codingPath,
|
||||
debugDescription: "Failed to find an include of the expected type. Attempts: \(attempts.map { $0.error }.compactMap { $0 })"))
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
switch self {
|
||||
case .a(let a):
|
||||
try container.encode(a)
|
||||
case .b(let b):
|
||||
try container.encode(b)
|
||||
case .c(let c):
|
||||
try container.encode(c)
|
||||
case .d(let d):
|
||||
try container.encode(d)
|
||||
case .e(let e):
|
||||
try container.encode(e)
|
||||
case .f(let f):
|
||||
try container.encode(f)
|
||||
case .g(let g):
|
||||
try container.encode(g)
|
||||
case .h(let h):
|
||||
try container.encode(h)
|
||||
case .i(let i):
|
||||
try container.encode(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Poly9: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let str: String
|
||||
switch self {
|
||||
case .a(let a):
|
||||
str = String(describing: a)
|
||||
case .b(let b):
|
||||
str = String(describing: b)
|
||||
case .c(let c):
|
||||
str = String(describing: c)
|
||||
case .d(let d):
|
||||
str = String(describing: d)
|
||||
case .e(let e):
|
||||
str = String(describing: e)
|
||||
case .f(let f):
|
||||
str = String(describing: f)
|
||||
case .g(let g):
|
||||
str = String(describing: g)
|
||||
case .h(let h):
|
||||
str = String(describing: h)
|
||||
case .i(let i):
|
||||
str = String(describing: i)
|
||||
}
|
||||
|
||||
return "Poly(\(str))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
//
|
||||
// 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)!
|
||||
@@ -961,55 +961,6 @@ extension DocumentTests {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Merging
|
||||
extension DocumentTests {
|
||||
public func test_MergeBodyDataBasic(){
|
||||
let entity1 = Article(attributes: .none, relationships: .init(author: "2"), meta: .none, links: .none)
|
||||
let entity2 = Article(attributes: .none, relationships: .init(author: "3"), meta: .none, links: .none)
|
||||
|
||||
let bodyData1 = Document<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [entity1]),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
let bodyData2 = Document<ManyResourceBody<Article>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [entity2]),
|
||||
includes: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
let combined = bodyData1.merging(bodyData2)
|
||||
|
||||
XCTAssertEqual(combined.primary.values, bodyData1.primary.values + bodyData2.primary.values)
|
||||
}
|
||||
|
||||
public func test_MergeBodyDataWithMergeFunctions() {
|
||||
let article1 = Article(attributes: .none, relationships: .init(author: "2"), meta: .none, links: .none)
|
||||
let author1 = Author(id: "2", attributes: .none, relationships: .none, meta: .none, links: .none)
|
||||
let article2 = Article(attributes: .none, relationships: .init(author: "3"), meta: .none, links: .none)
|
||||
let author2 = Author(id: "3", attributes: .none, relationships: .none, meta: .none, links: .none)
|
||||
|
||||
let bodyData1 = Document<ManyResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [article1]),
|
||||
includes: .init(values: [.init(author1)]),
|
||||
meta: .init(total: 50, limit: 5, offset: 5),
|
||||
links: .none)
|
||||
let bodyData2 = Document<ManyResourceBody<Article>, TestPageMetadata, NoLinks, Include1<Author>, NoAPIDescription, UnknownJSONAPIError>.Body.Data(primary: .init(entities: [article2]),
|
||||
includes: .init(values: [.init(author2)]),
|
||||
meta: .init(total: 60, limit: 5, offset: 5),
|
||||
links: .none)
|
||||
|
||||
let combined = bodyData1.merging(bodyData2,
|
||||
combiningMetaWith: { (meta1, meta2) in
|
||||
return .init(total: max(meta1.total, meta2.total), limit: max(meta1.limit, meta2.limit), offset: max(meta1.offset, meta2.offset))
|
||||
},
|
||||
combiningLinksWith: { _, _ in .none })
|
||||
|
||||
XCTAssertEqual(combined.meta.total, bodyData2.meta.total)
|
||||
XCTAssertEqual(combined.meta.limit, bodyData2.meta.limit)
|
||||
XCTAssertEqual(combined.meta.offset, bodyData1.meta.offset)
|
||||
|
||||
XCTAssertEqual(combined.includes, bodyData1.includes + bodyData2.includes)
|
||||
XCTAssertEqual(combined.primary, bodyData1.primary + bodyData2.primary)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
extension DocumentTests {
|
||||
enum AuthorType: EntityDescription {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import XCTest
|
||||
@testable import JSONAPI
|
||||
import JSONAPI
|
||||
|
||||
class IncludedTests: XCTestCase {
|
||||
|
||||
@@ -139,57 +139,6 @@ class IncludedTests: XCTestCase {
|
||||
test_DecodeEncodeEquality(type: Includes<Include7<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7>>.self,
|
||||
data: seven_different_type_includes)
|
||||
}
|
||||
|
||||
func test_EightDifferentIncludes() {
|
||||
let includes = decoded(type: Includes<Include8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8>>.self,
|
||||
data: eight_different_type_includes)
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity3.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity5.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity6.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity7.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity8.self].count, 1)
|
||||
}
|
||||
|
||||
func test_EightDifferentIncludes_encode() {
|
||||
test_DecodeEncodeEquality(type: Includes<Include8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8>>.self,
|
||||
data: eight_different_type_includes)
|
||||
}
|
||||
|
||||
func test_NineDifferentIncludes() {
|
||||
let includes = decoded(type: Includes<Include9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9>>.self,
|
||||
data: nine_different_type_includes)
|
||||
|
||||
XCTAssertEqual(includes[TestEntity.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity2.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity3.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity4.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity5.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity6.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity7.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity8.self].count, 1)
|
||||
XCTAssertEqual(includes[TestEntity9.self].count, 1)
|
||||
}
|
||||
|
||||
func test_NineDifferentIncludes_encode() {
|
||||
test_DecodeEncodeEquality(type: Includes<Include9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9>>.self,
|
||||
data: nine_different_type_includes)
|
||||
}
|
||||
}
|
||||
|
||||
extension IncludedTests {
|
||||
func test_appending() {
|
||||
let include1 = Includes<Include2<TestEntity8, TestEntity9>>(values: [.init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity9(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none))])
|
||||
|
||||
let include2 = Includes<Include2<TestEntity8, TestEntity9>>(values: [.init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity9(attributes: .none, relationships: .none, meta: .none, links: .none)), .init(TestEntity8(attributes: .none, relationships: .none, meta: .none, links: .none))])
|
||||
|
||||
let combined = include1 + include2
|
||||
|
||||
XCTAssertEqual(combined.values, include1.values + include2.values)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test types
|
||||
@@ -283,26 +232,4 @@ extension IncludedTests {
|
||||
}
|
||||
|
||||
typealias TestEntity7 = BasicEntity<TestEntityType7>
|
||||
|
||||
enum TestEntityType8: EntityDescription {
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
public static var type: String { return "test_entity8" }
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntity8 = BasicEntity<TestEntityType8>
|
||||
|
||||
enum TestEntityType9: EntityDescription {
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
public static var type: String { return "test_entity9" }
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntity9 = BasicEntity<TestEntityType9>
|
||||
}
|
||||
|
||||
@@ -354,161 +354,3 @@ let seven_different_type_includes = """
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let eight_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity3",
|
||||
"id": "11223B69-4DF1-467F-B52E-B0C9E44FC443",
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
},
|
||||
"entity2": {
|
||||
"data": [
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity6",
|
||||
"id": "11113B69-4DF1-467F-B52E-B0C9E44FC444",
|
||||
"relationships": {
|
||||
"entity4": {
|
||||
"data": {
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity5",
|
||||
"id": "A24B3B69-4DF1-467F-B52E-B0C9E44F436A"
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
},
|
||||
{
|
||||
"type": "test_entity7",
|
||||
"id": "364B3B69-4DF1-222F-B52E-B0C9E44F666E"
|
||||
},
|
||||
{
|
||||
"type": "test_entity8",
|
||||
"id": "364B3B69-4DF1-222F-B52E-B0C9E44F266F"
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let nine_different_type_includes = """
|
||||
[
|
||||
{
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF",
|
||||
"attributes": {
|
||||
"foo": "Hello",
|
||||
"bar": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333",
|
||||
"attributes": {
|
||||
"foo": "World",
|
||||
"bar": 456
|
||||
},
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity3",
|
||||
"id": "11223B69-4DF1-467F-B52E-B0C9E44FC443",
|
||||
"relationships": {
|
||||
"entity1": {
|
||||
"data": {
|
||||
"type": "test_entity1",
|
||||
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
|
||||
}
|
||||
},
|
||||
"entity2": {
|
||||
"data": [
|
||||
{
|
||||
"type": "test_entity2",
|
||||
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity6",
|
||||
"id": "11113B69-4DF1-467F-B52E-B0C9E44FC444",
|
||||
"relationships": {
|
||||
"entity4": {
|
||||
"data": {
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "test_entity5",
|
||||
"id": "A24B3B69-4DF1-467F-B52E-B0C9E44F436A"
|
||||
},
|
||||
{
|
||||
"type": "test_entity4",
|
||||
"id": "364B3B69-4DF1-467F-B52E-B0C9E44F666E"
|
||||
},
|
||||
{
|
||||
"type": "test_entity7",
|
||||
"id": "364B3B69-4DF1-222F-B52E-B0C9E44F666E"
|
||||
},
|
||||
{
|
||||
"type": "test_entity8",
|
||||
"id": "364B3B69-4DF1-222F-B52E-B0C9E44F266F"
|
||||
},
|
||||
{
|
||||
"type": "test_entity9",
|
||||
"id": "364B3B69-4DF1-218F-B52E-B0C9E44F2661"
|
||||
}
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
|
||||
@@ -61,7 +61,7 @@ extension EntityCheckTests {
|
||||
enum EnumAttributesDescription: EntityDescription {
|
||||
public static var type: String { return "hello" }
|
||||
|
||||
public enum Attributes: JSONAPI.Attributes {
|
||||
public enum Attributes: Codable, Equatable {
|
||||
case hello
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
@@ -82,7 +82,7 @@ extension EntityCheckTests {
|
||||
|
||||
public typealias Attributes = NoAttributes
|
||||
|
||||
public enum Relationships: JSONAPI.Relationships {
|
||||
public enum Relationships: Codable, Equatable {
|
||||
case hello
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
||||
@@ -230,191 +230,6 @@ class PolyTests: XCTestCase {
|
||||
XCTAssertNil(poly7.e)
|
||||
XCTAssertNil(poly7.f)
|
||||
}
|
||||
|
||||
func test_init_Poly8() {
|
||||
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
|
||||
let poly = Poly8<TestEntity5, TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly.a, entity)
|
||||
XCTAssertNil(poly.b)
|
||||
XCTAssertNil(poly.c)
|
||||
XCTAssertNil(poly.d)
|
||||
XCTAssertNil(poly.e)
|
||||
XCTAssertNil(poly.f)
|
||||
XCTAssertNil(poly.g)
|
||||
XCTAssertNil(poly.h)
|
||||
|
||||
let poly2 = Poly8<TestEntity, TestEntity5, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly2.b, entity)
|
||||
XCTAssertNil(poly2.a)
|
||||
XCTAssertNil(poly2.c)
|
||||
XCTAssertNil(poly2.d)
|
||||
XCTAssertNil(poly2.e)
|
||||
XCTAssertNil(poly2.f)
|
||||
XCTAssertNil(poly2.g)
|
||||
XCTAssertNil(poly2.h)
|
||||
|
||||
let poly3 = Poly8<TestEntity, TestEntity2, TestEntity5, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly3.c, entity)
|
||||
XCTAssertNil(poly3.a)
|
||||
XCTAssertNil(poly3.b)
|
||||
XCTAssertNil(poly3.d)
|
||||
XCTAssertNil(poly3.e)
|
||||
XCTAssertNil(poly3.f)
|
||||
XCTAssertNil(poly3.g)
|
||||
XCTAssertNil(poly3.h)
|
||||
|
||||
let poly4 = Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity5, TestEntity4, TestEntity6, TestEntity7, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly4.d, entity)
|
||||
XCTAssertNil(poly4.a)
|
||||
XCTAssertNil(poly4.b)
|
||||
XCTAssertNil(poly4.c)
|
||||
XCTAssertNil(poly4.e)
|
||||
XCTAssertNil(poly4.f)
|
||||
XCTAssertNil(poly4.g)
|
||||
XCTAssertNil(poly4.h)
|
||||
|
||||
let poly5 = Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly5.e, entity)
|
||||
XCTAssertNil(poly5.a)
|
||||
XCTAssertNil(poly5.b)
|
||||
XCTAssertNil(poly5.c)
|
||||
XCTAssertNil(poly5.d)
|
||||
XCTAssertNil(poly5.f)
|
||||
XCTAssertNil(poly5.g)
|
||||
XCTAssertNil(poly5.h)
|
||||
|
||||
let poly6 = Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity5, TestEntity7, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly6.f, entity)
|
||||
XCTAssertNil(poly6.a)
|
||||
XCTAssertNil(poly6.b)
|
||||
XCTAssertNil(poly6.c)
|
||||
XCTAssertNil(poly6.d)
|
||||
XCTAssertNil(poly6.e)
|
||||
XCTAssertNil(poly6.g)
|
||||
XCTAssertNil(poly6.h)
|
||||
|
||||
let poly7 = Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity5, TestEntity8>(entity)
|
||||
XCTAssertEqual(poly7.g, entity)
|
||||
XCTAssertNil(poly7.a)
|
||||
XCTAssertNil(poly7.b)
|
||||
XCTAssertNil(poly7.c)
|
||||
XCTAssertNil(poly7.d)
|
||||
XCTAssertNil(poly7.e)
|
||||
XCTAssertNil(poly7.f)
|
||||
XCTAssertNil(poly7.h)
|
||||
|
||||
let poly8 = Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity5>(entity)
|
||||
XCTAssertEqual(poly8.h, entity)
|
||||
XCTAssertNil(poly8.a)
|
||||
XCTAssertNil(poly8.b)
|
||||
XCTAssertNil(poly8.c)
|
||||
XCTAssertNil(poly8.d)
|
||||
XCTAssertNil(poly8.e)
|
||||
XCTAssertNil(poly8.f)
|
||||
XCTAssertNil(poly8.g)
|
||||
}
|
||||
|
||||
func test_init_Poly9() {
|
||||
let entity = TestEntity5(attributes: .none, relationships: .none, meta: .none, links: .none)
|
||||
let poly = Poly9<TestEntity5, TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly.a, entity)
|
||||
XCTAssertNil(poly.b)
|
||||
XCTAssertNil(poly.c)
|
||||
XCTAssertNil(poly.d)
|
||||
XCTAssertNil(poly.e)
|
||||
XCTAssertNil(poly.f)
|
||||
XCTAssertNil(poly.g)
|
||||
XCTAssertNil(poly.h)
|
||||
XCTAssertNil(poly.i)
|
||||
|
||||
let poly2 = Poly9<TestEntity, TestEntity5, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly2.b, entity)
|
||||
XCTAssertNil(poly2.a)
|
||||
XCTAssertNil(poly2.c)
|
||||
XCTAssertNil(poly2.d)
|
||||
XCTAssertNil(poly2.e)
|
||||
XCTAssertNil(poly2.f)
|
||||
XCTAssertNil(poly2.g)
|
||||
XCTAssertNil(poly2.h)
|
||||
XCTAssertNil(poly2.i)
|
||||
|
||||
let poly3 = Poly9<TestEntity, TestEntity2, TestEntity5, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly3.c, entity)
|
||||
XCTAssertNil(poly3.a)
|
||||
XCTAssertNil(poly3.b)
|
||||
XCTAssertNil(poly3.d)
|
||||
XCTAssertNil(poly3.e)
|
||||
XCTAssertNil(poly3.f)
|
||||
XCTAssertNil(poly3.g)
|
||||
XCTAssertNil(poly3.h)
|
||||
XCTAssertNil(poly3.i)
|
||||
|
||||
let poly4 = Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity5, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly4.d, entity)
|
||||
XCTAssertNil(poly4.a)
|
||||
XCTAssertNil(poly4.b)
|
||||
XCTAssertNil(poly4.c)
|
||||
XCTAssertNil(poly4.e)
|
||||
XCTAssertNil(poly4.f)
|
||||
XCTAssertNil(poly4.g)
|
||||
XCTAssertNil(poly4.h)
|
||||
XCTAssertNil(poly4.i)
|
||||
|
||||
let poly5 = Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly5.e, entity)
|
||||
XCTAssertNil(poly5.a)
|
||||
XCTAssertNil(poly5.b)
|
||||
XCTAssertNil(poly5.c)
|
||||
XCTAssertNil(poly5.d)
|
||||
XCTAssertNil(poly5.f)
|
||||
XCTAssertNil(poly5.g)
|
||||
XCTAssertNil(poly5.h)
|
||||
XCTAssertNil(poly5.i)
|
||||
|
||||
let poly6 = Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity5, TestEntity7, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly6.f, entity)
|
||||
XCTAssertNil(poly6.a)
|
||||
XCTAssertNil(poly6.b)
|
||||
XCTAssertNil(poly6.c)
|
||||
XCTAssertNil(poly6.d)
|
||||
XCTAssertNil(poly6.e)
|
||||
XCTAssertNil(poly6.g)
|
||||
XCTAssertNil(poly6.h)
|
||||
XCTAssertNil(poly6.i)
|
||||
|
||||
let poly7 = Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity5, TestEntity8, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly7.g, entity)
|
||||
XCTAssertNil(poly7.a)
|
||||
XCTAssertNil(poly7.b)
|
||||
XCTAssertNil(poly7.c)
|
||||
XCTAssertNil(poly7.d)
|
||||
XCTAssertNil(poly7.e)
|
||||
XCTAssertNil(poly7.f)
|
||||
XCTAssertNil(poly7.h)
|
||||
XCTAssertNil(poly7.i)
|
||||
|
||||
let poly8 = Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity5, TestEntity9>(entity)
|
||||
XCTAssertEqual(poly8.h, entity)
|
||||
XCTAssertNil(poly8.a)
|
||||
XCTAssertNil(poly8.b)
|
||||
XCTAssertNil(poly8.c)
|
||||
XCTAssertNil(poly8.d)
|
||||
XCTAssertNil(poly8.e)
|
||||
XCTAssertNil(poly8.f)
|
||||
XCTAssertNil(poly8.g)
|
||||
XCTAssertNil(poly8.i)
|
||||
|
||||
let poly9 = Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity6, TestEntity7, TestEntity8, TestEntity9, TestEntity5>(entity)
|
||||
XCTAssertEqual(poly9.i, entity)
|
||||
XCTAssertNil(poly9.a)
|
||||
XCTAssertNil(poly9.b)
|
||||
XCTAssertNil(poly9.c)
|
||||
XCTAssertNil(poly9.d)
|
||||
XCTAssertNil(poly9.e)
|
||||
XCTAssertNil(poly9.f)
|
||||
XCTAssertNil(poly9.g)
|
||||
XCTAssertNil(poly9.h)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - subscript lookup
|
||||
@@ -488,35 +303,6 @@ extension PolyTests {
|
||||
XCTAssertNil(poly[TestEntity6.self])
|
||||
XCTAssertEqual(entity, poly[TestEntity7.self])
|
||||
}
|
||||
|
||||
func test_Poly8_lookup() {
|
||||
let entity = decoded(type: TestEntity8.self, data: poly_entity8)
|
||||
let poly = decoded(type: Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8>.self, data: poly_entity8)
|
||||
|
||||
XCTAssertNil(poly[TestEntity.self])
|
||||
XCTAssertNil(poly[TestEntity2.self])
|
||||
XCTAssertNil(poly[TestEntity3.self])
|
||||
XCTAssertNil(poly[TestEntity4.self])
|
||||
XCTAssertNil(poly[TestEntity5.self])
|
||||
XCTAssertNil(poly[TestEntity6.self])
|
||||
XCTAssertNil(poly[TestEntity7.self])
|
||||
XCTAssertEqual(entity, poly[TestEntity8.self])
|
||||
}
|
||||
|
||||
func test_Poly9_lookup() {
|
||||
let entity = decoded(type: TestEntity9.self, data: poly_entity9)
|
||||
let poly = decoded(type: Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9>.self, data: poly_entity9)
|
||||
|
||||
XCTAssertNil(poly[TestEntity.self])
|
||||
XCTAssertNil(poly[TestEntity2.self])
|
||||
XCTAssertNil(poly[TestEntity3.self])
|
||||
XCTAssertNil(poly[TestEntity4.self])
|
||||
XCTAssertNil(poly[TestEntity5.self])
|
||||
XCTAssertNil(poly[TestEntity6.self])
|
||||
XCTAssertNil(poly[TestEntity7.self])
|
||||
XCTAssertNil(poly[TestEntity8.self])
|
||||
XCTAssertEqual(entity, poly[TestEntity9.self])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - failures
|
||||
@@ -556,14 +342,6 @@ extension PolyTests {
|
||||
func test_Poly7_decode_throws_typeNotFound() {
|
||||
XCTAssertThrowsError(try JSONDecoder().decode(Poly7<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7>.self, from: poly_entity8))
|
||||
}
|
||||
|
||||
func test_Poly8_decode_throws_typeNotFound() {
|
||||
XCTAssertThrowsError(try JSONDecoder().decode(Poly8<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8>.self, from: poly_entity9))
|
||||
}
|
||||
|
||||
func test_Poly9_decode_throws_typeNotFound() {
|
||||
XCTAssertThrowsError(try JSONDecoder().decode(Poly9<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7, TestEntity8, TestEntity9>.self, from: poly_entity10))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test types
|
||||
@@ -657,26 +435,4 @@ extension PolyTests {
|
||||
}
|
||||
|
||||
typealias TestEntity7 = BasicEntity<TestEntityType7>
|
||||
|
||||
enum TestEntityType8: EntityDescription {
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
public static var type: String { return "test_entity8" }
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntity8 = BasicEntity<TestEntityType8>
|
||||
|
||||
enum TestEntityType9: EntityDescription {
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
public static var type: String { return "test_entity9" }
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntity9 = BasicEntity<TestEntityType9>
|
||||
}
|
||||
|
||||
@@ -100,17 +100,3 @@ let poly_entity8 = """
|
||||
"id": "A24B3444-4DF1-467F-B52E-B0C9E44F436A"
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let poly_entity9 = """
|
||||
{
|
||||
"type": "test_entity9",
|
||||
"id": "A24B3444-4DF1-467F-B52E-B0C2B44F436A"
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
let poly_entity10 = """
|
||||
{
|
||||
"type": "test_entity10",
|
||||
"id": "A24B3444-4DF1-467F-B52E-B0C9E12F436A"
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
@@ -61,31 +61,6 @@ class ResourceBodyTests: XCTestCase {
|
||||
data: many_resource_body_empty)
|
||||
}
|
||||
|
||||
func test_manyResourceBodyMerge() {
|
||||
let body1 = ManyResourceBody(entities: [
|
||||
Article(attributes: .init(title: "hello"),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none),
|
||||
Article(attributes: .init(title: "world"),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
])
|
||||
|
||||
let body2 = ManyResourceBody(entities: [
|
||||
Article(attributes: .init(title: "once more"),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
])
|
||||
|
||||
let combined = body1 + body2
|
||||
|
||||
XCTAssertEqual(combined.values.count, 3)
|
||||
XCTAssertEqual(combined.values, body1.values + body2.values)
|
||||
}
|
||||
|
||||
enum ArticleType: EntityDescription {
|
||||
public static var type: String { return "articles" }
|
||||
|
||||
|
||||
@@ -73,15 +73,6 @@ extension ComputedPropertiesTests {
|
||||
]
|
||||
}
|
||||
|
||||
extension CustomAttributesTests {
|
||||
static let __allTests = [
|
||||
("test_customDecode", test_customDecode),
|
||||
("test_customEncode", test_customEncode),
|
||||
("test_customKeysDecode", test_customKeysDecode),
|
||||
("test_customKeysEncode", test_customKeysEncode),
|
||||
]
|
||||
}
|
||||
|
||||
extension DocumentTests {
|
||||
static let __allTests = [
|
||||
("test_errorDocumentFailsWithNoAPIDescription", test_errorDocumentFailsWithNoAPIDescription),
|
||||
@@ -97,8 +88,6 @@ extension DocumentTests {
|
||||
("test_manyDocumentSomeIncludes_encode", test_manyDocumentSomeIncludes_encode),
|
||||
("test_manyDocumentSomeIncludesWithAPIDescription", test_manyDocumentSomeIncludesWithAPIDescription),
|
||||
("test_manyDocumentSomeIncludesWithAPIDescription_encode", test_manyDocumentSomeIncludesWithAPIDescription_encode),
|
||||
("test_MergeBodyDataBasic", test_MergeBodyDataBasic),
|
||||
("test_MergeBodyDataWithMergeFunctions", test_MergeBodyDataWithMergeFunctions),
|
||||
("test_metaDataDocument", test_metaDataDocument),
|
||||
("test_metaDataDocument_encode", test_metaDataDocument_encode),
|
||||
("test_metaDataDocumentFailsIfMissingAPIDescription", test_metaDataDocumentFailsIfMissingAPIDescription),
|
||||
@@ -271,15 +260,10 @@ extension Id_LiteralTests {
|
||||
|
||||
extension IncludedTests {
|
||||
static let __allTests = [
|
||||
("test_appending", test_appending),
|
||||
("test_EightDifferentIncludes", test_EightDifferentIncludes),
|
||||
("test_EightDifferentIncludes_encode", test_EightDifferentIncludes_encode),
|
||||
("test_FiveDifferentIncludes", test_FiveDifferentIncludes),
|
||||
("test_FiveDifferentIncludes_encode", test_FiveDifferentIncludes_encode),
|
||||
("test_FourDifferentIncludes", test_FourDifferentIncludes),
|
||||
("test_FourDifferentIncludes_encode", test_FourDifferentIncludes_encode),
|
||||
("test_NineDifferentIncludes", test_NineDifferentIncludes),
|
||||
("test_NineDifferentIncludes_encode", test_NineDifferentIncludes_encode),
|
||||
("test_OneInclude", test_OneInclude),
|
||||
("test_OneInclude_encode", test_OneInclude_encode),
|
||||
("test_SevenDifferentIncludes", test_SevenDifferentIncludes),
|
||||
@@ -341,8 +325,6 @@ extension PolyTests {
|
||||
("test_init_Poly5", test_init_Poly5),
|
||||
("test_init_Poly6", test_init_Poly6),
|
||||
("test_init_Poly7", test_init_Poly7),
|
||||
("test_init_Poly8", test_init_Poly8),
|
||||
("test_init_Poly9", test_init_Poly9),
|
||||
("test_Poly0_decode_throws", test_Poly0_decode_throws),
|
||||
("test_Poly0_encode_throws", test_Poly0_encode_throws),
|
||||
("test_Poly1_decode_throws_typeNotFound", test_Poly1_decode_throws_typeNotFound),
|
||||
@@ -359,10 +341,6 @@ extension PolyTests {
|
||||
("test_Poly6_lookup", test_Poly6_lookup),
|
||||
("test_Poly7_decode_throws_typeNotFound", test_Poly7_decode_throws_typeNotFound),
|
||||
("test_Poly7_lookup", test_Poly7_lookup),
|
||||
("test_Poly8_decode_throws_typeNotFound", test_Poly8_decode_throws_typeNotFound),
|
||||
("test_Poly8_lookup", test_Poly8_lookup),
|
||||
("test_Poly9_decode_throws_typeNotFound", test_Poly9_decode_throws_typeNotFound),
|
||||
("test_Poly9_lookup", test_Poly9_lookup),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -408,7 +386,6 @@ extension ResourceBodyTests {
|
||||
("test_manyResourceBody_encode", test_manyResourceBody_encode),
|
||||
("test_manyResourceBodyEmpty", test_manyResourceBodyEmpty),
|
||||
("test_manyResourceBodyEmpty_encode", test_manyResourceBodyEmpty_encode),
|
||||
("test_manyResourceBodyMerge", test_manyResourceBodyMerge),
|
||||
("test_singleResourceBody", test_singleResourceBody),
|
||||
("test_singleResourceBody_encode", test_singleResourceBody_encode),
|
||||
]
|
||||
@@ -422,7 +399,6 @@ public func __allTests() -> [XCTestCaseEntry] {
|
||||
testCase(Attribute_FunctorTests.__allTests),
|
||||
testCase(Attribute_LiteralTests.__allTests),
|
||||
testCase(ComputedPropertiesTests.__allTests),
|
||||
testCase(CustomAttributesTests.__allTests),
|
||||
testCase(DocumentTests.__allTests),
|
||||
testCase(EntityCheckTests.__allTests),
|
||||
testCase(EntityTests.__allTests),
|
||||
|
||||
Reference in New Issue
Block a user