mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5a24c4adb | |||
| 669d5d1342 | |||
| 923ab7d9f4 | |||
| 72769b6107 |
@@ -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)), meta: .none, links: .none)
|
||||
self = Person(id: id ?? Person.Id(), attributes: .init(name: .init(value: name), favoriteColor: .init(value: favoriteColor)), relationships: .init(friends: .init(entities: friends), dogs: .init(entities: dogs), home: .init(entity: home)), meta: .none, links: .none)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,11 +121,11 @@ 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)
|
||||
self = Dog(attributes: .init(name: .init(value: 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)), meta: .none, links: .none)
|
||||
self = Dog(attributes: .init(name: .init(value: name)), relationships: .init(owner: .init(id: owner)), meta: .none, links: .none)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -456,7 +456,7 @@ extension EntityDescription1.Attributes {
|
||||
property2 = try .defaultDecoding(from: container, forKey: .property2)
|
||||
property3 = try .defaultDecoding(from: container, forKey: .property3)
|
||||
|
||||
weirdThing = "hello world"
|
||||
weirdThing = .init(value: "hello world")
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
||||
@@ -16,6 +16,8 @@ public struct APIDescription<Meta: JSONAPI.Meta>: APIDescriptionType {
|
||||
public let meta: Meta
|
||||
}
|
||||
|
||||
/// Can be used as `APIDescriptionType` for Documents that do not
|
||||
/// have any API Description (a.k.a. "JSON:API Object").
|
||||
public struct NoAPIDescription: APIDescriptionType, CustomStringConvertible {
|
||||
public typealias Meta = NoMetadata
|
||||
|
||||
|
||||
@@ -189,6 +189,26 @@ 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,6 +50,16 @@ 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))"
|
||||
@@ -123,3 +133,17 @@ 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,6 +19,14 @@ 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
|
||||
|
||||
@@ -27,12 +35,16 @@ public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: Resource
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: ResourceBody {
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: AppendableResourceBody {
|
||||
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
|
||||
|
||||
@@ -19,3 +19,18 @@ public extension TransformedAttribute {
|
||||
return Attribute<T>(value: try transform(value))
|
||||
}
|
||||
}
|
||||
|
||||
public extension Attribute {
|
||||
/// Map an Attribute to a new wrapped type.
|
||||
/// Note that the resulting Attribute will have no transformer, even if the
|
||||
/// source Attribute has a transformer.
|
||||
/// You are mapping the output of the source transform into
|
||||
/// the RawValue of a new transformerless Attribute.
|
||||
///
|
||||
/// Generally, this is the most useful operation. The transformer gives you
|
||||
/// control over the decoding of the Attribute, but once the Attribute exists,
|
||||
/// mapping on it is most useful for creating computed Attribute properties.
|
||||
public func map<T: Codable>(_ transform: (ValueType) throws -> T) rethrows -> Attribute<T> {
|
||||
return Attribute<T>(value: try transform(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,14 @@
|
||||
//
|
||||
|
||||
public protocol AttributeType: Codable {
|
||||
associatedtype RawValue: Codable
|
||||
associatedtype ValueType
|
||||
|
||||
var value: ValueType { get }
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let rawValue: RawValue
|
||||
@@ -21,16 +26,19 @@ public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Trans
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ValidatedAttribute
|
||||
/// A ValidatedAttribute does not transform its raw value, but it throws
|
||||
/// an error if the raw value does not match expectations.
|
||||
public typealias ValidatedAttribute<RawValue: Codable, Validator: JSONAPI.Validator> = TransformedAttribute<RawValue, Validator> where RawValue == Validator.From
|
||||
|
||||
// MARK: Attribute
|
||||
/// An Attribute simply represents a type that can be encoded and decoded.
|
||||
public typealias Attribute<T: Codable> = TransformedAttribute<T, IdentityTransformer<T>>
|
||||
extension TransformedAttribute where Transformer == IdentityTransformer<RawValue> {
|
||||
// If we are using the identity transform, we can skip the transform and guarantee no
|
||||
// error is thrown.
|
||||
public init(value: RawValue) {
|
||||
rawValue = value
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute where Transformer: ReversibleTransformer {
|
||||
/// Initialize a TransformedAttribute from its transformed value. The
|
||||
/// RawValue, which is what gets encoded/decoded, is determined using
|
||||
/// The Transformer's reverse function.
|
||||
public init(transformedValue: Transformer.To) throws {
|
||||
self.value = transformedValue
|
||||
rawValue = try Transformer.reverse(value)
|
||||
@@ -45,15 +53,35 @@ extension TransformedAttribute: CustomStringConvertible {
|
||||
|
||||
extension TransformedAttribute: Equatable where Transformer.From: Equatable, Transformer.To: Equatable {}
|
||||
|
||||
extension TransformedAttribute where Transformer == IdentityTransformer<RawValue> {
|
||||
// If we are using the identity transform, we can skip the transform and guarantee no
|
||||
// error is thrown.
|
||||
// MARK: ValidatedAttribute
|
||||
|
||||
/// A ValidatedAttribute does not transform its raw value, but it throws
|
||||
/// an error if the raw value does not match expectations.
|
||||
public typealias ValidatedAttribute<RawValue: Codable, Validator: JSONAPI.Validator> = TransformedAttribute<RawValue, Validator> where RawValue == Validator.From
|
||||
|
||||
// MARK: Attribute
|
||||
|
||||
/// An Attribute simply represents a type that can be encoded and decoded.
|
||||
public struct Attribute<RawValue: Codable>: AttributeType {
|
||||
let attribute: TransformedAttribute<RawValue, IdentityTransformer<RawValue>>
|
||||
|
||||
public var value: RawValue {
|
||||
return attribute.value
|
||||
}
|
||||
|
||||
public init(value: RawValue) {
|
||||
rawValue = value
|
||||
self.value = value
|
||||
attribute = .init(value: value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "Attribute<\(String(describing: RawValue.self))>(\(String(describing: value)))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: Equatable where RawValue: Equatable {}
|
||||
|
||||
// MARK: - Codable
|
||||
extension TransformedAttribute {
|
||||
public init(from decoder: Decoder) throws {
|
||||
@@ -85,6 +113,31 @@ extension TransformedAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
// A little trickery follows. If the value is nil, the
|
||||
// container.decode(Value.self) will fail even if Value
|
||||
// is Optional. However, we can check if decoding nil
|
||||
// succeeds and then attempt to coerce nil to a Value
|
||||
// type at which point we can store nil in `value`.
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? RawValue {
|
||||
attribute = .init(value: val)
|
||||
} else {
|
||||
attribute = try container.decode(TransformedAttribute<RawValue, IdentityTransformer<RawValue>>.self)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(attribute)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Attribute decoding and encoding defaults
|
||||
|
||||
extension AttributeType {
|
||||
|
||||
@@ -433,21 +433,21 @@ public extension EntityProxy {
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `entity[\.propertyName]` instead
|
||||
/// of `entity.relationships.propertyName`.
|
||||
subscript<T, TFRM: Transformer>(_ path: KeyPath<Description.Attributes, TransformedAttribute<T, TFRM>>) -> TFRM.To {
|
||||
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T>) -> T.ValueType {
|
||||
return attributes[keyPath: path].value
|
||||
}
|
||||
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `entity[\.propertyName]` instead
|
||||
/// of `entity.relationships.propertyName`.
|
||||
subscript<T, TFRM: Transformer>(_ path: KeyPath<Description.Attributes, TransformedAttribute<T, TFRM>?>) -> TFRM.To? {
|
||||
subscript<T: AttributeType>(_ path: KeyPath<Description.Attributes, T?>) -> T.ValueType? {
|
||||
return attributes[keyPath: path]?.value
|
||||
}
|
||||
|
||||
/// Access the attribute at the given keypath. This just
|
||||
/// allows you to write `entity[\.propertyName]` instead
|
||||
/// of `entity.relationships.propertyName`.
|
||||
subscript<T, TFRM: Transformer, U>(_ path: KeyPath<Description.Attributes, TransformedAttribute<T, TFRM>?>) -> U? where TFRM.To == U? {
|
||||
subscript<T: AttributeType, U>(_ path: KeyPath<Description.Attributes, T?>) -> U? where T.ValueType == U? {
|
||||
// Implementation Note: Handles Transform that returns optional
|
||||
// type.
|
||||
return attributes[keyPath: path].flatMap { $0.value }
|
||||
|
||||
@@ -799,3 +799,360 @@ 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))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Created by Mathew Polzin on 11/17/18.
|
||||
//
|
||||
|
||||
/// A Transformer simply defines a static function that transforms a value.
|
||||
public protocol Transformer {
|
||||
associatedtype From
|
||||
associatedtype To
|
||||
@@ -12,10 +13,13 @@ public protocol Transformer {
|
||||
static func transform(_ value: From) throws -> To
|
||||
}
|
||||
|
||||
/// ReversibleTransformers define a function that reverses the transform
|
||||
/// operation.
|
||||
public protocol ReversibleTransformer: Transformer {
|
||||
static func reverse(_ value: To) throws -> From
|
||||
}
|
||||
|
||||
/// The IdentityTransformer does not perform any transformation on a value.
|
||||
public enum IdentityTransformer<T>: ReversibleTransformer {
|
||||
public static func transform(_ value: T) throws -> T { return value }
|
||||
public static func reverse(_ value: T) throws -> T { return value }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import JSONAPI
|
||||
|
||||
extension TransformedAttribute: ExpressibleByUnicodeScalarLiteral where RawValue: ExpressibleByUnicodeScalarLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByUnicodeScalarLiteral where RawValue: ExpressibleByUnicodeScalarLiteral {
|
||||
public typealias UnicodeScalarLiteralType = RawValue.UnicodeScalarLiteralType
|
||||
|
||||
public init(unicodeScalarLiteral value: RawValue.UnicodeScalarLiteralType) {
|
||||
@@ -9,7 +9,7 @@ extension TransformedAttribute: ExpressibleByUnicodeScalarLiteral where RawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByExtendedGraphemeClusterLiteral where RawValue: ExpressibleByExtendedGraphemeClusterLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByExtendedGraphemeClusterLiteral where RawValue: ExpressibleByExtendedGraphemeClusterLiteral {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = RawValue.ExtendedGraphemeClusterLiteralType
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: RawValue.ExtendedGraphemeClusterLiteralType) {
|
||||
@@ -17,7 +17,7 @@ extension TransformedAttribute: ExpressibleByExtendedGraphemeClusterLiteral wher
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByStringLiteral where RawValue: ExpressibleByStringLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByStringLiteral where RawValue: ExpressibleByStringLiteral {
|
||||
public typealias StringLiteralType = RawValue.StringLiteralType
|
||||
|
||||
public init(stringLiteral value: RawValue.StringLiteralType) {
|
||||
@@ -25,13 +25,13 @@ extension TransformedAttribute: ExpressibleByStringLiteral where RawValue: Expre
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByNilLiteral where RawValue: ExpressibleByNilLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByNilLiteral where RawValue: ExpressibleByNilLiteral {
|
||||
public init(nilLiteral: ()) {
|
||||
self.init(value: RawValue(nilLiteral: ()))
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByFloatLiteral where RawValue: ExpressibleByFloatLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByFloatLiteral where RawValue: ExpressibleByFloatLiteral {
|
||||
public typealias FloatLiteralType = RawValue.FloatLiteralType
|
||||
|
||||
public init(floatLiteral value: RawValue.FloatLiteralType) {
|
||||
@@ -47,7 +47,7 @@ extension Optional: ExpressibleByFloatLiteral where Wrapped: ExpressibleByFloatL
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByBooleanLiteral where RawValue: ExpressibleByBooleanLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByBooleanLiteral where RawValue: ExpressibleByBooleanLiteral {
|
||||
public typealias BooleanLiteralType = RawValue.BooleanLiteralType
|
||||
|
||||
public init(booleanLiteral value: BooleanLiteralType) {
|
||||
@@ -63,7 +63,7 @@ extension Optional: ExpressibleByBooleanLiteral where Wrapped: ExpressibleByBool
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByIntegerLiteral where RawValue: ExpressibleByIntegerLiteral, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByIntegerLiteral where RawValue: ExpressibleByIntegerLiteral {
|
||||
public typealias IntegerLiteralType = RawValue.IntegerLiteralType
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
@@ -91,7 +91,7 @@ public protocol DictionaryType {
|
||||
}
|
||||
extension Dictionary: DictionaryType {}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByDictionaryLiteral where RawValue: DictionaryType, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByDictionaryLiteral where RawValue: DictionaryType {
|
||||
public typealias Key = RawValue.Key
|
||||
|
||||
public typealias Value = RawValue.Value
|
||||
@@ -121,7 +121,7 @@ public protocol ArrayType {
|
||||
extension Array: ArrayType {}
|
||||
extension ArraySlice: ArrayType {}
|
||||
|
||||
extension TransformedAttribute: ExpressibleByArrayLiteral where RawValue: ArrayType, Transformer == IdentityTransformer<RawValue> {
|
||||
extension Attribute: ExpressibleByArrayLiteral where RawValue: ArrayType {
|
||||
public typealias ArrayLiteralElement = RawValue.Element
|
||||
|
||||
public init(arrayLiteral elements: ArrayLiteralElement...) {
|
||||
|
||||
@@ -41,6 +41,7 @@ extension Optional: OptionalArray where Wrapped: ArrayType {}
|
||||
|
||||
private protocol AttributeTypeWithOptionalArray {}
|
||||
extension TransformedAttribute: AttributeTypeWithOptionalArray where RawValue: OptionalArray {}
|
||||
extension Attribute: AttributeTypeWithOptionalArray where RawValue: OptionalArray {}
|
||||
|
||||
private protocol OptionalRelationshipType {}
|
||||
extension Optional: OptionalRelationshipType where Wrapped: RelationshipType {}
|
||||
@@ -49,6 +50,10 @@ private protocol _RelationshipType {}
|
||||
extension ToOneRelationship: _RelationshipType {}
|
||||
extension ToManyRelationship: _RelationshipType {}
|
||||
|
||||
private protocol _AttributeType {}
|
||||
extension TransformedAttribute: _AttributeType {}
|
||||
extension Attribute: _AttributeType {}
|
||||
|
||||
public extension Entity {
|
||||
public static func check(_ entity: Entity) throws {
|
||||
var problems = [EntityCheckError]()
|
||||
@@ -60,7 +65,7 @@ public extension Entity {
|
||||
}
|
||||
|
||||
for attribute in attributesMirror.children {
|
||||
if attribute.value as? AttributeType == nil,
|
||||
if attribute.value as? _AttributeType == nil,
|
||||
attribute.value as? OptionalAttributeType == nil {
|
||||
problems.append(.nonAttribute(named: attribute.label ?? "unnamed"))
|
||||
}
|
||||
|
||||
@@ -10,12 +10,8 @@ import JSONAPI
|
||||
|
||||
class AttributeTests: XCTestCase {
|
||||
|
||||
func test_AttributeIsTransformedAttribute() {
|
||||
XCTAssertEqual(try TransformedAttribute<String, IdentityTransformer<String>>(rawValue: "hello"), try Attribute<String>(rawValue: "hello"))
|
||||
}
|
||||
|
||||
func test_AttributeNonThrowingConstructor() {
|
||||
XCTAssertEqual(try Attribute<String>(rawValue: "hello"), Attribute<String>(value: "hello"))
|
||||
func test_AttributeConstructor() {
|
||||
XCTAssertEqual(Attribute<String>(value: "hello").value, "hello")
|
||||
}
|
||||
|
||||
func test_TransformedAttributeNoThrow() {
|
||||
|
||||
@@ -73,6 +73,7 @@ extension DocumentTests {
|
||||
|
||||
XCTAssertTrue(document.body.isError)
|
||||
XCTAssertEqual(document.body.meta, NoMetadata())
|
||||
XCTAssertNil(document.body.data)
|
||||
XCTAssertNil(document.body.primaryResource)
|
||||
XCTAssertNil(document.body.includes)
|
||||
|
||||
@@ -525,6 +526,7 @@ extension DocumentTests {
|
||||
XCTAssertNil(document.body.errors)
|
||||
XCTAssertNotNil(document.body.primaryResource)
|
||||
XCTAssertEqual(document.body.primaryResource?.value.id.rawValue, "1")
|
||||
XCTAssertEqual(document.body.data?.primary, document.body.primaryResource)
|
||||
XCTAssertEqual(document.body.includes?.count, 0)
|
||||
XCTAssertEqual(document.body.meta, NoMetadata())
|
||||
}
|
||||
@@ -961,6 +963,55 @@ 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
|
||||
import JSONAPI
|
||||
@testable import JSONAPI
|
||||
|
||||
class IncludedTests: XCTestCase {
|
||||
|
||||
@@ -139,6 +139,57 @@ 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
|
||||
@@ -232,4 +283,26 @@ 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,3 +354,161 @@ 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)!
|
||||
|
||||
@@ -230,6 +230,191 @@ 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
|
||||
@@ -303,6 +488,35 @@ 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
|
||||
@@ -342,6 +556,14 @@ 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
|
||||
@@ -435,4 +657,26 @@ 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,3 +100,17 @@ 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)!
|
||||
|
||||
@@ -25,7 +25,10 @@ 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!")), relationships: .none, meta: .none, links: .none))
|
||||
attributes: ArticleType.Attributes(title: .init(value: "JSON:API paints my bikeshed!")),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none))
|
||||
}
|
||||
|
||||
func test_singleResourceBody_encode() {
|
||||
@@ -38,9 +41,21 @@ 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!")), 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)
|
||||
Article(id: .init(rawValue: "1"),
|
||||
attributes: .init(title: .init(value: "JSON:API paints my bikeshed!")),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none),
|
||||
Article(id: .init(rawValue: "2"),
|
||||
attributes: .init(title: .init(value: "Sick")),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none),
|
||||
Article(id: .init(rawValue: "3"),
|
||||
attributes: .init(title: .init(value: "Hello World")),
|
||||
relationships: .none,
|
||||
meta: .none,
|
||||
links: .none)
|
||||
])
|
||||
}
|
||||
|
||||
@@ -61,6 +76,31 @@ 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" }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user