mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Added support for relationship operator ~> to optional relationships
This commit is contained in:
@@ -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 ~>
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user