Compare commits

...

14 Commits

Author SHA1 Message Date
Mathew Polzin dfdc266645 remove a bunch of convenience initializers that appeared to be giving the compiler some strife 2018-12-10 22:45:28 -08:00
Mathew Polzin 585cad0a83 Added literal initialization for nullable Attributes (TestLib) 2018-12-10 21:50:30 -08:00
Mathew Polzin 89abdd4cca fix example code in Playground 2018-12-10 21:11:48 -08:00
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
34 changed files with 1150 additions and 173 deletions
@@ -13,7 +13,7 @@ Please enjoy these examples, but allow me the forced casting and the lack of err
// MARK: - Literal Expressibility
// The JSONAPITestLib provides literal expressibility for key types to
// make creating tests easier
let dog = Dog(id: "1234", attributes: Dog.Attributes(name: "Buddy"), relationships: Dog.Relationships(owner: nil))
let dog = Dog(id: "1234", attributes: Dog.Attributes(name: "Buddy"), relationships: Dog.Relationships(owner: nil), meta: .none, links: .none)
// MARK: - JSON API structure checking
// The JSONAPITestLib provides a `check` function for each Entity type
@@ -13,7 +13,7 @@ let dogFromCode = try! Dog(name: "Buddy", owner: nil)
typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
let singleDogDocument = SingleDogDocument(body: SingleResourceBody(entity: dogFromCode))
let singleDogDocument = SingleDogDocument(apiDescription: .none, body: .init(entity: dogFromCode), includes: .none, meta: .none, links: .none)
let singleDogData = try! JSONEncoder().encode(singleDogDocument)
@@ -24,13 +24,13 @@ let dogFromData = dogResponse.body.primaryData?.value
// MARK: - Create a request or response with multiple people and dogs and houses included
let personIds = [Person.Identifier(), Person.Identifier()]
let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner: personIds[0]), Dog(name: "Travis", owner: personIds[1])]
let houses = [House(), House()]
let houses = [House(attributes: .none, relationships: .none, meta: .none, links: .none), House(attributes: .none, relationships: .none, meta: .none, links: .none)]
let people = try! [Person(id: personIds[0], name: ["Gary", "Doe"], favoriteColor: "Orange-Red", friends: [], dogs: [dogs[0], dogs[1]], home: houses[0]), Person(id: personIds[1], name: ["Elise", "Joy"], favoriteColor: "Red", friends: [], dogs: [dogs[2]], home: houses[1])]
typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoAPIDescription, UnknownJSONAPIError>
let includes = dogs.map { BatchPeopleDocument.Include($0) } + houses.map { BatchPeopleDocument.Include($0) }
let batchPeopleDocument = BatchPeopleDocument(body: .init(entities: people), includes: .init(values: includes))
let batchPeopleDocument = BatchPeopleDocument(apiDescription: .none, body: .init(entities: people), includes: .init(values: includes), meta: .none, links: .none)
let batchPeopleData = try! JSONEncoder().encode(batchPeopleDocument)
// MARK: - Parse a request or response body with multiple people in it and dogs and houses included
+4 -4
View File
@@ -25,7 +25,7 @@ extension String: CreatableRawIdType {
// MARK: - typealiases for convenience
public typealias ExampleEntity<Description: EntityDescription> = Entity<Description, NoMetadata, NoLinks, String>
public typealias ToOne<E: OptionalRelatable> = ToOneRelationship<E, NoMetadata, NoLinks>
public typealias ToOne<E: Identifiable> = ToOneRelationship<E, NoMetadata, NoLinks>
public typealias ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLinks>
// MARK: - A few resource objects (entities)
@@ -64,7 +64,7 @@ public typealias Person = ExampleEntity<PersonDescription>
public extension Entity where Description == PersonDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
public init(id: Person.Id? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws {
self = try Person(id: id ?? Person.Id(), attributes: .init(name: .init(rawValue: name), favoriteColor: .init(rawValue: favoriteColor)), relationships: .init(friends: .init(entities: friends), dogs: .init(entities: dogs), home: .init(entity: home)))
self = try Person(id: id ?? Person.Id(), attributes: .init(name: .init(rawValue: name), favoriteColor: .init(rawValue: favoriteColor)), relationships: .init(friends: .init(entities: friends), dogs: .init(entities: dogs), home: .init(entity: home)), meta: .none, links: .none)
}
}
@@ -93,11 +93,11 @@ public typealias Dog = ExampleEntity<DogDescription>
public extension Entity where Description == DogDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
public init(name: String, owner: Person?) throws {
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: DogDescription.Relationships(owner: .init(entity: owner)))
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: DogDescription.Relationships(owner: .init(entity: owner)), meta: .none, links: .none)
}
public init(name: String, owner: Person.Id) throws {
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: .init(owner: .init(id: owner)))
self = try Dog(attributes: .init(name: .init(rawValue: name)), relationships: .init(owner: .init(id: owner)), meta: .none, links: .none)
}
}
+2 -1
View File
@@ -110,7 +110,7 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
self.apiDescription = apiDescription
}
}
/*
extension Document where IncludeType == NoIncludes {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
self.init(apiDescription: apiDescription, body: body, includes: .none, meta: meta, links: links)
@@ -182,6 +182,7 @@ extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, Link
self.init(apiDescription: .none, body: body)
}
}
*/
extension Document {
private enum RootCodingKeys: String, CodingKey {
+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)
}
}
+74 -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 {
@@ -140,6 +143,7 @@ extension Entity where EntityRawIdType == Unidentified {
}
}
/*
extension Entity where Description.Attributes == NoAttributes {
public init(id: Entity.Id, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.init(id: id, attributes: NoAttributes(), relationships: relationships, meta: meta, links: links)
@@ -326,6 +330,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 +348,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 +366,25 @@ 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 +412,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 +432,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() {
+155
View File
@@ -644,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)
}
}
}
@@ -39,6 +39,14 @@ extension TransformedAttribute: ExpressibleByFloatLiteral where RawValue: Expres
}
}
extension Optional: ExpressibleByFloatLiteral where Wrapped: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Wrapped.FloatLiteralType
public init(floatLiteral value: FloatLiteralType) {
self = .some(Wrapped(floatLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByBooleanLiteral where RawValue: ExpressibleByBooleanLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias BooleanLiteralType = RawValue.BooleanLiteralType
@@ -47,6 +55,14 @@ extension TransformedAttribute: ExpressibleByBooleanLiteral where RawValue: Expr
}
}
extension Optional: ExpressibleByBooleanLiteral where Wrapped: ExpressibleByBooleanLiteral {
public typealias BooleanLiteralType = Wrapped.BooleanLiteralType
public init(booleanLiteral value: BooleanLiteralType) {
self = .some(Wrapped(booleanLiteral: value))
}
}
extension TransformedAttribute: ExpressibleByIntegerLiteral where RawValue: ExpressibleByIntegerLiteral, Transformer == IdentityTransformer<RawValue> {
public typealias IntegerLiteralType = RawValue.IntegerLiteralType
@@ -55,6 +71,14 @@ extension TransformedAttribute: ExpressibleByIntegerLiteral where RawValue: Expr
}
}
extension Optional: ExpressibleByIntegerLiteral where Wrapped: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Wrapped.IntegerLiteralType
public init(integerLiteral value: IntegerLiteralType) {
self = .some(Wrapped(integerLiteral: value))
}
}
// regretably, array and dictionary literals are not so easy because Dictionaries and Arrays
// cannot be turned back into variadic arguments to pass onto the RawValue type's constructor.
@@ -79,6 +103,16 @@ extension TransformedAttribute: ExpressibleByDictionaryLiteral where RawValue: D
}
}
extension Optional: DictionaryType where Wrapped: DictionaryType {
public typealias Key = Wrapped.Key
public typealias Value = Wrapped.Value
public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Dictionary<Key, Value>.Value, Dictionary<Key, Value>.Value) throws -> Dictionary<Key, Value>.Value) rethrows where S : Sequence, S.Element == (Key, Value) {
self = try .some(Wrapped(keysAndValues, uniquingKeysWith: combine))
}
}
public protocol ArrayType {
associatedtype Element
@@ -94,3 +128,11 @@ extension TransformedAttribute: ExpressibleByArrayLiteral where RawValue: ArrayT
self.init(value: RawValue(elements))
}
}
extension Optional: ArrayType where Wrapped: ArrayType {
public typealias Element = Wrapped.Element
public init<S>(_ s: S) where Element == S.Element, S : Sequence {
self = .some(Wrapped(s))
}
}
+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))
}
}
@@ -11,7 +11,7 @@ import JSONAPITestLib
class Attribute_FunctorTests: XCTestCase {
func test_mapGuaranteed() {
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)))
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)), relationships: .none, meta: .none, links: .none)
XCTAssertNotNil(entity)
@@ -19,7 +19,7 @@ class Attribute_FunctorTests: XCTestCase {
}
func test_mapOptionalSuccess() {
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)))
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.0)), relationships: .none, meta: .none, links: .none)
XCTAssertNotNil(entity)
@@ -27,7 +27,7 @@ class Attribute_FunctorTests: XCTestCase {
}
func test_mapOptionalFailure() {
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.5)))
let entity = try? TestType(attributes: .init(name: "Frankie", number: .init(rawValue: 22.5)), relationships: .none, meta: .none, links: .none)
XCTAssertNotNil(entity)
@@ -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": {
@@ -847,7 +847,7 @@ extension DocumentTests {
// MARK: Poly PrimaryResource Tests
extension DocumentTests {
func test_singleDocument_PolyPrimaryResource() {
let article = Article(id: Id(rawValue: "1"), relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))))
let article = Article(id: Id(rawValue: "1"), attributes: .none, relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))), meta: .none, links: .none)
let document = decoded(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>.self, data: single_document_no_includes)
XCTAssertEqual(document.body.primaryData?.value[Article.self], article)
@@ -859,7 +859,7 @@ extension DocumentTests {
}
func test_singleDocument_PolyPrimaryResourceWithAPIDescription() {
let article = Article(id: Id(rawValue: "1"), relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))))
let article = Article(id: Id(rawValue: "1"), attributes: .none, relationships: .init(author: ToOneRelationship(id: Id(rawValue: "33"))), meta: .none, links: .none)
let document = decoded(type: Document<SingleResourceBody<Poly2<Article, Author>>, NoMetadata, NoLinks, NoIncludes, TestAPIDescription, UnknownJSONAPIError>.self, data: single_document_no_includes_with_api_description)
XCTAssertEqual(document.body.primaryData?.value[Article.self], article)
+102 -37
View File
@@ -12,31 +12,39 @@ import JSONAPITestLib
class EntityTests: XCTestCase {
func test_relationship_access() {
let entity1 = TestEntity1()
let entity2 = TestEntity2(relationships: .init(other: entity1.pointer))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(other: entity1.pointer), meta: .none, links: .none)
XCTAssertEqual(entity2.relationships.other, entity1.pointer)
}
func test_relationship_operator_access() {
let entity1 = TestEntity1()
let entity2 = TestEntity2(relationships: .init(other: entity1.pointer))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(other: entity1.pointer), meta: .none, links: .none)
XCTAssertEqual(entity2 ~> \.other, entity1.id)
}
func test_optional_relationship_operator_access() {
}
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 entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity4 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity3 = TestEntity3(attributes: .none, relationships: .init(others: .init(pointers: [entity1.pointer, entity2.pointer, entity4.pointer])), meta: .none, links: .none)
XCTAssertEqual(entity3 ~> \.others, [entity1.id, entity2.id, entity4.id])
}
func test_optionalToMany_relationship_opeartor_access() {
}
func test_relationshipIds() {
let entity1 = TestEntity1()
let entity2 = TestEntity2(relationships: .init(other: entity1.pointer))
let entity1 = TestEntity1(attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(attributes: .none, relationships: .init(other: entity1.pointer), meta: .none, links: .none)
XCTAssertEqual(entity2.relationships.other.id, entity1.id)
}
@@ -52,28 +60,31 @@ class EntityTests: XCTestCase {
}
func test_initialization() {
let entity1 = TestEntity1(id: .init(rawValue: "wow"))
let entity2 = TestEntity2(id: .init(rawValue: "cool"), relationships: .init(other: .init(entity: entity1)))
let _ = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)))
let _ = TestEntity2(id: .init(rawValue: "cool"), relationships: .init(other: .init(entity: entity1)), meta: .none)
let _ = TestEntity3(id: .init(rawValue: "3"), relationships: .init(others: .init(ids: [.init(rawValue: "10"), .init(rawValue: "20"), entity1.id])))
let _ = TestEntity3(id: .init(rawValue: "3"), relationships: .init(others: .none))
let _ = TestEntity4(id: .init(rawValue: "4"), attributes: .init(word: .init(value: "hello"), number: .init(value: 10), array: .init(value: [10.2, 10.3])), relationships: .init(other: entity2.pointer))
let _ = TestEntity5(id: .init(rawValue: "5"), attributes: .init(floater: .init(value: 10.2)))
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)))
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")))
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil))))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil))
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 entity1 = TestEntity1(id: .init(rawValue: "wow"), attributes: .none, relationships: .none, meta: .none, links: .none)
let entity2 = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)), meta: .none, links: .none)
let _ = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)), meta: .none, links: .none)
let _ = TestEntity2(id: .init(rawValue: "cool"), attributes: .none, relationships: .init(other: .init(entity: entity1)), meta: .none, links: .none)
let _ = TestEntity3(id: .init(rawValue: "3"), attributes: .none, relationships: .init(others: .init(ids: [.init(rawValue: "10"), .init(rawValue: "20"), entity1.id])), meta: .none, links: .none)
let _ = TestEntity3(id: .init(rawValue: "3"), attributes: .none, relationships: .init(others: .none), meta: .none, links: .none)
let _ = TestEntity4(id: .init(rawValue: "4"), attributes: .init(word: .init(value: "hello"), number: .init(value: 10), array: .init(value: [10.2, 10.3])), relationships: .init(other: entity2.pointer), meta: .none, links: .none)
let _ = TestEntity5(id: .init(rawValue: "5"), attributes: .init(floater: .init(value: 10.2)), relationships: .none, meta: .none, links: .none)
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)), relationships: .none, meta: .none, links: .none)
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")), relationships: .none, meta: .none, links: .none)
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil)), relationships: .none, meta: .none, links: .none))
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: entity1.pointer, optionalNullableOne: nil, optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: .init(entities: [], meta: .none, links: .none)), meta: .none, links: .none)
let e10id1 = TestEntity10.Identifier(rawValue: "hello")
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
let _ = TestEntity10(id: .init(rawValue: "10"), relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])))
XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11))))
let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")))
let _ = UnidentifiedTestEntityWithMeta(attributes: .init(me: .init(value: "hello")), meta: .init(x: "world", y: nil))
let _ = UnidentifiedTestEntityWithLinks(attributes: .init(me: .init(value: "hello")), links: .init(link1: .init(url: "hmmm")))
let _ = TestEntity10(id: .init(rawValue: "10"), attributes: .none, relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])), meta: .none, links: .none)
XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11)), relationships: .none, meta: .none, links: .none))
let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .none)
let _ = UnidentifiedTestEntityWithMeta(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .init(x: "world", y: nil), links: .none)
let _ = UnidentifiedTestEntityWithLinks(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .init(link1: .init(url: "hmmm")))
}
}
@@ -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.
}
}

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