Added support for relationship operator ~> to optional relationships

This commit is contained in:
Mathew Polzin
2018-12-07 20:59:39 -08:00
parent 53f7f55e07
commit 41a2a01788
4 changed files with 179 additions and 12 deletions
+25
View File
@@ -420,12 +420,37 @@ public extension EntityProxy {
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.WrappedIdentifier where OtherEntity.WrappedIdentifier == 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? where OtherEntity.WrappedIdentifier == 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 ~>
+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"))
}
}
+72 -11
View File
@@ -24,6 +24,10 @@ class EntityTests: XCTestCase {
XCTAssertEqual(entity2 ~> \.other, entity1.id)
}
func test_optional_relationship_operator_access() {
}
func test_toMany_relationship_operator_access() {
let entity1 = TestEntity1()
@@ -33,6 +37,10 @@ class EntityTests: XCTestCase {
XCTAssertEqual(entity3 ~> \.others, [entity1.id, entity2.id, entity4.id])
}
func test_optionalToMany_relationship_opeartor_access() {
}
func test_relationshipIds() {
let entity1 = TestEntity1()
@@ -63,9 +71,12 @@ class EntityTests: XCTestCase {
let _ = TestEntity6(id: .init(rawValue: "6"), attributes: .init(here: .init(value: "here"), maybeHere: nil, maybeNull: .init(value: nil)))
let _ = TestEntity7(id: .init(rawValue: "7"), attributes: .init(here: .init(value: "hello"), maybeHereMaybeNull: .init(value: "world")))
XCTAssertNoThrow(try TestEntity8(id: .init(rawValue: "8"), attributes: .init(string: .init(value: "hello"), int: .init(value: 10), stringFromInt: .init(rawValue: 20), plus: .init(rawValue: 30), doubleFromInt: .init(rawValue: 32), omitted: nil, nullToString: .init(rawValue: nil))))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil)))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none)))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: nil), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: .init(entity: entity1, meta: .none, links: .none), optionalOne: nil, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: entity1.pointer, optionalNullableOne: nil, optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: nil))
let _ = TestEntity9(id: .init(rawValue: "9"), relationships: .init(one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(entity: entity1, meta: .none, links: .none), optionalMany: .init(entities: [], meta: .none, links: .none)))
let e10id1 = TestEntity10.Identifier(rawValue: "hello")
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
@@ -275,18 +286,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 +338,7 @@ extension EntityTests {
XCTAssertNil(entity ~> \.nullableOne)
XCTAssertEqual((entity ~> \.one).rawValue, "4452")
XCTAssertNil(entity ~> \.optionalNullableOne)
XCTAssertNoThrow(try TestEntity9.check(entity))
}
@@ -302,6 +346,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 +641,14 @@ extension EntityTests {
let nullableOne: ToOneRelationship<TestEntity1?, NoMetadata, NoLinks>
let optionalOne: ToOneRelationship<TestEntity1, NoMetadata, NoLinks>?
let optionalNullableOne: ToOneRelationship<TestEntity1?, NoMetadata, NoLinks>?
let optionalMany: ToManyRelationship<TestEntity1, NoMetadata, NoLinks>?
// a nullable many is not allowed. it should
// just be an empty array.
// omitted relationships are not allowed either,
// so ToOneRelationship<TestEntity1>? (with the
// question on the relationship, not the entity)
// is not a thing.
}
}
@@ -228,6 +228,57 @@ let entity_int_to_string_attribute = """
}
""".data(using: .utf8)!
let entity_optional_not_omitted_relationship = """
{
"id": "1",
"type": "ninth_test_entities",
"relationships": {
"nullableOne": {
"data": {
"id": "3323",
"type": "test_entities"
}
},
"one": {
"data": {
"id": "4459",
"type": "test_entities"
}
},
"optionalNullableOne": {
"data": {
"id": "1229",
"type": "test_entities"
}
}
}
}
""".data(using: .utf8)!
let entity_optional_nullable_nulled_relationship = """
{
"id": "1",
"type": "ninth_test_entities",
"relationships": {
"nullableOne": {
"data": {
"id": "3323",
"type": "test_entities"
}
},
"one": {
"data": {
"id": "4459",
"type": "test_entities"
}
},
"optionalNullableOne": {
"data": null
}
}
}
""".data(using: .utf8)!
let entity_omitted_relationship = """
{
"id": "1",
@@ -249,6 +300,35 @@ let entity_omitted_relationship = """
}
""".data(using: .utf8)!
let entity_optional_to_many_relationship_not_omitted = """
{
"id": "1",
"type": "ninth_test_entities",
"relationships": {
"nullableOne": {
"data": {
"id": "3323",
"type": "test_entities"
}
},
"one": {
"data": {
"id": "4459",
"type": "test_entities"
}
},
"optionalMany": {
"data": [
{
"id": "332223",
"type": "test_entities"
}
]
}
}
}
""".data(using: .utf8)!
let entity_nulled_relationship = """
{
"id": "1",