Move encoding error type to its own file. Restructure Relatable and OptionalRelatable to not be dependent upon EntityDescription

This commit is contained in:
Mathew Polzin
2018-12-08 00:36:05 -08:00
parent 3047e2d23a
commit 08949d0a93
9 changed files with 156 additions and 69 deletions
+14
View File
@@ -0,0 +1,14 @@
//
// EncodingError.swift
// JSONAPI
//
// Created by Mathew Polzin on 12/7/18.
//
public enum JSONAPIEncodingError: Swift.Error {
case typeMismatch(expected: String, found: String)
case illegalEncoding(String)
case illegalDecoding(String)
case missingOrMalformedMetadata
case missingOrMalformedLinks
}
+14 -9
View File
@@ -26,22 +26,26 @@ public struct NoAttributes: Attributes {
public static var none: NoAttributes { return .init() }
}
/// Something that is JSONTyped provides a String representation
/// of its type.
public protocol JSONTyped {
static var type: String { get }
}
/// An `EntityDescription` describes a JSON API
/// Resource Object. The Resource Object
/// itself is encoded and decoded as an
/// `Entity`, which gets specialized on an
/// `EntityDescription`.
public protocol EntityDescription {
public protocol EntityDescription: JSONTyped {
associatedtype Attributes: JSONAPI.Attributes
associatedtype Relationships: JSONAPI.Relationships
static var type: String { get }
}
/// EntityProxy is a protocol that can be used to create
/// types that _act_ like Entities but cannot be encoded
/// or decoded as Entities.
public protocol EntityProxy: Equatable {
public protocol EntityProxy: Equatable, JSONTyped {
associatedtype Description: EntityDescription
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
@@ -108,9 +112,10 @@ public struct Entity<Description: JSONAPI.EntityDescription, MetaType: JSONAPI.M
}
}
extension Entity: IdentifiableEntityType, Relatable, WrappedRelatable where EntityRawIdType: JSONAPI.RawIdType {
extension Entity: IdentifiableEntityType, Relatable, OptionalRelatable where EntityRawIdType: JSONAPI.RawIdType {
public typealias Identifier = Entity.Id
public typealias WrappedIdentifier = Identifier
public typealias Wrapped = Entity
public typealias WrappedId = Identifier
}
extension Entity: CustomStringConvertible {
@@ -427,14 +432,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, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedIdentifier {
public static func ~><OtherEntity: OptionalRelatable, MType: JSONAPI.Meta, LType: JSONAPI.Links>(entity: Self, path: KeyPath<Description.Relationships, ToOneRelationship<OtherEntity, MType, LType>>) -> OtherEntity.WrappedId {
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? {
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? {
// Implementation Note: This signature applies to `ToOneRelationship<E?, _, _>?`
// whereas the one below applies to `ToOneRelationship<E, _, _>?`
return entity.relationships[keyPath: path]?.id
@@ -443,7 +448,7 @@ public extension EntityProxy {
/// 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 {
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 {
// Implementation Note: This signature applies to `ToOneRelationship<E, _, _>?`
// whereas the one above applies to `ToOneRelationship<E?, _, _>?`
return entity.relationships[keyPath: path]?.id
+4
View File
@@ -74,6 +74,10 @@ public struct Id<RawType: MaybeRawId, EntityType: JSONAPI.EntityProxy>: Codable,
extension Id: Hashable, CustomStringConvertible, IdType where RawType: RawIdType {}
extension Id: WrappedIdType where RawType: RawIdType {
public typealias Identifier = Id
}
extension Id: CreatableIdType where RawType: CreatableRawIdType {
public init() {
rawValue = .unique()
+47 -47
View File
@@ -17,14 +17,14 @@ public protocol RelationshipType: Codable {
/// 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, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
public struct ToOneRelationship<OptionalRelatable: JSONAPI.OptionalRelatable, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links>: RelationshipType, Equatable {
public let id: Relatable.WrappedIdentifier
public let id: OptionalRelatable.WrappedId
public let meta: MetaType
public let links: LinksType
public init(id: Relatable.WrappedIdentifier, meta: MetaType, links: LinksType) {
public init(id: OptionalRelatable.WrappedId, meta: MetaType, links: LinksType) {
self.id = id
self.meta = meta
self.links = links
@@ -32,31 +32,31 @@ public struct ToOneRelationship<Relatable: JSONAPI.OptionalRelatable, MetaType:
}
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init(id: Relatable.WrappedIdentifier) {
public init(id: OptionalRelatable.WrappedId) {
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 {
extension ToOneRelationship {
public init<E: EntityType>(entity: E, meta: MetaType, links: LinksType) where E.Id == OptionalRelatable.WrappedId {
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 {
extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init<E: EntityType>(entity: E) where E.Id == OptionalRelatable.WrappedId {
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 {
extension ToOneRelationship where OptionalRelatable.WrappedId == OptionalRelatable.Wrapped.Identifier? {
public init<E: EntityType>(entity: E?, meta: MetaType, links: LinksType) where E.Id == OptionalRelatable.Wrapped.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 {
extension ToOneRelationship where OptionalRelatable.WrappedId == OptionalRelatable.Wrapped.Identifier?, MetaType == NoMetadata, LinksType == NoLinks {
public init<E: EntityType>(entity: E?) where E.Id == OptionalRelatable.Wrapped.Identifier {
self.init(id: entity?.id, meta: .none, links: .none)
}
}
@@ -78,20 +78,20 @@ public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI
self.links = links
}
public init<T: JSONAPI.Relatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedIdentifier == Relatable.Identifier {
public init<T: JSONAPI.OptionalRelatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.WrappedId == Relatable.Identifier {
ids = pointers.map { $0.id }
self.meta = meta
self.links = links
}
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
public init<E: EntityType>(entities: [E], meta: MetaType, links: LinksType) where 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 func none(withMeta meta: MetaType, links: LinksType) -> ToManyRelationship {
return ToManyRelationship(meta: meta, links: links)
}
@@ -103,7 +103,7 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks
self.init(ids: ids, meta: .none, links: .none)
}
public init<T: JSONAPI.Relatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.WrappedIdentifier == Relatable.Identifier {
public init<T: JSONAPI.OptionalRelatable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.WrappedId == Relatable.Identifier {
self.init(pointers: pointers, meta: .none, links: .none)
}
@@ -111,28 +111,36 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks
return .none(withMeta: .none, links: .none)
}
public init<E: EntityType>(entities: [E]) where E.Description == Relatable.Description, E.Id == Relatable.Identifier {
public init<E: EntityType>(entities: [E]) where E.Id == Relatable.Identifier {
self.init(entities: entities, meta: .none, links: .none)
}
}
/// The WrappedRelatable (a.k.a OptionalRelatable) protocol
/// describes Optional<T: Relatable> and Relatable types.
public protocol WrappedRelatable: Codable, Equatable {
associatedtype Description: EntityDescription
associatedtype Identifier: JSONAPI.IdType
associatedtype WrappedIdentifier: Codable, Equatable
}
public typealias OptionalRelatable = WrappedRelatable
/// The Relatable protocol describes anything that
/// has an IdType Identifier
public protocol Relatable: WrappedRelatable {}
public protocol Relatable: JSONTyped {
associatedtype Identifier: JSONAPI.IdType
}
extension Optional: OptionalRelatable where Wrapped: Relatable {
public typealias Description = Wrapped.Description
public typealias Identifier = Wrapped.Identifier
public typealias WrappedIdentifier = Identifier?
/// OptionalRelatable just describes an Optional
/// with a Reltable Wrapped type.
public protocol OptionalRelatable: JSONTyped {
associatedtype Wrapped: JSONAPI.Relatable
associatedtype WrappedId: WrappedIdType where WrappedId.Identifier == Wrapped.Identifier
}
public protocol WrappedIdType: Codable, Equatable {
associatedtype Identifier: JSONAPI.IdType
}
extension Optional: WrappedIdType where Wrapped: IdType {
public typealias Identifier = Wrapped
}
extension Optional: OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
public typealias WrappedId = Wrapped.Identifier?
public static var type: String { return Wrapped.type }
}
// MARK: Codable
@@ -146,14 +154,6 @@ private enum ResourceIdentifierCodingKeys: String, CodingKey {
case entityType = "type"
}
public enum JSONAPIEncodingError: Swift.Error {
case typeMismatch(expected: String, found: String)
case illegalEncoding(String)
case illegalDecoding(String)
case missingOrMalformedMetadata
case missingOrMalformedLinks
}
extension ToOneRelationship {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResourceLinkageCodingKeys.self)
@@ -177,7 +177,7 @@ extension ToOneRelationship {
// type at which point we can store nil in `id`.
let anyNil: Any? = nil
if try container.decodeNil(forKey: .data),
let val = anyNil as? Relatable.WrappedIdentifier {
let val = anyNil as? OptionalRelatable.WrappedId {
id = val
return
}
@@ -186,11 +186,11 @@ extension ToOneRelationship {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.Description.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.Description.type, found: type)
guard type == OptionalRelatable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: OptionalRelatable.type, found: type)
}
id = try identifier.decode(Relatable.WrappedIdentifier.self, forKey: .id)
id = try identifier.decode(OptionalRelatable.WrappedId.self, forKey: .id)
}
public func encode(to encoder: Encoder) throws {
@@ -211,7 +211,7 @@ extension ToOneRelationship {
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
try identifier.encode(id, forKey: .id)
try identifier.encode(Relatable.Description.type, forKey: .entityType)
try identifier.encode(OptionalRelatable.type, forKey: .entityType)
}
}
@@ -239,8 +239,8 @@ extension ToManyRelationship {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.Description.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.Description.type, found: type)
guard type == Relatable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.type, found: type)
}
newIds.append(try identifier.decode(Relatable.Identifier.self, forKey: .id))
@@ -265,7 +265,7 @@ extension ToManyRelationship {
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
try identifier.encode(id, forKey: .id)
try identifier.encode(Relatable.Description.type, forKey: .entityType)
try identifier.encode(Relatable.type, forKey: .entityType)
}
}
}
@@ -7,34 +7,34 @@
import JSONAPI
extension ToOneRelationship: ExpressibleByNilLiteral where Relatable.WrappedIdentifier: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
extension ToOneRelationship: ExpressibleByNilLiteral where OptionalRelatable.WrappedId: ExpressibleByNilLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public init(nilLiteral: ()) {
self.init(id: Relatable.WrappedIdentifier(nilLiteral: ()))
self.init(id: OptionalRelatable.WrappedId(nilLiteral: ()))
}
}
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where Relatable.WrappedIdentifier: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias UnicodeScalarLiteralType = Relatable.WrappedIdentifier.UnicodeScalarLiteralType
extension ToOneRelationship: ExpressibleByUnicodeScalarLiteral where OptionalRelatable.WrappedId: ExpressibleByUnicodeScalarLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias UnicodeScalarLiteralType = OptionalRelatable.WrappedId.UnicodeScalarLiteralType
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(id: Relatable.WrappedIdentifier(unicodeScalarLiteral: value))
self.init(id: OptionalRelatable.WrappedId(unicodeScalarLiteral: value))
}
}
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where Relatable.WrappedIdentifier: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias ExtendedGraphemeClusterLiteralType = Relatable.WrappedIdentifier.ExtendedGraphemeClusterLiteralType
extension ToOneRelationship: ExpressibleByExtendedGraphemeClusterLiteral where OptionalRelatable.WrappedId: ExpressibleByExtendedGraphemeClusterLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias ExtendedGraphemeClusterLiteralType = OptionalRelatable.WrappedId.ExtendedGraphemeClusterLiteralType
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(id: Relatable.WrappedIdentifier(extendedGraphemeClusterLiteral: value))
self.init(id: OptionalRelatable.WrappedId(extendedGraphemeClusterLiteral: value))
}
}
extension ToOneRelationship: ExpressibleByStringLiteral where Relatable.WrappedIdentifier: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias StringLiteralType = Relatable.WrappedIdentifier.StringLiteralType
extension ToOneRelationship: ExpressibleByStringLiteral where OptionalRelatable.WrappedId: ExpressibleByStringLiteral, MetaType == NoMetadata, LinksType == NoLinks {
public typealias StringLiteralType = OptionalRelatable.WrappedId.StringLiteralType
public init(stringLiteral value: StringLiteralType) {
self.init(id: Relatable.WrappedIdentifier(stringLiteral: value))
self.init(id: OptionalRelatable.WrappedId(stringLiteral: value))
}
}
@@ -27,6 +27,7 @@ class ComputedPropertiesTests: XCTestCase {
let entity = decoded(type: TestType.self, data: computed_property_attribute)
XCTAssertEqual(entity[\.computed], "Sarah2")
XCTAssertEqual(entity[\.secretsOut], "shhhh")
}
func test_ComputedNonAttributeAccess() {
@@ -49,6 +50,8 @@ extension ComputedPropertiesTests {
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
private let secret: Attribute<String>
public var computed: Attribute<String> {
return name.map { $0 + "2" }
}
@@ -56,6 +59,10 @@ extension ComputedPropertiesTests {
public var computed2: String {
return computed.value
}
public var secretsOut: String {
return secret.value
}
}
public struct Relationships: JSONAPI.Relationships {
@@ -10,7 +10,8 @@ let computed_property_attribute = """
"id": "1234",
"type": "test",
"attributes": {
"name": "Sarah"
"name": "Sarah",
"secret": "shhhh"
},
"relationships": {
"other": {
@@ -0,0 +1,56 @@
//
// NonJSONAPIRelatableTests.swift
// JSONAPITests
//
// Created by Mathew Polzin on 12/7/18.
//
import XCTest
import JSONAPI
class NonJSONAPIRelatableTests: XCTestCase {
}
// MARK: - Test Types
extension NonJSONAPIRelatableTests {
// enum TestEntityDescription: EntityDescription {
// static var type: String { return "test" }
//
// typealias Attributes = NoAttributes
//
// struct Relationships: JSONAPI.Relationships {
// let one: ToOneRelationship<NonJSONAPIEntity, NoMetadata, NoLinks>
// let many: ToManyRelationship<NonJSONAPIEntity, NoMetadata, NoLinks>
// }
// }
// enum NonJSONAPIEntityDescription: EntityDescription {
// static var type: String { return "other" }
//
// typealias Attributes = NoAttributes
// typealias Relationships = NoRelationships
// }
// struct NonJSONAPIEntity: Relatable, OptionalRelatable {
// typealias Description = NonJSONAPIEntityDescription
// typealias EntityRawIdType = String
// typealias Identifier = NonJSONAPIEntity.Id
// typealias WrappedIdentifier = NonJSONAPIEntity.Id
//
// let id: Id
//
// let attributes: NoAttributes
// let relationships: NoRelationships
//
// struct Id: IdType {
// var rawValue: NonJSONAPIRelatableTests.NonJSONAPIEntity.Id.RawType
//
// typealias EntityType = <#type#>
//
// typealias RawType = String
//
// let rawValue: String
// }
// }
}
+1 -1
View File
@@ -89,7 +89,7 @@ public extension PolyProxyTests {
public typealias User = Poly2<UserA, UserB>
}
extension Poly2: EntityProxy where A == PolyProxyTests.UserA, B == PolyProxyTests.UserB {
extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == PolyProxyTests.UserB {
public var userA: PolyProxyTests.UserA? {
return a