Compare commits

..

7 Commits

22 changed files with 495 additions and 85 deletions
+12 -10
View File
@@ -23,8 +23,10 @@ extension String: CreatableRawIdType {
}
}
// MARK: - Entity typealias for convenience
public typealias ExampleEntity<Description: EntityDescription> = Entity<Description, String>
// 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 ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLinks>
// MARK: - A few resource objects (entities)
public enum PersonDescription: EntityDescription {
@@ -46,11 +48,11 @@ public enum PersonDescription: EntityDescription {
}
public struct Relationships: JSONAPI.Relationships {
public let friends: ToManyRelationship<Person>
public let dogs: ToManyRelationship<Dog>
public let home: ToOneRelationship<House>
public let friends: ToMany<Person>
public let dogs: ToMany<Dog>
public let home: ToOne<House>
public init(friends: ToManyRelationship<Person>, dogs: ToManyRelationship<Dog>, home: ToOneRelationship<House>) {
public init(friends: ToMany<Person>, dogs: ToMany<Dog>, home: ToOne<House>) {
self.friends = friends
self.dogs = dogs
self.home = home
@@ -60,7 +62,7 @@ public enum PersonDescription: EntityDescription {
public typealias Person = ExampleEntity<PersonDescription>
public extension Entity where Description == PersonDescription, EntityRawIdType == String {
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)))
}
@@ -79,9 +81,9 @@ public enum DogDescription: EntityDescription {
}
public struct Relationships: JSONAPI.Relationships {
public let owner: ToOneRelationship<Person?>
public let owner: ToOne<Person?>
public init(owner: ToOneRelationship<Person?>) {
public init(owner: ToOne<Person?>) {
self.owner = owner
}
}
@@ -89,7 +91,7 @@ public enum DogDescription: EntityDescription {
public typealias Dog = ExampleEntity<DogDescription>
public extension Entity where Description == DogDescription, EntityRawIdType == String {
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)))
}
+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"]),
+6 -4
View File
@@ -52,8 +52,8 @@ Note that Playground support for importing non-system Frameworks is still a bit
#### Relationship Object
- [x] `data`
- [ ] `links`
- [ ] `meta`
- [x] `links`
- [x] `meta`
#### Links Object
- [x] `href`
@@ -186,9 +186,11 @@ Note that I am assuming an unidentified person is a "new" person. I suspect that
There are two types of `Relationships`: `ToOneRelationship` and `ToManyRelationship`. An `EntityDescription`'s `Relationships` type can contain any number of `Relationship` properties of either of these types. Do not store anything other than `Relationship` properties in the `Relationships` struct of an `EntityDescription`.
To describe a relationship that may be omitted (i.e. the key is not even present in the JSON object), you make the entire `ToOneRelationship` or `ToManyRelationship` optional. However, this is not recommended because you can also represent optional relationships as nullable which means the key is always present. A `ToManyRelationship` can naturally represent the absence of related values with an empty array, so `ToManyRelationship` does not support nullability at all. A `ToOneRelationship` can be marked as nullable (i.e. the value might be `null` or it might be a resource identifier) like this:
In addition to identifying entities by Id and type, `Relationships` can contain `Meta` or `Links` that follow the same rules as [`Meta`](#jsonapimeta) and [`Links`](#jsonapilinks) elsewhere in the JSON API Document.
To describe a relationship that may be omitted (i.e. the key is not even present in the JSON object), you make the entire `ToOneRelationship` or `ToManyRelationship` optional. However, this is not recommended because you can also represent optional relationships as nullable which means the key is always present. A `ToManyRelationship` can naturally represent the absence of related values with an empty array, so `ToManyRelationship` does not support nullability at all. A `ToOneRelationship` can be marked as nullable (i.e. the value could be either `null` or a resource identifier) like this:
```
let nullableRelative: ToOneRelationship<Person?>
let nullableRelative: ToOneRelationship<Person?, NoMetadata, NoLinks>
```
An entity that does not have relationships can be described by adding the following to an `EntityDescription`:
+26 -4
View File
@@ -320,24 +320,46 @@ extension Entity where MetaType == NoMetadata {
}
}
extension Entity where MetaType == NoMetadata, EntityRawIdType: CreatableRawIdType {
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)
}
}
extension Entity where LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
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)
}
}
extension Entity where MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType: CreatableRawIdType {
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 {
/// Get a pointer to this entity that can be used as a
/// relationship to another entity.
public var pointer: ToOneRelationship<Entity> {
return ToOneRelationship(entity: self)
public var pointer: ToOneRelationship<Entity, NoMetadata, NoLinks> {
return ToOneRelationship(entity: self, meta: .none, links: .none)
}
public func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<Entity, MType, LType> {
return ToOneRelationship(entity: self, meta: meta, links: links)
}
}
@@ -370,14 +392,14 @@ 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>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity>>) -> OtherEntity.WrappedIdentifier {
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedIdentifier {
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>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity>>) -> [OtherEntity.Identifier] {
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
}
}
+1 -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
+111 -15
View File
@@ -5,30 +5,59 @@
// Created by Mathew Polzin on 8/31/18.
//
public protocol RelationshipType: Codable {}
public protocol RelationshipType: Codable {
associatedtype LinksType
associatedtype MetaType
var links: LinksType { get }
var meta: MetaType { get }
}
/// An Entity relationship that can be encoded to or decoded from
/// 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>: RelationshipType, Equatable {
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
public let id: Relatable.WrappedIdentifier
public init(id: Relatable.WrappedIdentifier) {
public let meta: MetaType
public let links: LinksType
public init(id: Relatable.WrappedIdentifier, meta: MetaType, links: LinksType) {
self.id = id
self.meta = meta
self.links = links
}
}
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init(id: Relatable.WrappedIdentifier) {
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 {
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 {
id = entity.id
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 {
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 {
id = entity?.id
self.init(id: entity?.id, meta: .none, links: .none)
}
}
@@ -36,30 +65,54 @@ extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Ident
/// a JSON API "Resource Linkage."
/// See https://jsonapi.org/format/#document-resource-object-linkage
/// A convenient typealias might make your code much more legible: `Many<EntityDescription>`
public struct ToManyRelationship<Relatable: JSONAPI.Relatable>: RelationshipType, Equatable {
public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
public let ids: [Relatable.Identifier]
public init(ids: [Relatable.Identifier]) {
public let meta: MetaType
public let links: LinksType
public init(ids: [Relatable.Identifier], meta: MetaType, links: LinksType) {
self.ids = ids
self.meta = meta
self.links = links
}
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T>]) where T.WrappedIdentifier == Relatable.Identifier {
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedIdentifier == Relatable.Identifier {
ids = relationships.map { $0.id }
self.meta = meta
self.links = links
}
private init() {
ids = []
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Description == Relatable.Description, 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 var none: ToManyRelationship {
return .init()
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
return ToManyRelationship(meta: meta, links: links)
}
}
extension ToManyRelationship {
extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init(ids: [Relatable.Identifier]) {
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 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 {
ids = entities.map { $0.id }
self.init(entities: entities, meta: .none, links: .none)
}
}
@@ -85,6 +138,8 @@ extension Optional: OptionalRelatable where Wrapped: Relatable {
// MARK: Codable
private enum ResourceLinkageCodingKeys: String, CodingKey {
case data = "data"
case meta = "meta"
case links = "links"
}
private enum ResourceIdentifierCodingKeys: String, CodingKey {
case id = "id"
@@ -103,6 +158,18 @@ extension ToOneRelationship {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
if let noMeta = NoMetadata() as? MetaType {
meta = noMeta
} else {
meta = try container.decode(MetaType.self, forKey: .meta)
}
if let noLinks = NoLinks() as? LinksType {
links = noLinks
} else {
links = try container.decode(LinksType.self, forKey: .links)
}
// A little trickery follows. If the id is nil, the
// container.decode(Identifier.self) will fail even if Identifier
// is Optional. However, we can check if decoding nil
@@ -133,6 +200,14 @@ extension ToOneRelationship {
try container.encodeNil(forKey: .data)
}
if MetaType.self != NoMetadata.self {
try container.encode(meta, forKey: .meta)
}
if LinksType.self != NoLinks.self {
try container.encode(links, forKey: .links)
}
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
try identifier.encode(id, forKey: .id)
@@ -143,7 +218,19 @@ extension ToOneRelationship {
extension ToManyRelationship {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
if let noMeta = NoMetadata() as? MetaType {
meta = noMeta
} else {
meta = try container.decode(MetaType.self, forKey: .meta)
}
if let noLinks = NoLinks() as? LinksType {
links = noLinks
} else {
links = try container.decode(LinksType.self, forKey: .links)
}
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
var newIds = [Relatable.Identifier]()
@@ -163,6 +250,15 @@ extension ToManyRelationship {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
if MetaType.self != NoMetadata.self {
try container.encode(meta, forKey: .meta)
}
if LinksType.self != NoLinks.self {
try container.encode(links, forKey: .links)
}
var identifiers = container.nestedUnkeyedContainer(forKey: .data)
for id in ids {
+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)
}
}
}
+5 -1
View File
@@ -45,6 +45,10 @@ extension TransformedAttribute: AttributeTypeWithOptionalArray where RawValue: O
private protocol OptionalRelationshipType {}
extension Optional: OptionalRelationshipType where Wrapped: RelationshipType {}
private protocol _RelationshipType {}
extension ToOneRelationship: _RelationshipType {}
extension ToManyRelationship: _RelationshipType {}
public extension Entity {
public static func check(_ entity: Entity) throws {
var problems = [EntityCheckError]()
@@ -72,7 +76,7 @@ public extension Entity {
}
for relationship in relationshipsMirror.children {
if relationship.value as? RelationshipType == nil {
if relationship.value as? _RelationshipType == nil {
problems.append(.nonRelationship(named: relationship.label ?? "unnamed"))
}
}
@@ -7,14 +7,14 @@
import JSONAPI
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral {
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public init(nilLiteral: ()) {
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
}
}
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral {
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias UnicodeScalarLiteralType = Relatable.WrappedIdentifier.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
@@ -22,7 +22,7 @@ extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.W
}
}
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral {
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias ExtendedGraphemeClusterLiteralType = Relatable.WrappedIdentifier.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
@@ -30,7 +30,7 @@ extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where R
}
}
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral {
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias StringLiteralType = Relatable.WrappedIdentifier.StringLiteralType
public init(stringLiteral value: StringLiteralType) {
@@ -38,7 +38,7 @@ extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedI
}
}
extension ToManyRelationship: ExpressibleByArrayLiteral {
extension ToManyRelationship: ExpressibleByArrayLiteral where MetaType == NoMetadata, LinksType == NoLinks {
public typealias ArrayLiteralElement = Relatable.Identifier
public init(arrayLiteral elements: ArrayLiteralElement...) {
@@ -10,6 +10,10 @@ import JSONAPI
class APIDescriptionTests: XCTestCase {
func test_NoDescriptionString() {
XCTAssertEqual(String(describing: NoAPIDescription()), "No JSON:API Object")
}
func test_empty() {
let description = decoded(type: APIDescription<NoMetadata>.self, data: api_description_empty)
@@ -49,9 +49,9 @@ extension ComputedPropertiesTests {
}
public struct Relationships: JSONAPI.Relationships {
public let other: ToOneRelationship<TestType>
public let other: ToOneRelationship<TestType, NoMetadata, NoLinks>
public var computed: ToOneRelationship<TestType> {
public var computed: ToOneRelationship<TestType, NoMetadata, NoLinks> {
return other
}
}
@@ -978,7 +978,7 @@ extension DocumentTests {
typealias Attributes = NoAttributes
struct Relationships: JSONAPI.Relationships {
let author: ToOneRelationship<Author>
let author: ToOneRelationship<Author, NoMetadata, NoLinks>
}
}
+22 -7
View File
@@ -41,16 +41,31 @@ class EntityTests: XCTestCase {
XCTAssertEqual(entity2.relationships.other.id, entity1.id)
}
func test_pointerWithMetaAndLinks() {
let entity = TestEntity4WithMetaAndLinks(attributes: .init(word: "hello", number: 10, array: []), relationships: .init(other: .init(id: "2")), meta: .init(x: "world", y: nil), links: .init(link1: .init(url: "ok")))
let pointer = entity.pointer(withMeta: TestEntityMeta(x: "world", y: nil), links: TestEntityLinks(link1: .init(url: "ok")))
XCTAssertEqual(pointer.id, entity.id)
XCTAssertEqual(pointer.meta.x, "world")
XCTAssertEqual(pointer.links.link1.url, "ok")
}
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 e10id1 = TestEntity10.Identifier(rawValue: "hello")
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
@@ -459,7 +474,7 @@ extension EntityTests {
typealias Attributes = NoAttributes
struct Relationships: JSONAPI.Relationships {
let other: ToOneRelationship<TestEntity1>
let other: ToOneRelationship<TestEntity1, NoMetadata, NoLinks>
}
}
@@ -471,7 +486,7 @@ extension EntityTests {
typealias Attributes = NoAttributes
struct Relationships: JSONAPI.Relationships {
let others: ToManyRelationship<TestEntity1>
let others: ToManyRelationship<TestEntity1, NoMetadata, NoLinks>
}
}
@@ -481,7 +496,7 @@ extension EntityTests {
static var type: String { return "fourth_test_entities"}
struct Relationships: JSONAPI.Relationships {
let other: ToOneRelationship<TestEntity2>
let other: ToOneRelationship<TestEntity2, NoMetadata, NoLinks>
}
struct Attributes: JSONAPI.Attributes {
@@ -562,9 +577,9 @@ extension EntityTests {
typealias Attributes = NoAttributes
public struct Relationships: JSONAPI.Relationships {
let one: ToOneRelationship<TestEntity1>
let one: ToOneRelationship<TestEntity1, NoMetadata, NoLinks>
let nullableOne: ToOneRelationship<TestEntity1?>
let nullableOne: ToOneRelationship<TestEntity1?, NoMetadata, NoLinks>
// a nullable many is not allowed. it should
// just be an empty array.
@@ -584,8 +599,8 @@ extension EntityTests {
typealias Attributes = NoAttributes
public struct Relationships: JSONAPI.Relationships {
let selfRef: ToOneRelationship<TestEntity10>
let selfRefs: ToManyRelationship<TestEntity10>
let selfRef: ToOneRelationship<TestEntity10, NoMetadata, NoLinks>
let selfRefs: ToManyRelationship<TestEntity10, NoMetadata, NoLinks>
}
}
@@ -144,7 +144,7 @@ extension IncludedTests {
public static var type: String { return "test_entity2" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity>
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
}
public struct Attributes: JSONAPI.Attributes {
@@ -162,8 +162,8 @@ extension IncludedTests {
public static var type: String { return "test_entity3" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity>
let entity2: ToManyRelationship<TestEntity2>
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
let entity2: ToManyRelationship<TestEntity2, NoMetadata, NoLinks>
}
}
@@ -198,7 +198,7 @@ extension IncludedTests {
public static var type: String { return "test_entity6" }
struct Relationships: JSONAPI.Relationships {
let entity4: ToOneRelationship<TestEntity4>
let entity4: ToOneRelationship<TestEntity4, NoMetadata, NoLinks>
}
}
@@ -115,7 +115,7 @@ extension EntityCheckTests {
public typealias Attributes = NoAttributes
public struct Relationships: JSONAPI.Relationships {
let x: ToOneRelationship<OkEntity>
let x: ToOneRelationship<OkEntity, NoMetadata, NoLinks>
let y: Id<String, OkEntity>
}
}
@@ -12,16 +12,16 @@ import JSONAPITestLib
class Relationship_LiteralTests: XCTestCase {
func test_NilLiteral() {
XCTAssertEqual(ToOneRelationship<TestEntity?>(id: nil), nil)
XCTAssertEqual(ToOneRelationship<TestEntity?, NoMetadata, NoLinks>(id: nil), nil)
}
func test_ArrayLiteral() {
XCTAssertEqual(ToManyRelationship<TestEntity>(ids: ["1", "2", "3"]), ["1", "2", "3"])
XCTAssertEqual(ToManyRelationship<TestEntity, NoMetadata, NoLinks>(ids: ["1", "2", "3"]), ["1", "2", "3"])
}
func test_StringLiteral() {
XCTAssertEqual(ToOneRelationship<TestEntity>(id: "123"), "123")
XCTAssertEqual(ToOneRelationship<TestEntity?>(id: "123"), "123")
XCTAssertEqual(ToOneRelationship<TestEntity, NoMetadata, NoLinks>(id: "123"), "123")
XCTAssertEqual(ToOneRelationship<TestEntity?, NoMetadata, NoLinks>(id: "123"), "123")
}
}
+4 -4
View File
@@ -282,7 +282,7 @@ extension PolyTests {
public static var type: String { return "test_entity2" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity>
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
}
public struct Attributes: JSONAPI.Attributes {
@@ -300,8 +300,8 @@ extension PolyTests {
public static var type: String { return "test_entity3" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity>
let entity2: ToManyRelationship<TestEntity2>
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
let entity2: ToManyRelationship<TestEntity2, NoMetadata, NoLinks>
}
}
@@ -336,7 +336,7 @@ extension PolyTests {
public static var type: String { return "test_entity6" }
struct Relationships: JSONAPI.Relationships {
let entity4: ToOneRelationship<TestEntity4>
let entity4: ToOneRelationship<TestEntity4, NoMetadata, NoLinks>
}
}
@@ -15,7 +15,7 @@ class RelationshipTests: XCTestCase {
let entity2 = TestEntity1()
let entity3 = TestEntity1()
let entity4 = TestEntity1()
let relationship = ToManyRelationship<TestEntity1>(entities: [entity1, entity2, entity3, entity4])
let relationship = ToManyRelationship<TestEntity1, NoMetadata, NoLinks>(entities: [entity1, entity2, entity3, entity4])
XCTAssertEqual(relationship.ids.count, 4)
XCTAssertEqual(relationship.ids, [entity1, entity2, entity3, entity4].map { $0.id })
@@ -26,7 +26,7 @@ class RelationshipTests: XCTestCase {
let entity2 = TestEntity1()
let entity3 = TestEntity1()
let entity4 = TestEntity1()
let relationship = ToManyRelationship<TestEntity1>(relationships: [entity1.pointer, entity2.pointer, entity3.pointer, entity4.pointer])
let relationship = ToManyRelationship<TestEntity1, NoMetadata, NoLinks>(relationships: [entity1.pointer, entity2.pointer, entity3.pointer, entity4.pointer])
XCTAssertEqual(relationship.ids.count, 4)
XCTAssertEqual(relationship.ids, [entity1, entity2, entity3, entity4].map { $0.id })
@@ -36,38 +36,118 @@ class RelationshipTests: XCTestCase {
// MARK: - Encode/Decode
extension RelationshipTests {
func test_ToOneRelationship() {
let relationship = decoded(type: ToOneRelationship<TestEntity1>.self,
let relationship = decoded(type: ToOneRelationship<TestEntity1, NoMetadata, NoLinks>.self,
data: to_one_relationship)
XCTAssertEqual(relationship.id.rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
}
func test_ToOneRelationship_encode() {
test_DecodeEncodeEquality(type: ToOneRelationship<TestEntity1>.self,
test_DecodeEncodeEquality(type: ToOneRelationship<TestEntity1, NoMetadata, NoLinks>.self,
data: to_one_relationship)
}
func test_ToOneRelationshipWithMeta() {
let relationship = decoded(type: ToOneWithMeta.self,
data: to_one_relationship_with_meta)
XCTAssertEqual(relationship.id.rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
XCTAssertEqual(relationship.meta.a, "hello")
}
func test_ToOneRelationshipWithMeta_encode() {
test_DecodeEncodeEquality(type: ToOneWithMeta.self,
data: to_one_relationship_with_meta)
}
func test_ToOneRelationshipWithLinks() {
let relationship = decoded(type: ToOneWithLinks.self,
data: to_one_relationship_with_links)
XCTAssertEqual(relationship.id.rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
XCTAssertEqual(relationship.links.b, .init(url: "world"))
}
func test_ToOneRelationshipWithLinks_encode() {
test_DecodeEncodeEquality(type: ToOneWithLinks.self,
data: to_one_relationship_with_links)
}
func test_ToOneRelationshipWithMetaAndLinks() {
let relationship = decoded(type: ToOneWithMetaAndLinks.self,
data: to_one_relationship_with_meta_and_links)
XCTAssertEqual(relationship.id.rawValue, "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF")
XCTAssertEqual(relationship.meta.a, "hello")
XCTAssertEqual(relationship.links.b, .init(url: "world"))
}
func test_ToOneRelationshipWithMetaAndLinks_encode() {
test_DecodeEncodeEquality(type: ToOneWithMetaAndLinks.self,
data: to_one_relationship_with_meta_and_links)
}
func test_ToManyRelationship() {
let relationship = decoded(type: ToManyRelationship<TestEntity1>.self,
let relationship = decoded(type: ToManyRelationship<TestEntity1, NoMetadata, NoLinks>.self,
data: to_many_relationship)
XCTAssertEqual(relationship.ids.map { $0.rawValue }, ["2DF03B69-4B0A-467F-B52E-B0C9E44FCECF", "90F03B69-4DF1-467F-B52E-B0C9E44FC333", "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"])
}
func test_ToManyRelationship_encode() {
test_DecodeEncodeEquality(type: ToManyRelationship<TestEntity1>.self,
test_DecodeEncodeEquality(type: ToManyRelationship<TestEntity1, NoMetadata, NoLinks>.self,
data: to_many_relationship)
}
func test_ToManyRelationshipWithMeta() {
let relationship = decoded(type: ToManyWithMeta.self,
data: to_many_relationship_with_meta)
XCTAssertEqual(relationship.ids.map { $0.rawValue }, ["2DF03B69-4B0A-467F-B52E-B0C9E44FCECF", "90F03B69-4DF1-467F-B52E-B0C9E44FC333", "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"])
XCTAssertEqual(relationship.meta.a, "hello")
}
func test_ToManyRelationshipWithMeta_encode() {
test_DecodeEncodeEquality(type: ToManyWithMeta.self,
data: to_many_relationship_with_meta)
}
func test_ToManyRelationshipWithLinks() {
let relationship = decoded(type: ToManyWithLinks.self,
data: to_many_relationship_with_links)
XCTAssertEqual(relationship.ids.map { $0.rawValue }, ["2DF03B69-4B0A-467F-B52E-B0C9E44FCECF", "90F03B69-4DF1-467F-B52E-B0C9E44FC333", "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"])
XCTAssertEqual(relationship.links.b, .init(url: "world"))
}
func test_ToManyRelationshipWithLinks_encode() {
test_DecodeEncodeEquality(type: ToManyWithLinks.self,
data: to_many_relationship_with_links)
}
func test_ToManyRelationshipWithMetaAndLinks() {
let relationship = decoded(type: ToManyWithMetaAndLinks.self,
data: to_many_relationship_with_meta_and_links)
XCTAssertEqual(relationship.ids.map { $0.rawValue }, ["2DF03B69-4B0A-467F-B52E-B0C9E44FCECF", "90F03B69-4DF1-467F-B52E-B0C9E44FC333", "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"])
XCTAssertEqual(relationship.meta.a, "hello")
XCTAssertEqual(relationship.links.b, .init(url: "world"))
}
func test_ToManyRelationshipWithMetaAndLinks_encode() {
test_DecodeEncodeEquality(type: ToManyWithMetaAndLinks.self,
data: to_many_relationship_with_meta_and_links)
}
}
// MARK: Failure tests
extension RelationshipTests {
func test_ToManyTypeMismatch() {
XCTAssertThrowsError(try JSONDecoder().decode(ToManyRelationship<TestEntity1>.self, from: to_many_relationship_type_mismatch))
XCTAssertThrowsError(try JSONDecoder().decode(ToManyRelationship<TestEntity1, NoMetadata, NoLinks>.self, from: to_many_relationship_type_mismatch))
}
func test_ToOneTypeMismatch() {
XCTAssertThrowsError(try JSONDecoder().decode(ToOneRelationship<TestEntity1>.self, from: to_one_relationship_type_mismatch))
XCTAssertThrowsError(try JSONDecoder().decode(ToOneRelationship<TestEntity1, NoMetadata, NoLinks>.self, from: to_one_relationship_type_mismatch))
}
}
@@ -82,4 +162,23 @@ extension RelationshipTests {
}
typealias TestEntity1 = BasicEntity<TestEntityType1>
typealias ToOneWithMeta = ToOneRelationship<TestEntity1, TestMeta, NoLinks>
typealias ToOneWithLinks = ToOneRelationship<TestEntity1, NoMetadata, TestLinks>
typealias ToOneWithMetaAndLinks = ToOneRelationship<TestEntity1, TestMeta, TestLinks>
typealias ToManyWithMeta = ToManyRelationship<TestEntity1, TestMeta, NoLinks>
typealias ToManyWithLinks = ToManyRelationship<TestEntity1, NoMetadata, TestLinks>
typealias ToManyWithMetaAndLinks = ToManyRelationship<TestEntity1, TestMeta, TestLinks>
struct TestMeta: JSONAPI.Meta {
let a: String
}
typealias TestLink = JSONAPI.Link<String, NoMetadata>
struct TestLinks: JSONAPI.Links {
let b: TestLink
}
}
@@ -23,6 +23,45 @@ let to_one_relationship_type_mismatch = """
}
""".data(using: .utf8)!
let to_one_relationship_with_meta = """
{
"data": {
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
},
"meta": {
"a": "hello"
}
}
""".data(using: .utf8)!
let to_one_relationship_with_links = """
{
"data": {
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
},
"links": {
"b": "world"
}
}
""".data(using: .utf8)!
let to_one_relationship_with_meta_and_links = """
{
"data": {
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
},
"meta": {
"a": "hello"
},
"links": {
"b": "world"
}
}
""".data(using: .utf8)!
let to_many_relationship = """
{
"data": [
@@ -42,6 +81,75 @@ let to_many_relationship = """
}
""".data(using: .utf8)!
let to_many_relationship_with_meta = """
{
"data": [
{
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
},
{
"type": "test_entity1",
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
},
{
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
}
],
"meta": {
"a": "hello"
}
}
""".data(using: .utf8)!
let to_many_relationship_with_links = """
{
"data": [
{
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
},
{
"type": "test_entity1",
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
},
{
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
}
],
"links": {
"b": "world"
}
}
""".data(using: .utf8)!
let to_many_relationship_with_meta_and_links = """
{
"data": [
{
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
},
{
"type": "test_entity1",
"id": "90F03B69-4DF1-467F-B52E-B0C9E44FC333"
},
{
"type": "test_entity1",
"id": "2DF03B69-4B0A-467F-B52E-B0C9E44FCECF"
}
],
"meta": {
"a": "hello"
},
"links": {
"b": "world"
}
}
""".data(using: .utf8)!
let to_many_relationship_type_mismatch = """
{
"data": [

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