Skip to content

Commit 08949d0

Browse files
committed
Move encoding error type to its own file. Restructure Relatable and OptionalRelatable to not be dependent upon EntityDescription
1 parent 3047e2d commit 08949d0

9 files changed

Lines changed: 155 additions & 68 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// EncodingError.swift
3+
// JSONAPI
4+
//
5+
// Created by Mathew Polzin on 12/7/18.
6+
//
7+
8+
public enum JSONAPIEncodingError: Swift.Error {
9+
case typeMismatch(expected: String, found: String)
10+
case illegalEncoding(String)
11+
case illegalDecoding(String)
12+
case missingOrMalformedMetadata
13+
case missingOrMalformedLinks
14+
}

Sources/JSONAPI/Resource/Entity.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,26 @@ public struct NoAttributes: Attributes {
2626
public static var none: NoAttributes { return .init() }
2727
}
2828

29+
/// Something that is JSONTyped provides a String representation
30+
/// of its type.
31+
public protocol JSONTyped {
32+
static var type: String { get }
33+
}
34+
2935
/// An `EntityDescription` describes a JSON API
3036
/// Resource Object. The Resource Object
3137
/// itself is encoded and decoded as an
3238
/// `Entity`, which gets specialized on an
3339
/// `EntityDescription`.
34-
public protocol EntityDescription {
40+
public protocol EntityDescription: JSONTyped {
3541
associatedtype Attributes: JSONAPI.Attributes
3642
associatedtype Relationships: JSONAPI.Relationships
37-
38-
static var type: String { get }
3943
}
4044

4145
/// EntityProxy is a protocol that can be used to create
4246
/// types that _act_ like Entities but cannot be encoded
4347
/// or decoded as Entities.
44-
public protocol EntityProxy: Equatable {
48+
public protocol EntityProxy: Equatable, JSONTyped {
4549
associatedtype Description: EntityDescription
4650
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
4751

@@ -108,9 +112,10 @@ public struct Entity<Description: JSONAPI.EntityDescription, MetaType: JSONAPI.M
108112
}
109113
}
110114

111-
extension Entity: IdentifiableEntityType, Relatable, WrappedRelatable where EntityRawIdType: JSONAPI.RawIdType {
115+
extension Entity: IdentifiableEntityType, Relatable, OptionalRelatable where EntityRawIdType: JSONAPI.RawIdType {
112116
public typealias Identifier = Entity.Id
113-
public typealias WrappedIdentifier = Identifier
117+
public typealias Wrapped = Entity
118+
public typealias WrappedId = Identifier
114119
}
115120

116121
extension Entity: CustomStringConvertible {
@@ -427,14 +432,14 @@ public extension EntityProxy {
427432
/// Access to an Id of a `ToOneRelationship`.
428433
/// This allows you to write `entity ~> \.other` instead
429434
/// of `entity.relationships.other.id`.
430-
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedIdentifier {
435+
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedId {
431436
return entity.relationships[keyPath: path].id
432437
}
433438

434439
/// Access to an Id of an optional `ToOneRelationship`.
435440
/// This allows you to write `entity ~> \.other` instead
436441
/// of `entity.relationships.other?.id`.
437-
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? {
442+
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.WrappedId where OtherEntity.WrappedId == OtherEntity.Wrapped.Identifier? {
438443
// Implementation Note: This signature applies to `ToOneRelationship<E?, _, _>?`
439444
// whereas the one below applies to `ToOneRelationship<E, _, _>?`
440445
return entity.relationships[keyPath: path]?.id
@@ -443,7 +448,7 @@ public extension EntityProxy {
443448
/// Access to an Id of an optional `ToOneRelationship`.
444449
/// This allows you to write `entity ~> \.other` instead
445450
/// of `entity.relationships.other?.id`.
446-
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 {
451+
public static func ~><OtherEntity: Relatable & OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>?>) -> OtherEntity.Identifier? where OtherEntity.WrappedId == OtherEntity.Identifier {
447452
// Implementation Note: This signature applies to `ToOneRelationship<E, _, _>?`
448453
// whereas the one above applies to `ToOneRelationship<E?, _, _>?`
449454
return entity.relationships[keyPath: path]?.id

Sources/JSONAPI/Resource/Id.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityProxy>: Codable,
7474

7575
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {}
7676

77+
extension Id: WrappedIdType where RawType: RawIdType {
78+
public typealias Identifier = Id
79+
}
80+
7781
extension Id: CreatableIdType where RawType: CreatableRawIdType {
7882
public init() {
7983
rawValue = .unique()

Sources/JSONAPI/Resource/Relationship.swift

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,46 @@ public protocol RelationshipType: Codable {
1717
/// a JSON API "Resource Linkage."
1818
/// See https://jsonapi.org/format/#document-resource-object-linkage
1919
/// A convenient typealias might make your code much more legible: `One<EntityDescription>`
20-
public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
20+
public struct ToOneRelationship<OptionalRelatable: JSONAPI.OptionalRelatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
2121

22-
public let id: Relatable.WrappedIdentifier
22+
public let id: OptionalRelatable.WrappedId
2323

2424
public let meta: MetaType
2525
public let links: LinksType
2626

27-
public init(id: Relatable.WrappedIdentifier, meta: MetaType, links: LinksType) {
27+
public init(id: OptionalRelatable.WrappedId, meta: MetaType, links: LinksType) {
2828
self.id = id
2929
self.meta = meta
3030
self.links = links
3131
}
3232
}
3333

3434
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
35-
public init(id: Relatable.WrappedIdentifier) {
35+
public init(id: OptionalRelatable.WrappedId) {
3636
self.init(id: id, meta: .none, links: .none)
3737
}
3838
}
3939

40-
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 {
40+
extension ToOneRelationship {
41+
public init<E: EntityType>(entity: E, meta: MetaType, links: LinksType) where E.Id == OptionalRelatable.WrappedId {
4242
self.init(id: entity.id, meta: meta, links: links)
4343
}
4444
}
4545

46-
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier, MetaType == NoMetadata, LinksType == NoLinks {
47-
public init<E: EntityType>(entity: E) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
46+
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
47+
public init<E: EntityType>(entity: E) where E.Id == OptionalRelatable.WrappedId {
4848
self.init(id: entity.id, meta: .none, links: .none)
4949
}
5050
}
5151

52-
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 {
52+
extension ToOneRelationship where OptionalRelatable.WrappedId == OptionalRelatable.Wrapped.Identifier? {
53+
public init<E: EntityType>(entity: E?, meta: MetaType, links: LinksType) where E.Id == OptionalRelatable.Wrapped.Identifier {
5454
self.init(id: entity?.id, meta: meta, links: links)
5555
}
5656
}
5757

58-
extension ToOneRelationship where Relatable.WrappedIdentifier == Relatable.Identifier?, MetaType == NoMetadata, LinksType == NoLinks {
59-
public init<E: EntityType>(entity: E?) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
58+
extension ToOneRelationship where OptionalRelatable.WrappedId == OptionalRelatable.Wrapped.Identifier?, MetaType == NoMetadata, LinksType == NoLinks {
59+
public init<E: EntityType>(entity: E?) where E.Id == OptionalRelatable.Wrapped.Identifier {
6060
self.init(id: entity?.id, meta: .none, links: .none)
6161
}
6262
}
@@ -78,20 +78,20 @@ public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI
7878
self.links = links
7979
}
8080

81-
public init<T: JSONAPI.Relatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedIdentifier == Relatable.Identifier {
81+
public init<T: JSONAPI.OptionalRelatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedId == Relatable.Identifier {
8282
ids = pointers.map { $0.id }
8383
self.meta = meta
8484
self.links = links
8585
}
8686

87-
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
87+
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Id == Relatable.Identifier {
8888
self.init(ids: entities.map { $0.id }, meta: meta, links: links)
8989
}
9090

9191
private init(meta: MetaType, links: LinksType) {
9292
self.init(ids: [], meta: meta, links: links)
9393
}
94-
94+
9595
public static func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
9696
return ToManyRelationship(meta: meta, links: links)
9797
}
@@ -103,36 +103,44 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks
103103
self.init(ids: ids, meta: .none, links: .none)
104104
}
105105

106-
public init<T: JSONAPI.Relatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.WrappedIdentifier == Relatable.Identifier {
106+
public init<T: JSONAPI.OptionalRelatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.WrappedId == Relatable.Identifier {
107107
self.init(pointers: pointers, meta: .none, links: .none)
108108
}
109109

110110
public static var none: ToManyRelationship {
111111
return .none(withMeta: .none, links: .none)
112112
}
113113

114-
public init<E: EntityType>(entities: [E]) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
114+
public init<E: EntityType>(entities: [E]) where E.Id == Relatable.Identifier {
115115
self.init(entities: entities, meta: .none, links: .none)
116116
}
117117
}
118118

119-
/// The WrappedRelatable (a.k.a OptionalRelatable) protocol
120-
/// describes Optional<T: Relatable> and Relatable types.
121-
public protocol WrappedRelatable: Codable, Equatable {
122-
associatedtype Description: EntityDescription
119+
/// The Relatable protocol describes anything that
120+
/// has an IdType Identifier
121+
public protocol Relatable: JSONTyped {
123122
associatedtype Identifier: JSONAPI.IdType
124-
associatedtype WrappedIdentifier: Codable, Equatable
125123
}
126-
public typealias OptionalRelatable = WrappedRelatable
127124

128-
/// The Relatable protocol describes anything that
129-
/// has an IdType Identifier
130-
public protocol Relatable: WrappedRelatable {}
125+
/// OptionalRelatable just describes an Optional
126+
/// with a Reltable Wrapped type.
127+
public protocol OptionalRelatable: JSONTyped {
128+
associatedtype Wrapped: JSONAPI.Relatable
129+
associatedtype WrappedId: WrappedIdType where WrappedId.Identifier == Wrapped.Identifier
130+
}
131131

132-
extension Optional: OptionalRelatable where Wrapped: Relatable {
133-
public typealias Description = Wrapped.Description
134-
public typealias Identifier = Wrapped.Identifier
135-
public typealias WrappedIdentifier = Identifier?
132+
public protocol WrappedIdType: Codable, Equatable {
133+
associatedtype Identifier: JSONAPI.IdType
134+
}
135+
136+
extension Optional: WrappedIdType where Wrapped: IdType {
137+
public typealias Identifier = Wrapped
138+
}
139+
140+
extension Optional: OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
141+
public typealias WrappedId = Wrapped.Identifier?
142+
143+
public static var type: String { return Wrapped.type }
136144
}
137145

138146
// MARK: Codable
@@ -146,14 +154,6 @@ private enum ResourceIdentifierCodingKeys: String, CodingKey {
146154
case entityType = "type"
147155
}
148156

149-
public enum JSONAPIEncodingError: Swift.Error {
150-
case typeMismatch(expected: String, found: String)
151-
case illegalEncoding(String)
152-
case illegalDecoding(String)
153-
case missingOrMalformedMetadata
154-
case missingOrMalformedLinks
155-
}
156-
157157
extension ToOneRelationship {
158158
public init(from decoder: Decoder) throws {
159159
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
@@ -177,7 +177,7 @@ extension ToOneRelationship {
177177
// type at which point we can store nil in `id`.
178178
let anyNil: Any? = nil
179179
if try container.decodeNil(forKey: .data),
180-
let val = anyNil as? Relatable.WrappedIdentifier {
180+
let val = anyNil as? OptionalRelatable.WrappedId {
181181
id = val
182182
return
183183
}
@@ -186,11 +186,11 @@ extension ToOneRelationship {
186186

187187
let type = try identifier.decode(String.self, forKey: .entityType)
188188

189-
guard type == Relatable.Description.type else {
190-
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.Description.type, found: type)
189+
guard type == OptionalRelatable.type else {
190+
throw JSONAPIEncodingError.typeMismatch(expected: OptionalRelatable.type, found: type)
191191
}
192192

193-
id = try identifier.decode(Relatable.WrappedIdentifier.self, forKey: .id)
193+
id = try identifier.decode(OptionalRelatable.WrappedId.self, forKey: .id)
194194
}
195195

196196
public func encode(to encoder: Encoder) throws {
@@ -211,7 +211,7 @@ extension ToOneRelationship {
211211
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
212212

213213
try identifier.encode(id, forKey: .id)
214-
try identifier.encode(Relatable.Description.type, forKey: .entityType)
214+
try identifier.encode(OptionalRelatable.type, forKey: .entityType)
215215
}
216216
}
217217

@@ -239,8 +239,8 @@ extension ToManyRelationship {
239239

240240
let type = try identifier.decode(String.self, forKey: .entityType)
241241

242-
guard type == Relatable.Description.type else {
243-
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.Description.type, found: type)
242+
guard type == Relatable.type else {
243+
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.type, found: type)
244244
}
245245

246246
newIds.append(try identifier.decode(Relatable.Identifier.self, forKey: .id))
@@ -265,7 +265,7 @@ extension ToManyRelationship {
265265
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
266266

267267
try identifier.encode(id, forKey: .id)
268-
try identifier.encode(Relatable.Description.type, forKey: .entityType)
268+
try identifier.encode(Relatable.type, forKey: .entityType)
269269
}
270270
}
271271
}

Sources/JSONAPITestLib/Relationship+Literal.swift

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

88
import JSONAPI
99

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

13-
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
13+
self.init(id: OptionalRelatable.WrappedId(nilLiteral: ()))
1414
}
1515
}
1616

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

2020
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
21-
self.init(id: Relatable.WrappedIdentifier(unicodeScalarLiteral: value))
21+
self.init(id: OptionalRelatable.WrappedId(unicodeScalarLiteral: value))
2222
}
2323
}
2424

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

2828
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
29-
self.init(id: Relatable.WrappedIdentifier(extendedGraphemeClusterLiteral: value))
29+
self.init(id: OptionalRelatable.WrappedId(extendedGraphemeClusterLiteral: value))
3030
}
3131
}
3232

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

3636
public init(stringLiteral value: StringLiteralType) {
37-
self.init(id: Relatable.WrappedIdentifier(stringLiteral: value))
37+
self.init(id: OptionalRelatable.WrappedId(stringLiteral: value))
3838
}
3939
}
4040

Tests/JSONAPITests/Computed Properties/ComputedPropertiesTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ComputedPropertiesTests: XCTestCase {
2727
let entity = decoded(type: TestType.self, data: computed_property_attribute)
2828

2929
XCTAssertEqual(entity[\.computed], "Sarah2")
30+
XCTAssertEqual(entity[\.secretsOut], "shhhh")
3031
}
3132

3233
func test_ComputedNonAttributeAccess() {
@@ -49,13 +50,19 @@ extension ComputedPropertiesTests {
4950

5051
public struct Attributes: JSONAPI.Attributes {
5152
public let name: Attribute<String>
53+
private let secret: Attribute<String>
54+
5255
public var computed: Attribute<String> {
5356
return name.map { $0 + "2" }
5457
}
5558

5659
public var computed2: String {
5760
return computed.value
5861
}
62+
63+
public var secretsOut: String {
64+
return secret.value
65+
}
5966
}
6067

6168
public struct Relationships: JSONAPI.Relationships {

Tests/JSONAPITests/Computed Properties/stubs/ComputedPropertiesStubs.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ let computed_property_attribute = """
1010
"id": "1234",
1111
"type": "test",
1212
"attributes": {
13-
"name": "Sarah"
13+
"name": "Sarah",
14+
"secret": "shhhh"
1415
},
1516
"relationships": {
1617
"other": {

0 commit comments

Comments
 (0)