Compare commits

...

12 Commits

Author SHA1 Message Date
Mathew Polzin 67a43be26c update linuxmain 2018-12-08 20:05:00 -08:00
Mathew Polzin 8f279ce191 Add tests around nullable attributes and remove unnecessary encoding code 2018-12-08 20:04:11 -08:00
Mathew Polzin c9d388579f Made it much more convenient to work with Non-EntityType relationships. Discovered and fixed a bug where nullable relationships were encoded incorrectly. 2018-12-08 19:48:10 -08:00
Mathew Polzin 1061283905 loosen the grip entity had on Ids 2018-12-08 01:17:47 -08:00
Mathew Polzin 08949d0a93 Move encoding error type to its own file. Restructure Relatable and OptionalRelatable to not be dependent upon EntityDescription 2018-12-08 00:36:05 -08:00
Mathew Polzin 3047e2d23a Add access to computed attributes that are not AttributeType 2018-12-07 21:19:08 -08:00
Mathew Polzin 41a2a01788 Added support for relationship operator ~> to optional relationships 2018-12-07 20:59:39 -08:00
Mathew Polzin 53f7f55e07 Add Poly7 and Include7 because why not 2018-12-06 18:38:20 -08:00
Mathew Polzin d8d030286d Add a little bit of code doc 2018-12-06 18:12:56 -08:00
Mathew Polzin 005a981bf7 Add a couple of missing initializers to Entity 2018-12-05 22:51:12 -08:00
Mathew Polzin fad45203dd small refactor to consider ToOneRelationship with no meta or links to be even more synonymous with 'pointer' 2018-12-05 22:42:34 -08:00
Mathew Polzin 3468cb555a Remove Result dependency in favor of a super stripped down internally scoped Result enum. This won't interfere with other Result libraries and I was not exposing the Result anyway. 2018-12-05 22:02:59 -08:00
27 changed files with 977 additions and 124 deletions
+1 -9
View File
@@ -1,15 +1,7 @@
{
"object": {
"pins": [
{
"package": "Result",
"repositoryURL": "https://github.com/mattpolzin/Result",
"state": {
"branch": "master",
"revision": "b98e238da6ea030fa7862ae6fd6500552370019c",
"version": null
}
}
]
},
"version": 1
+1 -3
View File
@@ -14,13 +14,11 @@ let package = Package(
targets: ["JSONAPITestLib"])
],
dependencies: [
// antitypical/Result without the Foundation requirement:
.package(url: "https://github.com/mattpolzin/Result", .branch("master"))
],
targets: [
.target(
name: "JSONAPI",
dependencies: ["Result"]),
dependencies: []),
.target(
name: "JSONAPITestLib",
dependencies: ["JSONAPI"]),
+7
View File
@@ -115,4 +115,11 @@ extension Includes where I: _Poly6 {
}
// MARK: - 7 includes
public typealias Include7 = Poly7
extension Includes where I: _Poly7 {
public subscript(_ lookup: I.G.Type) -> [I.G] {
return values.compactMap { $0.g }
}
}
// MARK: - 8 includes
+14
View File
@@ -0,0 +1,14 @@
//
// EncodingError.swift
// JSONAPI
//
// Created by Mathew Polzin on 12/7/18.
//
public enum JSONAPIEncodingError: Swift.Error {
case typeMismatch(expected: String, found: String)
case illegalEncoding(String)
case illegalDecoding(String)
case missingOrMalformedMetadata
case missingOrMalformedLinks
}
+12 -13
View File
@@ -8,6 +8,8 @@
public protocol AttributeType: Codable {
}
// MARK: TransformedAttribute
/// A TransformedAttribute takes a Codable type and attempts to turn it into another type.
public struct TransformedAttribute<RawValue: Codable, Transformer: JSONAPI.Transformer>: AttributeType where Transformer.From == RawValue {
private let rawValue: RawValue
@@ -19,6 +21,15 @@ 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: ReversibleTransformer {
public init(transformedValue: Transformer.To) throws {
self.value = transformedValue
@@ -43,10 +54,6 @@ extension TransformedAttribute where Transformer == IdentityTransformer<RawValue
}
}
public typealias ValidatedAttribute<RawValue: Codable, Validator: JSONAPI.Validator> = TransformedAttribute<RawValue, Validator> where RawValue == Validator.From
public typealias Attribute<T: Codable> = TransformedAttribute<T, IdentityTransformer<T>>
// MARK: - Codable
extension TransformedAttribute {
public init(from decoder: Decoder) throws {
@@ -73,15 +80,7 @@ extension TransformedAttribute {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
// See note in decode above about the weirdness
// going on here.
let anyNil: Any? = nil
if let _ = anyNil as? Transformer.From,
(rawValue as Any?) == nil {
try container.encodeNil()
}
try container.encode(rawValue)
}
}
+72 -9
View File
@@ -26,22 +26,26 @@ public struct NoAttributes: Attributes {
public static var none: NoAttributes { return .init() }
}
/// Something that is JSONTyped provides a String representation
/// of its type.
public protocol JSONTyped {
static var type: String { get }
}
/// An `EntityDescription` describes a JSON API
/// Resource Object. The Resource Object
/// itself is encoded and decoded as an
/// `Entity`, which gets specialized on an
/// `EntityDescription`.
public protocol EntityDescription {
public protocol EntityDescription: JSONTyped {
associatedtype Attributes: JSONAPI.Attributes
associatedtype Relationships: JSONAPI.Relationships
static var type: String { get }
}
/// EntityProxy is a protocol that can be used to create
/// types that _act_ like Entities but cannot be encoded
/// or decoded as Entities.
public protocol EntityProxy: Equatable {
public protocol EntityProxy: Equatable, JSONTyped {
associatedtype Description: EntityDescription
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
@@ -108,9 +112,8 @@ public struct Entity<Description: JSONAPI.EntityDescription, MetaType: JSONAPI.M
}
}
extension Entity: IdentifiableEntityType, Relatable, WrappedRelatable where EntityRawIdType: JSONAPI.RawIdType {
extension Entity: Identifiable, IdentifiableEntityType, Relatable where EntityRawIdType: JSONAPI.RawIdType {
public typealias Identifier = Entity.Id
public typealias WrappedIdentifier = Identifier
}
extension Entity: CustomStringConvertible {
@@ -326,6 +329,12 @@ extension Entity where MetaType == NoMetadata, EntityRawIdType: CreatableRawIdTy
}
}
extension Entity where MetaType == NoMetadata, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships, links: LinksType) {
self.init(attributes: attributes, relationships: relationships, meta: .none, links: links)
}
}
extension Entity where LinksType == NoLinks {
public init(id: Entity.Id, attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType) {
self.init(id: id, attributes: attributes, relationships: relationships, meta: meta, links: .none)
@@ -338,6 +347,12 @@ extension Entity where LinksType == NoLinks, EntityRawIdType: CreatableRawIdType
}
}
extension Entity where LinksType == NoLinks, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType) {
self.init(attributes: attributes, relationships: relationships, meta: meta, links: .none)
}
}
extension Entity where MetaType == NoMetadata, LinksType == NoLinks {
public init(id: Entity.Id, attributes: Description.Attributes, relationships: Description.Relationships) {
self.init(id: id, attributes: attributes, relationships: relationships, meta: .none, links: .none)
@@ -350,12 +365,24 @@ extension Entity where MetaType == NoMetadata, LinksType == NoLinks, EntityRawId
}
}
extension Entity where MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == Unidentified {
public init(attributes: Description.Attributes, relationships: Description.Relationships) {
self.init(attributes: attributes, relationships: relationships, meta: .none, links: .none)
}
}
// MARK: Pointer for Relationships use.
public extension Entity where EntityRawIdType: JSONAPI.RawIdType {
/// An Entity.Pointer is a `ToOneRelationship` with no metadata or links.
/// This is just a convenient way to reference an Entity given that
/// other Entities' Relationships can be built up from it.
public typealias Pointer = ToOneRelationship<Entity, NoMetadata, NoLinks>
/// Get a pointer to this entity that can be used as a
/// relationship to another entity.
public var pointer: ToOneRelationship<Entity, NoMetadata, NoLinks> {
return ToOneRelationship(entity: self, meta: .none, links: .none)
public var pointer: Pointer {
return Pointer(entity: self)
}
public func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<Entity, MType, LType> {
@@ -383,8 +410,19 @@ public extension EntityProxy {
/// 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? {
// Implementation Note: Handles Transform that returns optional
// type.
return attributes[keyPath: path].flatMap { $0.value }
}
/// Access the computed attribute at the given keypath. This just
/// allows you to write `entity[\.propertyName]` instead
/// of `entity.relationships.propertyName`.
subscript<T>(_ path: KeyPath<Description.Attributes, T>) -> T {
// Implementation Note: Handles attributes that are not
// AttributeType. These should only exist as computed properties.
return attributes[keyPath: path]
}
}
// MARK: Relationship Access
@@ -392,16 +430,41 @@ public extension EntityProxy {
/// Access to an Id of a `ToOneRelationship`.
/// This allows you to write `entity ~> \.other` instead
/// of `entity.relationships.other.id`.
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedIdentifier {
public static func ~><OtherEntity: Identifiable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.Identifier {
return entity.relationships[keyPath: path].id
}
/// Access to an Id of an optional `ToOneRelationship`.
/// This allows you to write `entity ~> \.other` instead
/// of `entity.relationships.other?.id`.
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier {
// Implementation Note: This signature applies to `ToOneRelationship<E?, _, _>?`
// whereas the one below applies to `ToOneRelationship<E, _, _>?`
return entity.relationships[keyPath: path]?.id
}
/// Access to an Id of an optional `ToOneRelationship`.
/// This allows you to write `entity ~> \.other` instead
/// of `entity.relationships.other?.id`.
public static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier? {
// Implementation Note: This signature applies to `ToOneRelationship<E, _, _>?`
// whereas the one above applies to `ToOneRelationship<E?, _, _>?`
return entity.relationships[keyPath: path]?.id
}
/// Access to all Ids of a `ToManyRelationship`.
/// This allows you to write `entity ~> \.others` instead
/// of `entity.relationships.others.ids`.
public static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>>) -> [OtherEntity.Identifier] {
return entity.relationships[keyPath: path].ids
}
/// Access to all Ids of an optional `ToManyRelationship`.
/// This allows you to write `entity ~> \.others` instead
/// of `entity.relationships.others?.ids`.
public static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>?>) -> [OtherEntity.Identifier]? {
return entity.relationships[keyPath: path]?.ids
}
}
infix operator ~>
+30 -7
View File
@@ -34,13 +34,31 @@ public struct Unidentified: MaybeRawId, CustomStringConvertible {
public var description: String { return "Unidentified" }
}
public protocol MaybeId: Codable {
associatedtype EntityType: JSONAPI.EntityProxy
public protocol OptionalId: Codable {
associatedtype IdentifiableType: JSONAPI.JSONTyped
associatedtype RawType: MaybeRawId
var rawValue: RawType { get }
init(rawValue: RawType)
}
public protocol IdType: MaybeId, CustomStringConvertible, Hashable where RawType: RawIdType {
var rawValue: RawType { get }
public protocol IdType: OptionalId, CustomStringConvertible, Hashable where RawType: RawIdType {}
extension Optional: MaybeRawId where Wrapped: Codable & Equatable {}
extension Optional: OptionalId where Wrapped: IdType {
public typealias IdentifiableType = Wrapped.IdentifiableType
public typealias RawType = Wrapped.RawType?
public var rawValue: Wrapped.RawType? {
guard case .some(let value) = self else {
return nil
}
return value.rawValue
}
public init(rawValue: Wrapped.RawType?) {
self = rawValue.map { Wrapped(rawValue: $0) }
}
}
public extension IdType {
@@ -53,7 +71,7 @@ public protocol CreatableIdType: IdType {
/// An Entity ID. These IDs can be encoded to or decoded from
/// JSON API IDs.
public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityProxy>: Codable, Equatable, MaybeId {
public struct Id<RawType: MaybeRawId, IdentifiableType: JSONAPI.JSONTyped>: Equatable, OptionalId {
public let rawValue: RawType
@@ -63,7 +81,8 @@ public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityProxy>: Codable,
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
rawValue = try container.decode(RawType.self)
let rawValue = try container.decode(RawType.self)
self.init(rawValue: rawValue)
}
public func encode(to encoder: Encoder) throws {
@@ -72,7 +91,11 @@ public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityProxy>: Codable,
}
}
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {}
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {
public static func id(from rawValue: RawType) -> Id<RawType, IdentifiableType> {
return Id(rawValue: rawValue)
}
}
extension Id: CreatableIdType where RawType: CreatableRawIdType {
public init() {
+156 -3
View File
@@ -5,8 +5,6 @@
// Created by Mathew Polzin on 11/22/18.
//
import Result
/// Poly is a protocol to which types that
/// are polymorphic belong to. Specifically,
/// Poly1, Poly2, Poly3, etc. types conform
@@ -28,7 +26,7 @@ private func decode<Entity: JSONAPI.EntityType>(_ type: Entity.Type, from contai
} catch (let err) {
ret = .failure(DecodingError.typeMismatch(Entity.Description.self,
.init(codingPath: container.codingPath,
debugDescription: err.localizedDescription,
debugDescription: String(describing: err),
underlyingError: err)))
}
return ret
@@ -646,3 +644,158 @@ extension Poly6: CustomStringConvertible {
return "Poly(\(str))"
}
}
// MARK: - 7 types
public protocol _Poly7: _Poly6 {
associatedtype G: EntityType
var g: G? { get }
init(_ g: G)
}
public extension _Poly7 {
subscript(_ lookup: G.Type) -> G? {
return g
}
}
public enum Poly7<A: EntityType, B: EntityType, C: EntityType, D: EntityType, E: EntityType, F: EntityType, G: EntityType>: _Poly7 {
case a(A)
case b(B)
case c(C)
case d(D)
case e(E)
case f(F)
case g(G)
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 init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let attempts = [
try decode(A.self, from: container).map { Poly7.a($0) },
try decode(B.self, from: container).map { Poly7.b($0) },
try decode(C.self, from: container).map { Poly7.c($0) },
try decode(D.self, from: container).map { Poly7.d($0) },
try decode(E.self, from: container).map { Poly7.e($0) },
try decode(F.self, from: container).map { Poly7.f($0) },
try decode(G.self, from: container).map { Poly7.g($0) }]
let maybeVal: Poly7<A, B, C, D, E, F, G>? = attempts
.compactMap { $0.value }
.first
guard let val = maybeVal else {
throw EncodingError.invalidValue(Poly7<A, B, C, D, E, F, G>.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)
}
}
}
extension Poly7: 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)
}
return "Poly(\(str))"
}
}
+56 -53
View File
@@ -5,7 +5,7 @@
// Created by Mathew Polzin on 8/31/18.
//
public protocol RelationshipType: Codable {
public protocol RelationshipType {
associatedtype LinksType
associatedtype MetaType
@@ -17,14 +17,14 @@ public protocol RelationshipType: Codable {
/// a JSON API "Resource Linkage."
/// See https://jsonapi.org/format/#document-resource-object-linkage
/// A convenient typealias might make your code much more legible: `One<EntityDescription>`
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
public struct ToOneRelationship<Identifiable: JSONAPI.Identifiable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
public let id: Relatable.WrappedIdentifier
public let id: Identifiable.Identifier
public let meta: MetaType
public let links: LinksType
public init(id: Relatable.WrappedIdentifier, meta: MetaType, links: LinksType) {
public init(id: Identifiable.Identifier, meta: MetaType, links: LinksType) {
self.id = id
self.meta = meta
self.links = links
@@ -32,31 +32,31 @@ public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable, MetaType:
}
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init(id: Relatable.WrappedIdentifier) {
public init(id: Identifiable.Identifier) {
self.init(id: id, meta: .none, links: .none)
}
}
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier {
public init<E: EntityType>(entity: E, meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
extension ToOneRelationship {
public init<E: EntityType>(entity: E, meta: MetaType, links: LinksType) where E.Id == Identifiable.Identifier {
self.init(id: entity.id, meta: meta, links: links)
}
}
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier, MetaType == NoMetadata, LinksType == NoLinks {
public init<E: EntityType>(entity: E) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init<E: EntityType>(entity: E) where E.Id == Identifiable.Identifier {
self.init(id: entity.id, meta: .none, links: .none)
}
}
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier? {
public init<E: EntityType>(entity: E?, meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
extension ToOneRelationship where Identifiable: OptionalRelatable {
public init<E: EntityType>(entity: E?, meta: MetaType, links: LinksType) where E.Id == Identifiable.Wrapped.Identifier {
self.init(id: entity?.id, meta: meta, links: links)
}
}
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier?, MetaType == NoMetadata, LinksType == NoLinks {
public init<E: EntityType>(entity: E?) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks {
public init<E: EntityType>(entity: E?) where E.Id == Identifiable.Wrapped.Identifier {
self.init(id: entity?.id, meta: .none, links: .none)
}
}
@@ -78,20 +78,20 @@ public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI
self.links = links
}
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedIdentifier == Relatable.Identifier {
ids = relationships.map { $0.id }
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.Identifier == Relatable.Identifier {
ids = pointers.map { $0.id }
self.meta = meta
self.links = links
}
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Id == Relatable.Identifier {
self.init(ids: entities.map { $0.id }, meta: meta, links: links)
}
private init(meta: MetaType, links: LinksType) {
self.init(ids: [], meta: meta, links: links)
}
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
return ToManyRelationship(meta: meta, links: links)
}
@@ -103,36 +103,38 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks
self.init(ids: ids, meta: .none, links: .none)
}
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.WrappedIdentifier == Relatable.Identifier {
self.init(relationships: relationships, meta: .none, links: .none)
public init<T: JSONAPI.Identifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.Identifier == Relatable.Identifier {
self.init(pointers: pointers, meta: .none, links: .none)
}
public static var none: ToManyRelationship {
return .none(withMeta: .none, links: .none)
}
public init<E: EntityType>(entities: [E]) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
public init<E: EntityType>(entities: [E]) where E.Id == Relatable.Identifier {
self.init(entities: entities, meta: .none, links: .none)
}
}
/// The WrappedRelatable (a.k.a OptionalRelatable) protocol
/// describes Optional<T: Relatable> and Relatable types.
public protocol WrappedRelatable: Codable, Equatable {
associatedtype Description: EntityDescription
associatedtype Identifier: JSONAPI.IdType
associatedtype WrappedIdentifier: Codable, Equatable
public protocol Identifiable: JSONTyped {
associatedtype Identifier: Equatable
}
public typealias OptionalRelatable = WrappedRelatable
/// The Relatable protocol describes anything that
/// has an IdType Identifier
public protocol Relatable: WrappedRelatable {}
public protocol Relatable: Identifiable where Identifier: JSONAPI.IdType {
}
extension Optional: OptionalRelatable where Wrapped: Relatable {
public typealias Description = Wrapped.Description
public typealias Identifier = Wrapped.Identifier
public typealias WrappedIdentifier = Identifier?
/// OptionalRelatable just describes an Optional
/// with a Reltable Wrapped type.
public protocol OptionalRelatable: Identifiable where Identifier == Wrapped.Identifier? {
associatedtype Wrapped: JSONAPI.Relatable
}
extension Optional: Identifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
public typealias Identifier = Wrapped.Identifier?
public static var type: String { return Wrapped.type }
}
// MARK: Codable
@@ -146,15 +148,7 @@ private enum ResourceIdentifierCodingKeys: String, CodingKey {
case entityType = "type"
}
public enum JSONAPIEncodingError: Swift.Error {
case typeMismatch(expected: String, found: String)
case illegalEncoding(String)
case illegalDecoding(String)
case missingOrMalformedMetadata
case missingOrMalformedLinks
}
extension ToOneRelationship {
extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
@@ -177,7 +171,7 @@ extension ToOneRelationship {
// type at which point we can store nil in `id`.
let anyNil: Any? = nil
if try container.decodeNil(forKey: .data),
let val = anyNil as? Relatable.WrappedIdentifier {
let val = anyNil as? Identifiable.Identifier {
id = val
return
}
@@ -186,11 +180,11 @@ extension ToOneRelationship {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.Description.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.Description.type, found: type)
guard type == Identifiable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.type, found: type)
}
id = try identifier.decode(Relatable.WrappedIdentifier.self, forKey: .id)
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
}
public func encode(to encoder: Encoder) throws {
@@ -208,14 +202,23 @@ extension ToOneRelationship {
try container.encode(links, forKey: .links)
}
// If id is nil, instead of {id: , type: } we will just
// encode `null`
let anyNil: Any? = nil
let nilId = anyNil as? Identifiable.Identifier
guard id != nilId else {
try container.encodeNil(forKey: .data)
return
}
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
try identifier.encode(id, forKey: .id)
try identifier.encode(Relatable.Description.type, forKey: .entityType)
try identifier.encode(id.rawValue, forKey: .id)
try identifier.encode(Identifiable.type, forKey: .entityType)
}
}
extension ToManyRelationship {
extension ToManyRelationship: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
@@ -239,11 +242,11 @@ extension ToManyRelationship {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.Description.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.Description.type, found: type)
guard type == Relatable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.type, found: type)
}
newIds.append(try identifier.decode(Relatable.Identifier.self, forKey: .id))
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
}
ids = newIds
}
@@ -264,8 +267,8 @@ extension ToManyRelationship {
for id in ids {
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
try identifier.encode(id, forKey: .id)
try identifier.encode(Relatable.Description.type, forKey: .entityType)
try identifier.encode(id.rawValue, forKey: .id)
try identifier.encode(Relatable.type, forKey: .entityType)
}
}
}
+45
View File
@@ -0,0 +1,45 @@
//
// Result.swift
// JSONAPI
//
// Created by Mathew Polzin on 12/5/18.
//
enum Result<T, E: Swift.Error> {
case success(T)
case failure(E)
var value: T? {
guard case .success(let val) = self else {
return nil
}
return val
}
var error: E? {
guard case .failure(let err) = self else {
return nil
}
return err
}
func map<U>(_ transform: (T) -> U) -> Result<U, E> {
switch self {
case .failure(let err):
return .failure(err)
case .success(let val):
return .success(transform(val))
}
}
}
extension Result: CustomStringConvertible where T: CustomStringConvertible, E: CustomStringConvertible {
var description: String {
switch self {
case .success(let val):
return String(describing: val)
case .failure(let err):
return String(describing: err)
}
}
}
+2 -1
View File
@@ -76,7 +76,8 @@ public extension Entity {
}
for relationship in relationshipsMirror.children {
if relationship.value as? _RelationshipType == nil {
if relationship.value as? _RelationshipType == nil,
relationship.value as? OptionalRelationshipType == nil {
problems.append(.nonRelationship(named: relationship.label ?? "unnamed"))
}
}
@@ -7,34 +7,34 @@
import JSONAPI
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
extension ToOneRelationship: ExpressibleByNilLiteral where Identifiable.Identifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public init(nilLiteral: ()) {
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
self.init(id: Identifiable.Identifier(nilLiteral: ()))
}
}
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias UnicodeScalarLiteralType = Relatable.WrappedIdentifier.UnicodeScalarLiteralType
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Identifiable.Identifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias UnicodeScalarLiteralType = Identifiable.Identifier.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(id: Relatable.WrappedIdentifier(unicodeScalarLiteral: value))
self.init(id: Identifiable.Identifier(unicodeScalarLiteral: value))
}
}
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias ExtendedGraphemeClusterLiteralType = Relatable.WrappedIdentifier.ExtendedGraphemeClusterLiteralType
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Identifiable.Identifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias ExtendedGraphemeClusterLiteralType = Identifiable.Identifier.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(id: Relatable.WrappedIdentifier(extendedGraphemeClusterLiteral: value))
self.init(id: Identifiable.Identifier(extendedGraphemeClusterLiteral: value))
}
}
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias StringLiteralType = Relatable.WrappedIdentifier.StringLiteralType
extension ToOneRelationship: ExpressibleByStringLiteral where Identifiable.Identifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias StringLiteralType = Identifiable.Identifier.StringLiteralType
public init(stringLiteral value: StringLiteralType) {
self.init(id: Relatable.WrappedIdentifier(stringLiteral: value))
self.init(id: Identifiable.Identifier(stringLiteral: value))
}
}
@@ -29,6 +29,29 @@ class AttributeTests: XCTestCase {
func test_TransformedAttributeReversNoThrow() {
XCTAssertNoThrow(try TransformedAttribute<String, TestTransformer>(transformedValue: 10))
}
func test_NullableIsNullIfNil() {
struct Wrapper: Codable {
let dummy: Attribute<String?>
}
let data = encoded(value: Wrapper(dummy: .init(value: nil)))
let string = String(data: data, encoding: .utf8)!
XCTAssertEqual(string, "{\"dummy\":null}")
}
func test_NullableIsEqualToNonNullableIfNotNil() {
struct Wrapper1: Codable {
let dummy: Attribute<String?>
}
struct Wrapper2: Codable {
let dummy: Attribute<String>
}
let data1 = encoded(value: Wrapper1(dummy: .init(value: "hello")))
let data2 = encoded(value: Wrapper2(dummy: .init(value: "hello")))
XCTAssertEqual(data1, data2)
}
}
// MARK: Test types
@@ -27,6 +27,13 @@ class ComputedPropertiesTests: XCTestCase {
let entity = decoded(type: TestType.self, data: computed_property_attribute)
XCTAssertEqual(entity[\.computed], "Sarah2")
XCTAssertEqual(entity[\.secretsOut], "shhhh")
}
func test_ComputedNonAttributeAccess() {
let entity = decoded(type: TestType.self, data: computed_property_attribute)
XCTAssertEqual(entity[\.computed2], "Sarah2")
}
func test_ComputedRelationshipAccess() {
@@ -43,9 +50,19 @@ extension ComputedPropertiesTests {
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
private let secret: Attribute<String>
public var computed: Attribute<String> {
return name.map { $0 + "2" }
}
public var computed2: String {
return computed.value
}
public var secretsOut: String {
return secret.value
}
}
public struct Relationships: JSONAPI.Relationships {
@@ -10,7 +10,8 @@ let computed_property_attribute = """
"id": "1234",
"type": "test",
"attributes": {
"name": "Sarah"
"name": "Sarah",
"secret": "shhhh"
},
"relationships": {
"other": {
+77 -12
View File
@@ -24,15 +24,23 @@ class EntityTests: XCTestCase {
XCTAssertEqual(entity2 ~> \.other, entity1.id)
}
func test_optional_relationship_operator_access() {
}
func test_toMany_relationship_operator_access() {
let entity1 = TestEntity1()
let entity2 = TestEntity1()
let entity4 = TestEntity1()
let entity3 = TestEntity3(relationships: .init(others: .init(relationships: [entity1.pointer, entity2.pointer, entity4.pointer])))
let entity3 = TestEntity3(relationships: .init(others: .init(pointers: [entity1.pointer, entity2.pointer, entity4.pointer])))
XCTAssertEqual(entity3 ~> \.others, [entity1.id, entity2.id, entity4.id])
}
func test_optionalToMany_relationship_opeartor_access() {
}
func test_relationshipIds() {
let entity1 = TestEntity1()
@@ -63,9 +71,12 @@ class EntityTests: XCTestCase {
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)))
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")))
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil))))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil)))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none)))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: entity1.pointer, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: .init(entities: [], meta: .none, links: .none)))
let e10id1 = TestEntity10.Identifier(rawValue: "hello")
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
@@ -196,6 +207,8 @@ extension EntityTests {
XCTAssertNil(entity[\.maybeHere])
XCTAssertNil(entity[\.maybeNull])
XCTAssertNoThrow(try TestEntity6.check(entity))
print(encodable: entity)
}
func test_entityOneNullAndOneOmittedAttribute_encode() {
@@ -215,6 +228,8 @@ extension EntityTests {
XCTAssertEqual(entity[\.here], "Hello")
XCTAssertNil(entity[\.maybeHereMaybeNull])
XCTAssertNoThrow(try TestEntity7.check(entity))
print(encodable: entity)
}
func test_NullOptionalNullableAttribute_encode() {
@@ -275,18 +290,50 @@ extension EntityTests {
// MARK: Relationship omission and nullification
extension EntityTests {
func test_nullableRelationshipNotNull() {
func test_nullableRelationshipNotNullOrOmitted() {
let entity = decoded(type: TestEntity9.self,
data: entity_omitted_relationship)
data: entity_optional_not_omitted_relationship)
XCTAssertEqual((entity ~> \.nullableOne)?.rawValue, "3323")
XCTAssertEqual((entity ~> \.one).rawValue, "4459")
XCTAssertNil(entity ~> \.optionalOne)
XCTAssertEqual((entity ~> \.optionalNullableOne)?.rawValue, "1229")
XCTAssertNoThrow(try TestEntity9.check(entity))
}
func test_nullableRelationshipNotNullOrOmitted_encode() {
test_DecodeEncodeEquality(type: TestEntity9.self,
data: entity_optional_not_omitted_relationship)
}
func test_nullableRelationshipNotNull() {
let entity = decoded(type: TestEntity9.self,
data: entity_omitted_relationship)
XCTAssertEqual((entity ~> \.nullableOne)?.rawValue, "3323")
XCTAssertEqual((entity ~> \.one).rawValue, "4459")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
}
func test_nullableRelationshipNotNull_encode() {
test_DecodeEncodeEquality(type: TestEntity9.self,
data: entity_omitted_relationship)
data: entity_omitted_relationship)
}
func test_optionalNullableRelationshipNulled() {
let entity = decoded(type: TestEntity9.self,
data: entity_optional_nullable_nulled_relationship)
XCTAssertEqual((entity ~> \.nullableOne)?.rawValue, "3323")
XCTAssertEqual((entity ~> \.one).rawValue, "4459")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
}
func test_optionalNullableRelationshipNulled_encode() {
test_DecodeEncodeEquality(type: TestEntity9.self,
data: entity_optional_nullable_nulled_relationship)
}
func test_nullableRelationshipIsNull() {
@@ -295,6 +342,7 @@ extension EntityTests {
XCTAssertNil(entity ~> \.nullableOne)
XCTAssertEqual((entity ~> \.one).rawValue, "4452")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
}
@@ -302,6 +350,22 @@ extension EntityTests {
test_DecodeEncodeEquality(type: TestEntity9.self,
data: entity_nulled_relationship)
}
func test_optionalToManyIsNotOmitted() {
let entity = decoded(type: TestEntity9.self,
data: entity_optional_to_many_relationship_not_omitted)
XCTAssertEqual((entity ~> \.nullableOne)?.rawValue, "3323")
XCTAssertEqual((entity ~> \.one).rawValue, "4459")
XCTAssertEqual((entity ~> \.optionalMany)?[0].rawValue, "332223")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
}
func test_optionalToManyIsNotOmitted_encode() {
test_DecodeEncodeEquality(type: TestEntity9.self,
data: entity_optional_to_many_relationship_not_omitted)
}
}
// MARK: Relationships of same type as root entity
@@ -581,13 +645,14 @@ extension EntityTests {
let nullableOne: ToOneRelationship<TestEntity1?, NoMetadata, NoLinks>
let optionalOne: ToOneRelationship<TestEntity1, NoMetadata, NoLinks>?
let optionalNullableOne: ToOneRelationship<TestEntity1?, NoMetadata, NoLinks>?
let optionalMany: ToManyRelationship<TestEntity1, NoMetadata, NoLinks>?
// a nullable many is not allowed. it should
// just be an empty array.
// omitted relationships are not allowed either,
// so ToOneRelationship<TestEntity1>? (with the
// question on the relationship, not the entity)
// is not a thing.
}
}
@@ -228,6 +228,57 @@ let entity_int_to_string_attribute = """
}
""".data(using: .utf8)!
let entity_optional_not_omitted_relationship = """
{
"id": "1",
"type": "ninth_test_entities",
"relationships": {
"nullableOne": {
"data": {
"id": "3323",
"type": "test_entities"
}
},
"one": {
"data": {
"id": "4459",
"type": "test_entities"
}
},
"optionalNullableOne": {
"data": {
"id": "1229",
"type": "test_entities"
}
}
}
}
""".data(using: .utf8)!
let entity_optional_nullable_nulled_relationship = """
{
"id": "1",
"type": "ninth_test_entities",
"relationships": {
"nullableOne": {
"data": {
"id": "3323",
"type": "test_entities"
}
},
"one": {
"data": {
"id": "4459",
"type": "test_entities"
}
},
"optionalNullableOne": {
"data": null
}
}
}
""".data(using: .utf8)!
let entity_omitted_relationship = """
{
"id": "1",
@@ -249,6 +300,35 @@ let entity_omitted_relationship = """
}
""".data(using: .utf8)!
let entity_optional_to_many_relationship_not_omitted = """
{
"id": "1",
"type": "ninth_test_entities",
"relationships": {
"nullableOne": {
"data": {
"id": "3323",
"type": "test_entities"
}
},
"one": {
"data": {
"id": "4459",
"type": "test_entities"
}
},
"optionalMany": {
"data": [
{
"id": "332223",
"type": "test_entities"
}
]
}
}
}
""".data(using: .utf8)!
let entity_nulled_relationship = """
{
"id": "1",
@@ -121,6 +121,24 @@ class IncludedTests: XCTestCase {
test_DecodeEncodeEquality(type: Includes<Include6<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6>>.self,
data: six_different_type_includes)
}
func test_SevenDifferentIncludes() {
let includes = decoded(type: Includes<Include7<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7>>.self,
data: seven_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)
}
func test_SevenDifferentIncludes_encode() {
test_DecodeEncodeEquality(type: Includes<Include7<TestEntity, TestEntity2, TestEntity3, TestEntity4, TestEntity5, TestEntity6, TestEntity7>>.self,
data: seven_different_type_includes)
}
}
// MARK: - Test types
@@ -203,4 +221,15 @@ extension IncludedTests {
}
typealias TestEntity6 = BasicEntity<TestEntityType6>
enum TestEntityType7: EntityDescription {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity7" }
typealias Relationships = NoRelationships
}
typealias TestEntity7 = BasicEntity<TestEntityType7>
}
@@ -281,3 +281,76 @@ let six_different_type_includes = """
}
]
""".data(using: .utf8)!
let seven_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"
}
]
""".data(using: .utf8)!
@@ -0,0 +1,101 @@
//
// NonJSONAPIRelatableTests.swift
// JSONAPITests
//
// Created by Mathew Polzin on 12/7/18.
//
import XCTest
import JSONAPI
class NonJSONAPIRelatableTests: XCTestCase {
func test_initialization1() {
let e1 = NonJSONAPIEntity(id: .init(rawValue: "hello"))
let e2 = NonJSONAPIEntity(id: .init(rawValue: "world"))
let entity = TestEntity(relationships: .init(one: .init(id: e1.id), many: .init(ids: [e1.id, e2.id])))
XCTAssertEqual(entity ~> \.one, e1.id)
XCTAssertEqual(entity ~> \.many, [e1.id, e2.id])
XCTAssertNoThrow(try TestEntity.check(entity))
}
func test_initialization2_all_relationships_there() {
let e1 = NonJSONAPIEntity(id: .init(rawValue: "hello"))
let e2 = NonJSONAPIEntity(id: .init(rawValue: "world"))
let entity = TestEntity2(relationships: .init(nullableOne: .init(id: e1.id), nullableMaybeOne: .init(id: e2.id), maybeOne: .init(id: e2.id), maybeMany: .init(ids: [e2.id, e1.id])))
XCTAssertEqual((entity ~> \.nullableOne)?.rawValue, "hello")
XCTAssertEqual((entity ~> \.nullableMaybeOne)?.rawValue, "world")
XCTAssertEqual((entity ~> \.maybeOne)?.rawValue, "world")
XCTAssertEqual((entity ~> \.maybeMany)?.map { $0.rawValue }, ["world", "hello"])
}
func test_initialization2_all_relationships_missing() {
let entity = TestEntity2(relationships: .init(nullableOne: .init(id: nil), nullableMaybeOne: .init(id: nil), maybeOne: nil, maybeMany: nil))
let entity2 = TestEntity2(relationships: .init(nullableOne: .init(id: nil), nullableMaybeOne: nil, maybeOne: nil, maybeMany: nil))
XCTAssertNil((entity ~> \.nullableOne))
XCTAssertNil((entity ~> \.nullableMaybeOne))
XCTAssertNil((entity ~> \.maybeOne))
XCTAssertNil((entity ~> \.maybeMany))
XCTAssertNil((entity2 ~> \.nullableOne))
XCTAssertNil((entity2 ~> \.nullableMaybeOne))
XCTAssertNil((entity2 ~> \.maybeOne))
XCTAssertNil((entity2 ~> \.maybeMany))
}
}
// MARK: - Test Types
extension NonJSONAPIRelatableTests {
enum TestEntityDescription: EntityDescription {
static var type: String { return "test" }
typealias Attributes = NoAttributes
struct Relationships: JSONAPI.Relationships {
let one: ToOneRelationship<NonJSONAPIEntity, NoMetadata, NoLinks>
let many: ToManyRelationship<NonJSONAPIEntity, NoMetadata, NoLinks>
}
}
typealias TestEntity = JSONAPI.Entity<TestEntityDescription, NoMetadata, NoLinks, String>
enum TestEntity2Description: EntityDescription {
static var type: String { return "test" }
typealias Attributes = NoAttributes
struct Relationships: JSONAPI.Relationships {
let nullableOne: ToOneRelationship<NonJSONAPIEntity?, NoMetadata, NoLinks>
let nullableMaybeOne: ToOneRelationship<NonJSONAPIEntity?, NoMetadata, NoLinks>?
let maybeOne: ToOneRelationship<NonJSONAPIEntity, NoMetadata, NoLinks>?
let maybeMany: ToManyRelationship<NonJSONAPIEntity, NoMetadata, NoLinks>?
}
}
typealias TestEntity2 = JSONAPI.Entity<TestEntity2Description, NoMetadata, NoLinks, String>
struct NonJSONAPIEntity: Relatable, JSONTyped {
static var type: String { return "other" }
typealias Identifier = NonJSONAPIEntity.Id
let id: Id
struct Id: IdType {
var rawValue: String
typealias IdentifiableType = NonJSONAPIEntity
typealias RawType = String
static func id(from rawValue: String) -> Id {
return Id(rawValue: rawValue)
}
}
}
}

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