beginning to do some breaking change cleanup

This commit is contained in:
Mathew Polzin
2019-11-05 19:50:52 -08:00
parent 1a15ab6f9d
commit 33a5ff41a0
3 changed files with 165 additions and 102 deletions
+161 -98
View File
@@ -1,5 +1,5 @@
//
// JSONAPIDocument.swift
// Document.swift
// JSONAPI
//
// Created by Mathew Polzin on 11/5/18.
@@ -7,25 +7,96 @@
import Poly
/// An `EncodableJSONAPIDocument` supports encoding but not decoding.
/// It is actually more restrictive than `JSONAPIDocument` which supports both
/// encoding and decoding.
public protocol EncodableJSONAPIDocument: Equatable, Encodable {
public protocol DocumentBodyDataContext {
associatedtype PrimaryResourceBody: JSONAPI.EncodableResourceBody
associatedtype MetaType: JSONAPI.Meta
associatedtype LinksType: JSONAPI.Links
associatedtype IncludeType: JSONAPI.Include
associatedtype APIDescription: APIDescriptionType
associatedtype Error: JSONAPIError
}
typealias Body = Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body
public protocol DocumentBodyContext: DocumentBodyDataContext {
associatedtype Error: JSONAPIError
associatedtype BodyData: DocumentBodyData
where
BodyData.PrimaryResourceBody == PrimaryResourceBody,
BodyData.MetaType == MetaType,
BodyData.LinksType == LinksType,
BodyData.IncludeType == IncludeType
}
public protocol DocumentBodyData: DocumentBodyDataContext {
/// The document's primary resource body
/// (contains one or many resource objects)
var primary: PrimaryResourceBody { get }
/// The document's included objects
var includes: Includes<IncludeType> { get }
var meta: MetaType { get }
var links: LinksType { get }
}
public protocol DocumentBody: DocumentBodyContext {
/// `true` if the document represents one or more errors. `false` if the
/// document represents JSON:API data and/or metadata.
var isError: Bool { get }
/// Get all errors in the document, if any.
///
/// `nil` if the Document is _not_ an error response. Otherwise,
/// an array containing all errors.
var errors: [Error]? { get }
/// Get the document data
///
/// `nil` if the Document is an error response. Otherwise,
/// a structure containing the primary resource, any included
/// resources, metadata, and links.
var data: BodyData? { get }
/// Quick access to the `data`'s primary resource.
///
/// `nil` if the Document is an error document. Otherwise,
/// the primary resource body, which will contain zero/one, one/many
/// resources dependening on the `PrimaryResourceBody` type.
///
/// See `SingleResourceBody` and `ManyResourceBody`.
var primaryResource: PrimaryResourceBody? { get }
/// Quick access to the `data`'s includes.
///
/// `nil` if the Document is an error document. Otherwise,
/// zero or more includes.
var includes: Includes<IncludeType>? { get }
/// The metadata for the error or data document or `nil` if
/// no metadata is found.
var meta: MetaType? { get }
/// The links for the error or data document or `nil` if
/// no links are found.
var links: LinksType? { get }
}
/// An `EncodableJSONAPIDocument` supports encoding but not decoding.
/// It is actually more restrictive than `JSONAPIDocument` which supports both
/// encoding and decoding.
public protocol EncodableJSONAPIDocument: Equatable, Encodable, DocumentBodyContext {
associatedtype APIDescription: APIDescriptionType
associatedtype Body: DocumentBody
where
Body.PrimaryResourceBody == PrimaryResourceBody,
Body.MetaType == MetaType,
Body.LinksType == LinksType,
Body.IncludeType == IncludeType,
Body.Error == Error,
Body.BodyData == BodyData
var body: Body { get }
}
/// A `JSONAPIDocument` supports encoding and decoding of a JSON:API
/// A `CodableJSONAPIDocument` supports encoding and decoding of a JSON:API
/// compliant Document.
public protocol JSONAPIDocument: EncodableJSONAPIDocument, Decodable where PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: Decodable {}
public protocol CodableJSONAPIDocument: EncodableJSONAPIDocument, Decodable where PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: Decodable {}
/// A JSON API Document represents the entire body
/// of a JSON API request or the entire body of
@@ -36,6 +107,7 @@ public protocol JSONAPIDocument: EncodableJSONAPIDocument, Decodable where Prima
/// Foundation JSONEncoder/Decoder: `KeyDecodingStrategy`
public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, APIDescription: APIDescriptionType, Error: JSONAPIError>: EncodableJSONAPIDocument {
public typealias Include = IncludeType
public typealias BodyData = Body.Data
/// The JSON API Spec calls this the JSON:API Object. It contains version
/// and metadata information about the API itself.
@@ -47,12 +119,14 @@ public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaT
/// included resources, the metadata, and the links that this
/// document type specifies.
public let body: Body
public enum Body: Equatable {
public enum Body: DocumentBody, Equatable {
case errors([Error], meta: MetaType?, links: LinksType?)
case data(Data)
public struct Data: Equatable {
public typealias BodyData = Data
public struct Data: DocumentBodyData, Equatable {
/// The document's Primary Resource object(s)
public let primary: PrimaryResourceBody
/// The document's included objects
@@ -133,81 +207,7 @@ public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaT
}
}
/*
extension Document where IncludeType == NoIncludes {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
self.init(apiDescription: apiDescription, body: body, includes: .none, meta: meta, links: links)
}
}
extension Document where MetaType == NoMetadata {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>, links: LinksType) {
self.init(apiDescription: apiDescription, body: body, includes: includes, meta: .none, links: links)
}
}
extension Document where LinksType == NoLinks {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType) {
self.init(apiDescription: apiDescription, body: body, includes: includes, meta: meta, links: .none)
}
}
extension Document where APIDescription == NoAPIDescription {
public init(body: PrimaryResourceBody, includes: Includes<Include>, meta: MetaType, links: LinksType) {
self.init(apiDescription: .none, body: body, includes: includes, meta: meta, links: links)
}
}
extension Document where IncludeType == NoIncludes, LinksType == NoLinks {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, meta: MetaType) {
self.init(apiDescription: apiDescription, body: body, meta: meta, links: .none)
}
}
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, links: LinksType) {
self.init(apiDescription: apiDescription, body: body, meta: .none, links: links)
}
}
extension Document where IncludeType == NoIncludes, APIDescription == NoAPIDescription {
public init(body: PrimaryResourceBody, meta: MetaType, links: LinksType) {
self.init(apiDescription: .none, body: body, meta: meta, links: links)
}
}
extension Document where MetaType == NoMetadata, LinksType == NoLinks {
public init(apiDescription: APIDescription, body: PrimaryResourceBody, includes: Includes<Include>) {
self.init(apiDescription: apiDescription, body: body, includes: includes, links: .none)
}
}
extension Document where MetaType == NoMetadata, APIDescription == NoAPIDescription {
public init(body: PrimaryResourceBody, includes: Includes<Include>, links: LinksType) {
self.init(apiDescription: .none, body: body, includes: includes, links: links)
}
}
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, LinksType == NoLinks {
public init(apiDescription: APIDescription, body: PrimaryResourceBody) {
self.init(apiDescription: apiDescription, body: body, includes: .none)
}
}
extension Document where MetaType == NoMetadata, LinksType == NoLinks, APIDescription == NoAPIDescription {
public init(body: PrimaryResourceBody, includes: Includes<Include>) {
self.init(apiDescription: .none, body: body, includes: includes)
}
}
extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, LinksType == NoLinks, APIDescription == NoAPIDescription {
public init(body: PrimaryResourceBody) {
self.init(apiDescription: .none, body: body)
}
}
*/
extension Document.Body.Data where PrimaryResourceBody: Appendable {
extension Document.Body.Data where PrimaryResourceBody: ResourceBodyAppendable {
public func merging(_ other: Document.Body.Data,
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
@@ -218,7 +218,7 @@ extension Document.Body.Data where PrimaryResourceBody: Appendable {
}
}
extension Document.Body.Data where PrimaryResourceBody: Appendable, MetaType == NoMetadata, LinksType == NoLinks {
extension Document.Body.Data where PrimaryResourceBody: ResourceBodyAppendable, MetaType == NoMetadata, LinksType == NoLinks {
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
return merging(other,
combiningMetaWith: { _, _ in .none },
@@ -328,7 +328,7 @@ extension Document {
}
}
extension Document: Decodable, JSONAPIDocument where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
extension Document: Decodable, CodableJSONAPIDocument where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootCodingKeys.self)
@@ -420,8 +420,9 @@ extension Document.Body.Data: CustomStringConvertible {
extension Document {
/// A Document that only supports error bodies. This is useful if you wish to pass around a
/// Document type but you wish to constrain it to error values.
@dynamicMemberLookup
public struct ErrorDocument: EncodableJSONAPIDocument {
public typealias BodyData = Document.BodyData
public var body: Document.Body { return document.body }
private let document: Document
@@ -436,8 +437,27 @@ extension Document {
try container.encode(document)
}
public subscript<T>(dynamicMember path: KeyPath<Document, T>) -> T {
return document[keyPath: path]
/// The JSON API Spec calls this the JSON:API Object. It contains version
/// and metadata information about the API itself.
public var apiDescription: APIDescription {
return document.apiDescription
}
/// Get all errors in the document, if any.
public var errors: [Error] {
return document.body.errors ?? []
}
/// The metadata for the error or data document or `nil` if
/// no metadata is found.
public var meta: MetaType? {
return document.body.meta
}
/// The links for the error or data document or `nil` if
/// no links are found.
public var links: LinksType? {
return document.body.links
}
public static func ==(lhs: Document, rhs: ErrorDocument) -> Bool {
@@ -447,8 +467,9 @@ extension Document {
/// A Document that only supports success bodies. This is useful if you wish to pass around a
/// Document type but you wish to constrain it to success values.
@dynamicMemberLookup
public struct SuccessDocument: EncodableJSONAPIDocument {
public typealias BodyData = Document.BodyData
public var body: Document.Body { return document.body }
private let document: Document
@@ -471,8 +492,50 @@ extension Document {
try container.encode(document)
}
public subscript<T>(dynamicMember path: KeyPath<Document, T>) -> T {
return document[keyPath: path]
/// The JSON API Spec calls this the JSON:API Object. It contains version
/// and metadata information about the API itself.
public var apiDescription: APIDescription {
return document.apiDescription
}
/// Get the document data
///
/// `nil` if the Document is an error response. Otherwise,
/// a structure containing the primary resource, any included
/// resources, metadata, and links.
var data: BodyData? {
return document.body.data
}
/// Quick access to the `data`'s primary resource.
///
/// `nil` if the Document is an error document. Otherwise,
/// the primary resource body, which will contain zero/one, one/many
/// resources dependening on the `PrimaryResourceBody` type.
///
/// See `SingleResourceBody` and `ManyResourceBody`.
var primaryResource: PrimaryResourceBody? {
return document.body.primaryResource
}
/// Quick access to the `data`'s includes.
///
/// `nil` if the Document is an error document. Otherwise,
/// zero or more includes.
var includes: Includes<IncludeType>? {
return document.body.includes
}
/// The metadata for the error or data document or `nil` if
/// no metadata is found.
var meta: MetaType? {
return document.body.meta
}
/// The links for the error or data document or `nil` if
/// no links are found.
var links: LinksType? {
return document.body.links
}
public static func ==(lhs: Document, rhs: SuccessDocument) -> Bool {
@@ -481,7 +544,7 @@ extension Document {
}
}
extension Document.ErrorDocument: Decodable, JSONAPIDocument
extension Document.ErrorDocument: Decodable, CodableJSONAPIDocument
where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
@@ -494,7 +557,7 @@ extension Document.ErrorDocument: Decodable, JSONAPIDocument
}
}
extension Document.SuccessDocument: Decodable, JSONAPIDocument
extension Document.SuccessDocument: Decodable, CodableJSONAPIDocument
where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
+3 -3
View File
@@ -40,11 +40,11 @@ public protocol ResourceBody: Decodable, EncodableResourceBody {}
/// A `ResourceBody` that has the ability to take on more primary
/// resources by appending another similarly typed `ResourceBody`.
public protocol Appendable {
public protocol ResourceBodyAppendable {
func appending(_ other: Self) -> Self
}
public func +<R: Appendable>(_ left: R, right: R) -> R {
public func +<R: ResourceBodyAppendable>(_ left: R, right: R) -> R {
return left.appending(right)
}
@@ -60,7 +60,7 @@ public struct SingleResourceBody<Entity: JSONAPI.OptionalEncodablePrimaryResourc
}
/// A type allowing for a document body containing 0 or more primary resources.
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, Appendable {
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, ResourceBodyAppendable {
public let values: [Entity]
public init(resourceObjects: [Entity]) {
@@ -12,7 +12,7 @@ import Poly
class DocumentTests: XCTestCase {
func test_genericDocFunc() {
func test<Doc: JSONAPIDocument>(_ doc: Doc) {
func test<Doc: CodableJSONAPIDocument>(_ doc: Doc) {
let _ = encoded(value: doc)
XCTAssert(Doc.PrimaryResourceBody.self == NoResourceBody.self)