Skip to content

Commit 35d6cbb

Browse files
committed
Add support for Relationship Meta and Links (untested)
1 parent d667e91 commit 35d6cbb

14 files changed

Lines changed: 171 additions & 65 deletions

File tree

JSONAPI.playground/Sources/Entities.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ extension String: CreatableRawIdType {
2323
}
2424
}
2525

26-
// MARK: - Entity typealias for convenience
26+
// MARK: - typealiases for convenience
2727
public typealias ExampleEntity<Description: EntityDescription> = Entity<Description, NoMetadata, NoLinks, String>
28+
public typealias ToOne<E: OptionalRelatable> = ToOneRelationship<E, NoMetadata, NoLinks>
29+
public typealias ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLinks>
2830

2931
// MARK: - A few resource objects (entities)
3032
public enum PersonDescription: EntityDescription {
@@ -46,11 +48,11 @@ public enum PersonDescription: EntityDescription {
4648
}
4749

4850
public struct Relationships: JSONAPI.Relationships {
49-
public let friends: ToManyRelationship<Person>
50-
public let dogs: ToManyRelationship<Dog>
51-
public let home: ToOneRelationship<House>
51+
public let friends: ToMany<Person>
52+
public let dogs: ToMany<Dog>
53+
public let home: ToOne<House>
5254

53-
public init(friends: ToManyRelationship<Person>, dogs: ToManyRelationship<Dog>, home: ToOneRelationship<House>) {
55+
public init(friends: ToMany<Person>, dogs: ToMany<Dog>, home: ToOne<House>) {
5456
self.friends = friends
5557
self.dogs = dogs
5658
self.home = home
@@ -79,9 +81,9 @@ public enum DogDescription: EntityDescription {
7981
}
8082

8183
public struct Relationships: JSONAPI.Relationships {
82-
public let owner: ToOneRelationship<Person?>
84+
public let owner: ToOne<Person?>
8385

84-
public init(owner: ToOneRelationship<Person?>) {
86+
public init(owner: ToOne<Person?>) {
8587
self.owner = owner
8688
}
8789
}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ Note that Playground support for importing non-system Frameworks is still a bit
5252

5353
#### Relationship Object
5454
- [x] `data`
55-
- [ ] `links`
56-
- [ ] `meta`
55+
- [x] `links` (untested)
56+
- [x] `meta` (untested)
5757

5858
#### Links Object
5959
- [x] `href`

Sources/JSONAPI/Resource/Entity.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,12 @@ extension Entity where MetaType == NoMetadata, LinksType == NoLinks, EntityRawId
354354
public extension Entity where EntityRawIdType: JSONAPI.RawIdType {
355355
/// Get a pointer to this entity that can be used as a
356356
/// relationship to another entity.
357-
public var pointer: ToOneRelationship<Entity> {
358-
return ToOneRelationship(entity: self)
357+
public var pointer: ToOneRelationship<Entity, NoMetadata, NoLinks> {
358+
return ToOneRelationship(entity: self, meta: .none, links: .none)
359+
}
360+
361+
public func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, andLinks links: LType) -> ToOneRelationship<Entity, MType, LType> {
362+
return ToOneRelationship(entity: self, meta: meta, links: links)
359363
}
360364
}
361365

@@ -388,14 +392,14 @@ public extension EntityProxy {
388392
/// Access to an Id of a `ToOneRelationship`.
389393
/// This allows you to write `entity ~> \.other` instead
390394
/// of `entity.relationships.other.id`.
391-
public static func ~><OtherEntity: OptionalRelatable>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity>>) -> OtherEntity.WrappedIdentifier {
395+
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedIdentifier {
392396
return entity.relationships[keyPath: path].id
393397
}
394398

395399
/// Access to all Ids of a `ToManyRelationship`.
396400
/// This allows you to write `entity ~> \.others` instead
397401
/// of `entity.relationships.others.ids`.
398-
public static func ~><OtherEntity: Relatable>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity>>) -> [OtherEntity.Identifier] {
402+
public static func ~><OtherEntity: Relatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToManyRelationship<OtherEntity, MType, LType>>) -> [OtherEntity.Identifier] {
399403
return entity.relationships[keyPath: path].ids
400404
}
401405
}

Sources/JSONAPI/Resource/Relationship.swift

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,61 +5,114 @@
55
// Created by Mathew Polzin on 8/31/18.
66
//
77

8-
public protocol RelationshipType: Codable {}
8+
public protocol RelationshipType: Codable {
9+
associatedtype LinksType
10+
associatedtype MetaType
11+
12+
var links: LinksType { get }
13+
var meta: MetaType { get }
14+
}
915

1016
/// An Entity relationship that can be encoded to or decoded from
1117
/// a JSON API "Resource Linkage."
1218
/// See https://jsonapi.org/format/#document-resource-object-linkage
1319
/// A convenient typealias might make your code much more legible: `One<EntityDescription>`
14-
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable>: RelationshipType, Equatable {
20+
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
1521

1622
public let id: Relatable.WrappedIdentifier
1723

18-
public init(id: Relatable.WrappedIdentifier) {
24+
public let meta: MetaType
25+
public let links: LinksType
26+
27+
public init(id: Relatable.WrappedIdentifier, meta: MetaType, links: LinksType) {
1928
self.id = id
29+
self.meta = meta
30+
self.links = links
31+
}
32+
}
33+
34+
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
35+
public init(id: Relatable.WrappedIdentifier) {
36+
self.init(id: id, meta: .none, links: .none)
2037
}
2138
}
2239

2340
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier {
41+
public init<E: EntityType>(entity: E, meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
42+
self.init(id: entity.id, meta: meta, links: links)
43+
}
44+
}
45+
46+
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier, MetaType == NoMetadata, LinksType == NoLinks {
2447
public init<E: EntityType>(entity: E) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
25-
id = entity.id
48+
self.init(id: entity.id, meta: .none, links: .none)
2649
}
2750
}
2851

2952
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier? {
53+
public init<E: EntityType>(entity: E?, meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
54+
self.init(id: entity?.id, meta: meta, links: links)
55+
}
56+
}
57+
58+
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier?, MetaType == NoMetadata, LinksType == NoLinks {
3059
public init<E: EntityType>(entity: E?) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
31-
id = entity?.id
60+
self.init(id: entity?.id, meta: .none, links: .none)
3261
}
3362
}
3463

3564
/// An Entity relationship that can be encoded to or decoded from
3665
/// a JSON API "Resource Linkage."
3766
/// See https://jsonapi.org/format/#document-resource-object-linkage
3867
/// A convenient typealias might make your code much more legible: `Many<EntityDescription>`
39-
public struct ToManyRelationship<Relatable: JSONAPI.Relatable>: RelationshipType, Equatable {
68+
public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
4069

4170
public let ids: [Relatable.Identifier]
4271

43-
public init(ids: [Relatable.Identifier]) {
72+
public let meta: MetaType
73+
public let links: LinksType
74+
75+
public init(ids: [Relatable.Identifier], meta: MetaType, links: LinksType) {
4476
self.ids = ids
77+
self.meta = meta
78+
self.links = links
4579
}
4680

47-
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T>]) where T.WrappedIdentifier == Relatable.Identifier {
81+
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedIdentifier == Relatable.Identifier {
4882
ids = relationships.map { $0.id }
83+
self.meta = meta
84+
self.links = links
85+
}
86+
87+
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
88+
self.init(ids: entities.map { $0.id }, meta: meta, links: links)
4989
}
5090

51-
private init() {
52-
ids = []
91+
private init(meta: MetaType, links: LinksType) {
92+
self.init(ids: [], meta: meta, links: links)
5393
}
5494

55-
public static var none: ToManyRelationship {
56-
return .init()
95+
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
96+
return ToManyRelationship(meta: meta, links: links)
5797
}
5898
}
5999

60-
extension ToManyRelationship {
100+
extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks {
101+
102+
public init(ids: [Relatable.Identifier]) {
103+
self.init(ids: ids, meta: .none, links: .none)
104+
}
105+
106+
public init<T: JSONAPI.Relatable>(relationships: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.WrappedIdentifier == Relatable.Identifier {
107+
self.init(relationships: relationships, meta: .none, links: .none)
108+
}
109+
110+
public static var none: ToManyRelationship {
111+
return .none(withMeta: .none, links: .none)
112+
}
113+
61114
public init<E: EntityType>(entities: [E]) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
62-
ids = entities.map { $0.id }
115+
self.init(entities: entities, meta: .none, links: .none)
63116
}
64117
}
65118

@@ -85,6 +138,8 @@ extension Optional: OptionalRelatable where Wrapped: Relatable {
85138
// MARK: Codable
86139
private enum ResourceLinkageCodingKeys: String, CodingKey {
87140
case data = "data"
141+
case meta = "meta"
142+
case links = "links"
88143
}
89144
private enum ResourceIdentifierCodingKeys: String, CodingKey {
90145
case id = "id"
@@ -103,6 +158,18 @@ extension ToOneRelationship {
103158
public init(from decoder: Decoder) throws {
104159
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
105160

161+
if let noMeta = NoMetadata() as? MetaType {
162+
meta = noMeta
163+
} else {
164+
meta = try container.decode(MetaType.self, forKey: .meta)
165+
}
166+
167+
if let noLinks = NoLinks() as? LinksType {
168+
links = noLinks
169+
} else {
170+
links = try container.decode(LinksType.self, forKey: .links)
171+
}
172+
106173
// A little trickery follows. If the id is nil, the
107174
// container.decode(Identifier.self) will fail even if Identifier
108175
// is Optional. However, we can check if decoding nil
@@ -133,6 +200,14 @@ extension ToOneRelationship {
133200
try container.encodeNil(forKey: .data)
134201
}
135202

203+
if MetaType.self != NoMetadata.self {
204+
try container.encode(meta, forKey: .meta)
205+
}
206+
207+
if LinksType.self != NoLinks.self {
208+
try container.encode(links, forKey: .links)
209+
}
210+
136211
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
137212

138213
try identifier.encode(id, forKey: .id)
@@ -143,7 +218,19 @@ extension ToOneRelationship {
143218
extension ToManyRelationship {
144219
public init(from decoder: Decoder) throws {
145220
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
146-
221+
222+
if let noMeta = NoMetadata() as? MetaType {
223+
meta = noMeta
224+
} else {
225+
meta = try container.decode(MetaType.self, forKey: .meta)
226+
}
227+
228+
if let noLinks = NoLinks() as? LinksType {
229+
links = noLinks
230+
} else {
231+
links = try container.decode(LinksType.self, forKey: .links)
232+
}
233+
147234
var identifiers = try container.nestedUnkeyedContainer(forKey: .data)
148235

149236
var newIds = [Relatable.Identifier]()
@@ -163,6 +250,15 @@ extension ToManyRelationship {
163250

164251
public func encode(to encoder: Encoder) throws {
165252
var container = encoder.container(keyedBy: ResourceLinkageCodingKeys.self)
253+
254+
if MetaType.self != NoMetadata.self {
255+
try container.encode(meta, forKey: .meta)
256+
}
257+
258+
if LinksType.self != NoLinks.self {
259+
try container.encode(links, forKey: .links)
260+
}
261+
166262
var identifiers = container.nestedUnkeyedContainer(forKey: .data)
167263

168264
for id in ids {

Sources/JSONAPITestLib/EntityCheck.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ extension TransformedAttribute: AttributeTypeWithOptionalArray where RawValue: O
4545
private protocol OptionalRelationshipType {}
4646
extension Optional: OptionalRelationshipType where Wrapped: RelationshipType {}
4747

48+
private protocol _RelationshipType {}
49+
extension ToOneRelationship: _RelationshipType {}
50+
extension ToManyRelationship: _RelationshipType {}
51+
4852
public extension Entity {
4953
public static func check(_ entity: Entity) throws {
5054
var problems = [EntityCheckError]()
@@ -72,7 +76,7 @@ public extension Entity {
7276
}
7377

7478
for relationship in relationshipsMirror.children {
75-
if relationship.value as? RelationshipType == nil {
79+
if relationship.value as? _RelationshipType == nil {
7680
problems.append(.nonRelationship(named: relationship.label ?? "unnamed"))
7781
}
7882
}

Sources/JSONAPITestLib/Relationship+Literal.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,38 @@
77

88
import JSONAPI
99

10-
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral {
10+
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
1111
public init(nilLiteral: ()) {
1212

1313
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
1414
}
1515
}
1616

17-
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral {
17+
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
1818
public typealias UnicodeScalarLiteralType = Relatable.WrappedIdentifier.UnicodeScalarLiteralType
1919

2020
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
2121
self.init(id: Relatable.WrappedIdentifier(unicodeScalarLiteral: value))
2222
}
2323
}
2424

25-
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral {
25+
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
2626
public typealias ExtendedGraphemeClusterLiteralType = Relatable.WrappedIdentifier.ExtendedGraphemeClusterLiteralType
2727

2828
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
2929
self.init(id: Relatable.WrappedIdentifier(extendedGraphemeClusterLiteral: value))
3030
}
3131
}
3232

33-
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral {
33+
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
3434
public typealias StringLiteralType = Relatable.WrappedIdentifier.StringLiteralType
3535

3636
public init(stringLiteral value: StringLiteralType) {
3737
self.init(id: Relatable.WrappedIdentifier(stringLiteral: value))
3838
}
3939
}
4040

41-
extension ToManyRelationship: ExpressibleByArrayLiteral {
41+
extension ToManyRelationship: ExpressibleByArrayLiteral where MetaType == NoMetadata, LinksType == NoLinks {
4242
public typealias ArrayLiteralElement = Relatable.Identifier
4343

4444
public init(arrayLiteral elements: ArrayLiteralElement...) {

Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ extension ComputedPropertiesTests {
4949
}
5050

5151
public struct Relationships: JSONAPI.Relationships {
52-
public let other: ToOneRelationship<TestType>
52+
public let other: ToOneRelationship<TestType, NoMetadata, NoLinks>
5353

54-
public var computed: ToOneRelationship<TestType> {
54+
public var computed: ToOneRelationship<TestType, NoMetadata, NoLinks> {
5555
return other
5656
}
5757
}

Tests/JSONAPITests/Document/DocumentTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,7 @@ extension DocumentTests {
978978
typealias Attributes = NoAttributes
979979

980980
struct Relationships: JSONAPI.Relationships {
981-
let author: ToOneRelationship<Author>
981+
let author: ToOneRelationship<Author, NoMetadata, NoLinks>
982982
}
983983
}
984984

0 commit comments

Comments
 (0)