mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
merge w/ master
This commit is contained in:
@@ -7,19 +7,26 @@
|
||||
|
||||
import Poly
|
||||
|
||||
public protocol JSONAPIDocument: Codable, Equatable {
|
||||
associatedtype PrimaryResourceBody: JSONAPI.ResourceBody
|
||||
associatedtype MetaType: JSONAPI.Meta
|
||||
associatedtype LinksType: JSONAPI.Links
|
||||
associatedtype IncludeType: JSONAPI.Include
|
||||
associatedtype APIDescription: APIDescriptionType
|
||||
associatedtype Error: JSONAPIError
|
||||
/// 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 {
|
||||
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
|
||||
typealias Body = Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error>.Body
|
||||
|
||||
var body: Body { get }
|
||||
var body: Body { get }
|
||||
}
|
||||
|
||||
/// A `JSONAPIDocument` supports encoding and decoding of a JSON:API
|
||||
/// compliant Document.
|
||||
public protocol JSONAPIDocument: 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
|
||||
/// a JSON API response.
|
||||
@@ -27,7 +34,7 @@ public protocol JSONAPIDocument: Codable, Equatable {
|
||||
/// API uses snake case, you will want to use
|
||||
/// a conversion such as the one offerred by the
|
||||
/// Foundation JSONEncoder/Decoder: `KeyDecodingStrategy`
|
||||
public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, APIDescription: APIDescriptionType, Error: JSONAPIError>: JSONAPIDocument {
|
||||
public struct Document<PrimaryResourceBody: JSONAPI.EncodableResourceBody, MetaType: JSONAPI.Meta, LinksType: JSONAPI.Links, IncludeType: JSONAPI.Include, APIDescription: APIDescriptionType, Error: JSONAPIError>: EncodableJSONAPIDocument {
|
||||
public typealias Include = IncludeType
|
||||
|
||||
/// The JSON API Spec calls this the JSON:API Object. It contains version
|
||||
@@ -46,7 +53,9 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
|
||||
case data(Data)
|
||||
|
||||
public struct Data: Equatable {
|
||||
/// The document's Primary Resource object(s)
|
||||
public let primary: PrimaryResourceBody
|
||||
/// The document's included objects
|
||||
public let includes: Includes<Include>
|
||||
public let meta: MetaType
|
||||
public let links: LinksType
|
||||
@@ -59,6 +68,8 @@ public struct Document<PrimaryResourceBody: JSONAPI.ResourceBody, MetaType: JSON
|
||||
}
|
||||
}
|
||||
|
||||
/// `true` if the document represents one or more errors. `false` if the
|
||||
/// document represents JSON:API data and/or metadata.
|
||||
public var isError: Bool {
|
||||
guard case .errors = self else { return false }
|
||||
return true
|
||||
@@ -196,7 +207,7 @@ extension Document where IncludeType == NoIncludes, MetaType == NoMetadata, Link
|
||||
}
|
||||
*/
|
||||
|
||||
extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody {
|
||||
extension Document.Body.Data where PrimaryResourceBody: Appendable {
|
||||
public func merging(_ other: Document.Body.Data,
|
||||
combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType,
|
||||
combiningLinksWith linksMerge: (LinksType, LinksType) -> LinksType) -> Document.Body.Data {
|
||||
@@ -207,7 +218,7 @@ extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody {
|
||||
}
|
||||
}
|
||||
|
||||
extension Document.Body.Data where PrimaryResourceBody: AppendableResourceBody, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
extension Document.Body.Data where PrimaryResourceBody: Appendable, MetaType == NoMetadata, LinksType == NoLinks {
|
||||
public func merging(_ other: Document.Body.Data) -> Document.Body.Data {
|
||||
return merging(other,
|
||||
combiningMetaWith: { _, _ in .none },
|
||||
@@ -273,66 +284,6 @@ extension Document {
|
||||
case links
|
||||
case jsonapi
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: RootCodingKeys.self)
|
||||
|
||||
if let noData = NoAPIDescription() as? APIDescription {
|
||||
apiDescription = noData
|
||||
} else {
|
||||
apiDescription = try container.decode(APIDescription.self, forKey: .jsonapi)
|
||||
}
|
||||
|
||||
let errors = try container.decodeIfPresent([Error].self, forKey: .errors)
|
||||
|
||||
let meta: MetaType?
|
||||
if let noMeta = NoMetadata() as? MetaType {
|
||||
meta = noMeta
|
||||
} else {
|
||||
do {
|
||||
meta = try container.decode(MetaType.self, forKey: .meta)
|
||||
} catch {
|
||||
meta = nil
|
||||
}
|
||||
}
|
||||
|
||||
let links: LinksType?
|
||||
if let noLinks = NoLinks() as? LinksType {
|
||||
links = noLinks
|
||||
} else {
|
||||
do {
|
||||
links = try container.decode(LinksType.self, forKey: .links)
|
||||
} catch {
|
||||
links = nil
|
||||
}
|
||||
}
|
||||
|
||||
// If there are errors, there cannot be a body. Return errors and any metadata found.
|
||||
if let errors = errors {
|
||||
body = .errors(errors, meta: meta, links: links)
|
||||
return
|
||||
}
|
||||
|
||||
let data: PrimaryResourceBody
|
||||
if let noData = NoResourceBody() as? PrimaryResourceBody {
|
||||
data = noData
|
||||
} else {
|
||||
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
|
||||
}
|
||||
|
||||
let maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
|
||||
|
||||
// TODO come back to this and make robust
|
||||
|
||||
guard let metaVal = meta else {
|
||||
throw JSONAPIEncodingError.missingOrMalformedMetadata
|
||||
}
|
||||
guard let linksVal = links else {
|
||||
throw JSONAPIEncodingError.missingOrMalformedLinks
|
||||
}
|
||||
|
||||
body = .data(.init(primary: data, includes: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal))
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: RootCodingKeys.self)
|
||||
@@ -377,6 +328,68 @@ extension Document {
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Decodable, JSONAPIDocument where PrimaryResourceBody: ResourceBody, IncludeType: Decodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: RootCodingKeys.self)
|
||||
|
||||
if let noData = NoAPIDescription() as? APIDescription {
|
||||
apiDescription = noData
|
||||
} else {
|
||||
apiDescription = try container.decode(APIDescription.self, forKey: .jsonapi)
|
||||
}
|
||||
|
||||
let errors = try container.decodeIfPresent([Error].self, forKey: .errors)
|
||||
|
||||
let meta: MetaType?
|
||||
if let noMeta = NoMetadata() as? MetaType {
|
||||
meta = noMeta
|
||||
} else {
|
||||
do {
|
||||
meta = try container.decode(MetaType.self, forKey: .meta)
|
||||
} catch {
|
||||
meta = nil
|
||||
}
|
||||
}
|
||||
|
||||
let links: LinksType?
|
||||
if let noLinks = NoLinks() as? LinksType {
|
||||
links = noLinks
|
||||
} else {
|
||||
do {
|
||||
links = try container.decode(LinksType.self, forKey: .links)
|
||||
} catch {
|
||||
links = nil
|
||||
}
|
||||
}
|
||||
|
||||
// If there are errors, there cannot be a body. Return errors and any metadata found.
|
||||
if let errors = errors {
|
||||
body = .errors(errors, meta: meta, links: links)
|
||||
return
|
||||
}
|
||||
|
||||
let data: PrimaryResourceBody
|
||||
if let noData = NoResourceBody() as? PrimaryResourceBody {
|
||||
data = noData
|
||||
} else {
|
||||
data = try container.decode(PrimaryResourceBody.self, forKey: .data)
|
||||
}
|
||||
|
||||
let maybeIncludes = try container.decodeIfPresent(Includes<Include>.self, forKey: .included)
|
||||
|
||||
// TODO come back to this and make robust
|
||||
|
||||
guard let metaVal = meta else {
|
||||
throw JSONAPIEncodingError.missingOrMalformedMetadata
|
||||
}
|
||||
guard let linksVal = links else {
|
||||
throw JSONAPIEncodingError.missingOrMalformedLinks
|
||||
}
|
||||
|
||||
body = .data(.init(primary: data, includes: maybeIncludes ?? Includes<Include>.none, meta: metaVal, links: linksVal))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension Document: CustomStringConvertible {
|
||||
|
||||
@@ -9,6 +9,9 @@ public protocol JSONAPIError: Swift.Error, Equatable, Codable {
|
||||
static var unknown: Self { get }
|
||||
}
|
||||
|
||||
/// `UnknownJSONAPIError` can actually be used in any sitaution
|
||||
/// where you don't know what errors are possible _or_ you just don't
|
||||
/// care what errors might show up.
|
||||
public enum UnknownJSONAPIError: JSONAPIError {
|
||||
case unknownError
|
||||
|
||||
|
||||
@@ -7,9 +7,19 @@
|
||||
|
||||
import Poly
|
||||
|
||||
public typealias Include = JSONPoly
|
||||
public typealias Include = EncodableJSONPoly
|
||||
|
||||
public struct Includes<I: Include>: Codable, Equatable {
|
||||
/// A structure holding zero or more included Resource Objects.
|
||||
/// The resources are accessed by their type using a subscript.
|
||||
///
|
||||
/// If you have
|
||||
///
|
||||
/// `let includes: Includes<Include2<Thing1, Thing2>> = ...`
|
||||
///
|
||||
/// then you can access all `Thing1` included resources with
|
||||
///
|
||||
/// `let includedThings = includes[Thing1.self]`
|
||||
public struct Includes<I: Include>: Encodable, Equatable {
|
||||
public static var none: Includes { return .init(values: []) }
|
||||
|
||||
let values: [I]
|
||||
@@ -17,23 +27,6 @@ public struct Includes<I: Include>: Codable, Equatable {
|
||||
public init(values: [I]) {
|
||||
self.values = values
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
|
||||
// If not parsing includes, no need to loop over them.
|
||||
guard I.self != NoIncludes.self else {
|
||||
values = []
|
||||
return
|
||||
}
|
||||
|
||||
var valueAggregator = [I]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(I.self))
|
||||
}
|
||||
|
||||
values = valueAggregator
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
@@ -52,6 +45,25 @@ public struct Includes<I: Include>: Codable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes: Decodable where I: Decodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
|
||||
// If not parsing includes, no need to loop over them.
|
||||
guard I.self != NoIncludes.self else {
|
||||
values = []
|
||||
return
|
||||
}
|
||||
|
||||
var valueAggregator = [I]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(I.self))
|
||||
}
|
||||
|
||||
values = valueAggregator
|
||||
}
|
||||
}
|
||||
|
||||
extension Includes {
|
||||
public func appending(_ other: Includes<I>) -> Includes {
|
||||
return Includes(values: values + other.values)
|
||||
|
||||
@@ -5,29 +5,53 @@
|
||||
// Created by Mathew Polzin on 11/10/18.
|
||||
//
|
||||
|
||||
public protocol MaybePrimaryResource: Equatable, Codable {}
|
||||
/// This protocol allows for a `SingleResourceBody` to contain a `null`
|
||||
/// data object where `ManyResourceBody` cannot (because an empty
|
||||
/// array should be used for no results).
|
||||
public protocol OptionalEncodablePrimaryResource: Equatable, Encodable {}
|
||||
|
||||
/// A PrimaryResource is a type that can be used in the body of a JSON API
|
||||
/// An `EncodablePrimaryResource` is a `PrimaryResource` that only supports encoding.
|
||||
/// This is actually more restrictave than `PrimaryResource`, which supports both encoding and
|
||||
/// decoding.
|
||||
public protocol EncodablePrimaryResource: OptionalEncodablePrimaryResource {}
|
||||
|
||||
/// This protocol allows for `SingleResourceBody` to contain a `null`
|
||||
/// data object where `ManyResourceBody` cannot (because an empty
|
||||
/// array should be used for no results).
|
||||
public protocol OptionalPrimaryResource: OptionalEncodablePrimaryResource, Decodable {}
|
||||
|
||||
/// A `PrimaryResource` is a type that can be used in the body of a JSON API
|
||||
/// document as the primary resource.
|
||||
public protocol PrimaryResource: MaybePrimaryResource {}
|
||||
public protocol PrimaryResource: EncodablePrimaryResource, OptionalPrimaryResource {}
|
||||
|
||||
extension Optional: MaybePrimaryResource where Wrapped: PrimaryResource {}
|
||||
extension Optional: OptionalEncodablePrimaryResource where Wrapped: EncodablePrimaryResource {}
|
||||
|
||||
extension Optional: OptionalPrimaryResource where Wrapped: PrimaryResource {}
|
||||
|
||||
/// An `EncodableResourceBody` is a `ResourceBody` that only supports being
|
||||
/// encoded. It is actually weaker than `ResourceBody`, which supports both encoding
|
||||
/// and decoding.
|
||||
public protocol EncodableResourceBody: Equatable, Encodable {}
|
||||
|
||||
/// A ResourceBody is a representation of the body of the JSON API Document.
|
||||
/// It can either be one resource (which can be specified as optional or not)
|
||||
/// or it can contain many resources (and array with zero or more entries).
|
||||
public protocol ResourceBody: Codable, Equatable {
|
||||
}
|
||||
public protocol ResourceBody: Decodable, EncodableResourceBody {}
|
||||
|
||||
public protocol AppendableResourceBody: ResourceBody {
|
||||
/// A `ResourceBody` that has the ability to take on more primary
|
||||
/// resources by appending another similarly typed `ResourceBody`.
|
||||
public protocol Appendable {
|
||||
func appending(_ other: Self) -> Self
|
||||
}
|
||||
|
||||
public func +<R: AppendableResourceBody>(_ left: R, right: R) -> R {
|
||||
public func +<R: Appendable>(_ left: R, right: R) -> R {
|
||||
return left.appending(right)
|
||||
}
|
||||
|
||||
public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: ResourceBody {
|
||||
/// A type allowing for a document body containing 1 primary resource.
|
||||
/// If the `Entity` specialization is an `Optional` type, the body can contain
|
||||
/// 0 or 1 primary resources.
|
||||
public struct SingleResourceBody<Entity: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
||||
public let value: Entity
|
||||
|
||||
public init(resourceObject: Entity) {
|
||||
@@ -35,7 +59,8 @@ public struct SingleResourceBody<Entity: JSONAPI.MaybePrimaryResource>: Resource
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManyResourceBody<Entity: JSONAPI.PrimaryResource>: AppendableResourceBody {
|
||||
/// A type allowing for a document body containing 0 or more primary resources.
|
||||
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, Appendable {
|
||||
public let values: [Entity]
|
||||
|
||||
public init(resourceObjects: [Entity]) {
|
||||
@@ -55,19 +80,6 @@ public struct NoResourceBody: ResourceBody {
|
||||
|
||||
// MARK: Codable
|
||||
extension SingleResourceBody {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? Entity {
|
||||
value = val
|
||||
return
|
||||
}
|
||||
|
||||
value = try container.decode(Entity.self)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
@@ -82,16 +94,22 @@ extension SingleResourceBody {
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody {
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
var valueAggregator = [Entity]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(Entity.self))
|
||||
}
|
||||
values = valueAggregator
|
||||
}
|
||||
extension SingleResourceBody: Decodable, ResourceBody where Entity: OptionalPrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let anyNil: Any? = nil
|
||||
if container.decodeNil(),
|
||||
let val = anyNil as? Entity {
|
||||
value = val
|
||||
return
|
||||
}
|
||||
|
||||
value = try container.decode(Entity.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
|
||||
@@ -101,6 +119,17 @@ extension ManyResourceBody {
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: Decodable, ResourceBody where Entity: PrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
var valueAggregator = [Entity]()
|
||||
while !container.isAtEnd {
|
||||
valueAggregator.append(try container.decode(Entity.self))
|
||||
}
|
||||
values = valueAggregator
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
extension SingleResourceBody: CustomStringConvertible {
|
||||
|
||||
@@ -19,6 +19,8 @@ public protocol Meta: Codable, Equatable {
|
||||
// nullable.
|
||||
extension Optional: Meta where Wrapped: Meta {}
|
||||
|
||||
/// Use this type when you want to specify not to encode or decode any metadata
|
||||
/// for a type.
|
||||
public struct NoMetadata: Meta, CustomStringConvertible {
|
||||
public static var none: NoMetadata { return NoMetadata() }
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ public protocol CreatableRawIdType: RawIdType {
|
||||
|
||||
extension String: RawIdType {}
|
||||
|
||||
/// A type that can be used as the `MaybeRawId` for a `ResourceObject` that does not
|
||||
/// have an Id (most likely because it was created by a client and the server will be responsible
|
||||
/// for assigning it an Id).
|
||||
public struct Unidentified: MaybeRawId, CustomStringConvertible {
|
||||
public init() {}
|
||||
|
||||
|
||||
@@ -15,9 +15,10 @@ import Poly
|
||||
/// disparate types under one roof for
|
||||
/// the purposes of JSON API compliant
|
||||
/// encoding or decoding.
|
||||
public typealias JSONPoly = Poly & PrimaryResource
|
||||
public typealias EncodableJSONPoly = Poly & EncodablePrimaryResource
|
||||
|
||||
public typealias PolyWrapped = Codable & Equatable
|
||||
public typealias EncodablePolyWrapped = Encodable & Equatable
|
||||
public typealias PolyWrapped = EncodablePolyWrapped & Decodable
|
||||
|
||||
extension Poly0: PrimaryResource {
|
||||
public init(from decoder: Decoder) throws {
|
||||
@@ -30,28 +31,46 @@ extension Poly0: PrimaryResource {
|
||||
}
|
||||
|
||||
// MARK: - 1 type
|
||||
extension Poly1: PrimaryResource, MaybePrimaryResource where A: PolyWrapped {}
|
||||
extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly1: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped {}
|
||||
|
||||
// MARK: - 2 types
|
||||
extension Poly2: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped {}
|
||||
extension Poly2: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly2: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped {}
|
||||
|
||||
// MARK: - 3 types
|
||||
extension Poly3: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
||||
extension Poly3: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly3: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped {}
|
||||
|
||||
// MARK: - 4 types
|
||||
extension Poly4: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
||||
extension Poly4: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly4: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped {}
|
||||
|
||||
// MARK: - 5 types
|
||||
extension Poly5: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
||||
extension Poly5: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly5: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped {}
|
||||
|
||||
// MARK: - 6 types
|
||||
extension Poly6: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
||||
extension Poly6: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly6: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped {}
|
||||
|
||||
// MARK: - 7 types
|
||||
extension Poly7: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
||||
extension Poly7: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly7: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped {}
|
||||
|
||||
// MARK: - 8 types
|
||||
extension Poly8: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
||||
extension Poly8: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly8: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped {}
|
||||
|
||||
// MARK: - 9 types
|
||||
extension Poly9: PrimaryResource, MaybePrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
|
||||
extension Poly9: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped, B: EncodablePolyWrapped, C: EncodablePolyWrapped, D: EncodablePolyWrapped, E: EncodablePolyWrapped, F: EncodablePolyWrapped, G: EncodablePolyWrapped, H: EncodablePolyWrapped, I: EncodablePolyWrapped {}
|
||||
|
||||
extension Poly9: PrimaryResource, OptionalPrimaryResource where A: PolyWrapped, B: PolyWrapped, C: PolyWrapped, D: PolyWrapped, E: PolyWrapped, F: PolyWrapped, G: PolyWrapped, H: PolyWrapped, I: PolyWrapped {}
|
||||
|
||||
@@ -15,6 +15,16 @@ public protocol Relationships: Codable & Equatable {}
|
||||
/// properties of any types that are JSON encodable.
|
||||
public protocol Attributes: Codable & Equatable {}
|
||||
|
||||
/// CodingKeys must be `CodingKey` and `Equatable` in order
|
||||
/// to support Sparse Fieldsets.
|
||||
public typealias SparsableCodingKey = CodingKey & Equatable
|
||||
|
||||
/// Attributes containing publicly accessible and `Equatable`
|
||||
/// CodingKeys are required to support Sparse Fieldsets.
|
||||
public protocol SparsableAttributes: Attributes {
|
||||
associatedtype CodingKeys: SparsableCodingKey
|
||||
}
|
||||
|
||||
/// Can be used as `Relationships` Type for Entities that do not
|
||||
/// have any Relationships.
|
||||
public struct NoRelationships: Relationships {
|
||||
@@ -48,7 +58,7 @@ public protocol ResourceObjectProxyDescription: JSONTyped {
|
||||
associatedtype Relationships: Equatable
|
||||
}
|
||||
|
||||
/// An `ResourceObjectDescription` describes a JSON API
|
||||
/// A `ResourceObjectDescription` describes a JSON API
|
||||
/// Resource Object. The Resource Object
|
||||
/// itself is encoded and decoded as an
|
||||
/// `ResourceObject`, which gets specialized on an
|
||||
@@ -396,14 +406,14 @@ extension ResourceObject where MetaType == NoMetadata, LinksType == NoLinks, Ent
|
||||
// MARK: - Pointer for Relationships use
|
||||
public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
|
||||
|
||||
/// An ResourceObject.Pointer is a `ToOneRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference an ResourceObject so that
|
||||
/// other Entities' Relationships can be built up from it.
|
||||
/// A `ResourceObject.Pointer` is a `ToOneRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a `ResourceObject` so that
|
||||
/// other ResourceObjects' Relationships can be built up from it.
|
||||
typealias Pointer = ToOneRelationship<ResourceObject, NoMetadata, NoLinks>
|
||||
|
||||
/// ResourceObject.Pointers is a `ToManyRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a bunch of Entities so
|
||||
/// that other Entities' Relationships can be built up from them.
|
||||
/// `ResourceObject.Pointers` is a `ToManyRelationship` with no metadata or links.
|
||||
/// This is just a convenient way to reference a bunch of ResourceObjects so
|
||||
/// that other ResourceObjects' Relationships can be built up from them.
|
||||
typealias Pointers = ToManyRelationship<ResourceObject, NoMetadata, NoLinks>
|
||||
|
||||
/// Get a pointer to this resource object that can be used as a
|
||||
@@ -412,6 +422,8 @@ public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
|
||||
return Pointer(resourceObject: self)
|
||||
}
|
||||
|
||||
/// Get a pointer (i.e. `ToOneRelationship`) to this resource
|
||||
/// object with the given metadata and links attached.
|
||||
func pointer<MType: JSONAPI.Meta, LType: JSONAPI.Links>(withMeta meta: MType, links: LType) -> ToOneRelationship<ResourceObject, MType, LType> {
|
||||
return ToOneRelationship(resourceObject: self, meta: meta, links: links)
|
||||
}
|
||||
@@ -419,20 +431,20 @@ public extension ResourceObject where EntityRawIdType: JSONAPI.RawIdType {
|
||||
|
||||
// MARK: - Identifying Unidentified Entities
|
||||
public extension ResourceObject where EntityRawIdType == Unidentified {
|
||||
/// Create a new ResourceObject from this one with a newly created
|
||||
/// Create a new `ResourceObject` from this one with a newly created
|
||||
/// unique Id of the given type.
|
||||
func identified<RawIdType: CreatableRawIdType>(byType: RawIdType.Type) -> ResourceObject<Description, MetaType, LinksType, RawIdType> {
|
||||
return .init(attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
|
||||
/// Create a new ResourceObject from this one with the given Id.
|
||||
/// Create a new `ResourceObject` from this one with the given Id.
|
||||
func identified<RawIdType: JSONAPI.RawIdType>(by id: RawIdType) -> ResourceObject<Description, MetaType, LinksType, RawIdType> {
|
||||
return .init(id: ResourceObject<Description, MetaType, LinksType, RawIdType>.Identifier(rawValue: id), attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
}
|
||||
|
||||
public extension ResourceObject where EntityRawIdType: CreatableRawIdType {
|
||||
/// Create a copy of this ResourceObject with a new unique Id.
|
||||
/// Create a copy of this `ResourceObject` with a new unique Id.
|
||||
func withNewIdentifier() -> ResourceObject {
|
||||
return ResourceObject(attributes: attributes, relationships: relationships, meta: meta, links: links)
|
||||
}
|
||||
@@ -580,62 +592,63 @@ infix operator ~>
|
||||
|
||||
// MARK: - Codable
|
||||
private enum ResourceObjectCodingKeys: String, CodingKey {
|
||||
case type = "type"
|
||||
case id = "id"
|
||||
case attributes = "attributes"
|
||||
case relationships = "relationships"
|
||||
case meta = "meta"
|
||||
case links = "links"
|
||||
case type = "type"
|
||||
case id = "id"
|
||||
case attributes = "attributes"
|
||||
case relationships = "relationships"
|
||||
case meta = "meta"
|
||||
case links = "links"
|
||||
}
|
||||
|
||||
public extension ResourceObject {
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceObjectCodingKeys.self)
|
||||
|
||||
try container.encode(ResourceObject.jsonType, forKey: .type)
|
||||
|
||||
if EntityRawIdType.self != Unidentified.self {
|
||||
try container.encode(id, forKey: .id)
|
||||
}
|
||||
|
||||
if Description.Attributes.self != NoAttributes.self {
|
||||
try container.encode(attributes, forKey: .attributes)
|
||||
}
|
||||
|
||||
if Description.Relationships.self != NoRelationships.self {
|
||||
try container.encode(relationships, forKey: .relationships)
|
||||
}
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: ResourceObjectCodingKeys.self)
|
||||
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
try container.encode(ResourceObject.jsonType, forKey: .type)
|
||||
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(links, forKey: .links)
|
||||
}
|
||||
}
|
||||
if EntityRawIdType.self != Unidentified.self {
|
||||
try container.encode(id, forKey: .id)
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self)
|
||||
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
|
||||
guard ResourceObject.jsonType == type else {
|
||||
throw JSONAPIEncodingError.typeMismatch(expected: Description.jsonType, found: type)
|
||||
}
|
||||
if Description.Attributes.self != NoAttributes.self {
|
||||
let nestedEncoder = container.superEncoder(forKey: .attributes)
|
||||
try attributes.encode(to: nestedEncoder)
|
||||
}
|
||||
|
||||
let maybeUnidentified = Unidentified() as? EntityRawIdType
|
||||
id = try maybeUnidentified.map { ResourceObject.Id(rawValue: $0) } ?? container.decode(ResourceObject.Id.self, forKey: .id)
|
||||
if Description.Relationships.self != NoRelationships.self {
|
||||
try container.encode(relationships, forKey: .relationships)
|
||||
}
|
||||
|
||||
attributes = try (NoAttributes() as? Description.Attributes) ??
|
||||
container.decode(Description.Attributes.self, forKey: .attributes)
|
||||
if MetaType.self != NoMetadata.self {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
|
||||
relationships = try (NoRelationships() as? Description.Relationships)
|
||||
?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships)
|
||||
?? Description.Relationships(from: EmptyObjectDecoder())
|
||||
if LinksType.self != NoLinks.self {
|
||||
try container.encode(links, forKey: .links)
|
||||
}
|
||||
}
|
||||
|
||||
meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta)
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceObjectCodingKeys.self)
|
||||
|
||||
links = try (NoLinks() as? LinksType) ?? container.decode(LinksType.self, forKey: .links)
|
||||
}
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
|
||||
guard ResourceObject.jsonType == type else {
|
||||
throw JSONAPIEncodingError.typeMismatch(expected: Description.jsonType, found: type)
|
||||
}
|
||||
|
||||
let maybeUnidentified = Unidentified() as? EntityRawIdType
|
||||
id = try maybeUnidentified.map { ResourceObject.Id(rawValue: $0) } ?? container.decode(ResourceObject.Id.self, forKey: .id)
|
||||
|
||||
attributes = try (NoAttributes() as? Description.Attributes) ??
|
||||
container.decode(Description.Attributes.self, forKey: .attributes)
|
||||
|
||||
relationships = try (NoRelationships() as? Description.Relationships)
|
||||
?? container.decodeIfPresent(Description.Relationships.self, forKey: .relationships)
|
||||
?? Description.Relationships(from: EmptyObjectDecoder())
|
||||
|
||||
meta = try (NoMetadata() as? MetaType) ?? container.decode(MetaType.self, forKey: .meta)
|
||||
|
||||
links = try (NoLinks() as? LinksType) ?? container.decode(LinksType.self, forKey: .links)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,26 @@
|
||||
|
||||
/// A Transformer simply defines a static function that transforms a value.
|
||||
public protocol Transformer {
|
||||
associatedtype From
|
||||
associatedtype To
|
||||
associatedtype From
|
||||
associatedtype To
|
||||
|
||||
static func transform(_ value: From) throws -> To
|
||||
/// Turn value of type `From` into a value of type `To` or
|
||||
/// throw an error on failure.
|
||||
static func transform(_ value: From) throws -> To
|
||||
}
|
||||
|
||||
/// ReversibleTransformers define a function that reverses the transform
|
||||
/// operation.
|
||||
public protocol ReversibleTransformer: Transformer {
|
||||
static func reverse(_ value: To) throws -> From
|
||||
/// Turn a value of type `To` into a value of type `From` or
|
||||
/// throw an error on failure.
|
||||
static func reverse(_ value: To) throws -> From
|
||||
}
|
||||
|
||||
/// The IdentityTransformer does not perform any transformation on a value.
|
||||
public enum IdentityTransformer<T>: ReversibleTransformer {
|
||||
public static func transform(_ value: T) throws -> T { return value }
|
||||
public static func reverse(_ value: T) throws -> T { return value }
|
||||
public static func transform(_ value: T) throws -> T { return value }
|
||||
public static func reverse(_ value: T) throws -> T { return value }
|
||||
}
|
||||
|
||||
// MARK: - Validator
|
||||
@@ -37,15 +41,16 @@ public protocol Validator: ReversibleTransformer where From == To {
|
||||
}
|
||||
|
||||
extension Validator {
|
||||
public static func reverse(_ value: To) throws -> To {
|
||||
let _ = try transform(value)
|
||||
return value
|
||||
}
|
||||
public static func reverse(_ value: To) throws -> To {
|
||||
let _ = try transform(value)
|
||||
return value
|
||||
}
|
||||
|
||||
/// Validate the given value and then return it if valid.
|
||||
/// throws if invalid.
|
||||
public static func validate(_ value: To) throws -> To {
|
||||
let _ = try transform(value)
|
||||
return value
|
||||
}
|
||||
/// throws an erro if invalid.
|
||||
/// - returns: The same value passed in, if it was valid.
|
||||
public static func validate(_ value: To) throws -> To {
|
||||
let _ = try transform(value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// SparseEncoder.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 8/4/19.
|
||||
//
|
||||
|
||||
class SparseFieldEncoder<SparseKey: CodingKey & Equatable>: Encoder {
|
||||
private let wrappedEncoder: Encoder
|
||||
private let allowedKeys: [SparseKey]
|
||||
|
||||
public var codingPath: [CodingKey] {
|
||||
return wrappedEncoder.codingPath
|
||||
}
|
||||
|
||||
public var userInfo: [CodingUserInfoKey : Any] {
|
||||
return wrappedEncoder.userInfo
|
||||
}
|
||||
|
||||
public init(wrapping encoder: Encoder, encoding allowedKeys: [SparseKey]) {
|
||||
wrappedEncoder = encoder
|
||||
self.allowedKeys = allowedKeys
|
||||
}
|
||||
|
||||
public func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
|
||||
let container = SparseFieldKeyedEncodingContainer(wrapping: wrappedEncoder.container(keyedBy: type),
|
||||
encoding: allowedKeys)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
public func unkeyedContainer() -> UnkeyedEncodingContainer {
|
||||
return wrappedEncoder.unkeyedContainer()
|
||||
}
|
||||
|
||||
public func singleValueContainer() -> SingleValueEncodingContainer {
|
||||
return wrappedEncoder.singleValueContainer()
|
||||
}
|
||||
}
|
||||
|
||||
struct SparseFieldKeyedEncodingContainer<Key, SparseKey>: KeyedEncodingContainerProtocol where SparseKey: CodingKey, SparseKey: Equatable, Key: CodingKey {
|
||||
private var wrappedContainer: KeyedEncodingContainer<Key>
|
||||
private let allowedKeys: [SparseKey]
|
||||
|
||||
public var codingPath: [CodingKey] {
|
||||
return wrappedContainer.codingPath
|
||||
}
|
||||
|
||||
public init(wrapping container: KeyedEncodingContainer<Key>, encoding allowedKeys: [SparseKey]) {
|
||||
wrappedContainer = container
|
||||
self.allowedKeys = allowedKeys
|
||||
}
|
||||
|
||||
/// Ask the container whether the given key should be encoded.
|
||||
public func shouldAllow(key: Key) -> Bool {
|
||||
if let key = key as? SparseKey {
|
||||
return allowedKeys.contains(key)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public mutating func encodeNil(forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encodeNil(forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Bool, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: String, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Double, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Float, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Int, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Int8, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Int16, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Int32, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: Int64, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: UInt, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: UInt8, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: UInt16, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: UInt32, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode(_ value: UInt64, forKey key: Key) throws {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
|
||||
guard shouldAllow(key: key) else { return }
|
||||
|
||||
try wrappedContainer.encode(value, forKey: key)
|
||||
}
|
||||
|
||||
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type,
|
||||
forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
|
||||
guard shouldAllow(key: key) else {
|
||||
return KeyedEncodingContainer(
|
||||
// TODO: not needed by JSONAPI library, but for completeness could
|
||||
// add an EmptyObjectEncoder that can be returned here so that
|
||||
// at least nothing gets encoded within the nested container
|
||||
SparseFieldKeyedEncodingContainer<NestedKey, SparseKey>(wrapping: wrappedContainer.nestedContainer(keyedBy: keyType,
|
||||
forKey: key),
|
||||
encoding: [])
|
||||
)
|
||||
}
|
||||
|
||||
return KeyedEncodingContainer(
|
||||
SparseFieldKeyedEncodingContainer<NestedKey, SparseKey>(wrapping: wrappedContainer.nestedContainer(keyedBy: keyType,
|
||||
forKey: key),
|
||||
encoding: allowedKeys)
|
||||
)
|
||||
}
|
||||
|
||||
public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
||||
guard shouldAllow(key: key) else {
|
||||
// TODO: not needed by JSONAPI library, but for completeness could
|
||||
// add an EmptyObjectEncoder that can be returned here so that
|
||||
// at least nothing gets encoded within the nested container
|
||||
return wrappedContainer.nestedUnkeyedContainer(forKey: key)
|
||||
}
|
||||
|
||||
return wrappedContainer.nestedUnkeyedContainer(forKey: key)
|
||||
}
|
||||
|
||||
public mutating func superEncoder() -> Encoder {
|
||||
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(),
|
||||
encoding: allowedKeys)
|
||||
}
|
||||
|
||||
public mutating func superEncoder(forKey key: Key) -> Encoder {
|
||||
guard shouldAllow(key: key) else {
|
||||
// NOTE: We are creating a sparse field encoder with no allowed keys
|
||||
// here because the given key should not be allowed.
|
||||
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key),
|
||||
encoding: [SparseKey]())
|
||||
}
|
||||
|
||||
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key),
|
||||
encoding: allowedKeys)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// SparseFieldset.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 8/4/19.
|
||||
//
|
||||
|
||||
/// A SparseFieldset represents an `Encodable` subset of the fields
|
||||
/// a `ResourceObject` would normally encode. Currently, you can
|
||||
/// only apply sparse fieldset's to `ResourceObject.Attributes`.
|
||||
public struct SparseFieldset<
|
||||
Description: JSONAPI.ResourceObjectDescription,
|
||||
MetaType: JSONAPI.Meta,
|
||||
LinksType: JSONAPI.Links,
|
||||
EntityRawIdType: JSONAPI.MaybeRawId
|
||||
>: EncodablePrimaryResource where Description.Attributes: SparsableAttributes {
|
||||
|
||||
/// The `ResourceObject` type this `SparseFieldset` is capable of modifying.
|
||||
public typealias Resource = JSONAPI.ResourceObject<Description, MetaType, LinksType, EntityRawIdType>
|
||||
|
||||
public let resourceObject: Resource
|
||||
public let fields: [Description.Attributes.CodingKeys]
|
||||
|
||||
public init(_ resourceObject: Resource, fields: [Description.Attributes.CodingKeys]) {
|
||||
self.resourceObject = resourceObject
|
||||
self.fields = fields
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
let sparseEncoder = SparseFieldEncoder(wrapping: encoder,
|
||||
encoding: fields)
|
||||
|
||||
try resourceObject.encode(to: sparseEncoder)
|
||||
}
|
||||
}
|
||||
|
||||
public extension ResourceObject where Description.Attributes: SparsableAttributes {
|
||||
|
||||
/// Get a Sparse Fieldset of this `ResourceObject` that can be encoded
|
||||
/// as a `SparsePrimaryResource`.
|
||||
func sparse(with fields: [Description.Attributes.CodingKeys]) -> SparseFieldset<Description, MetaType, LinksType, EntityRawIdType> {
|
||||
return SparseFieldset(self, fields: fields)
|
||||
}
|
||||
}
|
||||
|
||||
public extension ResourceObject where Description.Attributes: SparsableAttributes {
|
||||
|
||||
/// The `SparseFieldset` type for this `ResourceObject`
|
||||
typealias SparseType = SparseFieldset<Description, MetaType, LinksType, EntityRawIdType>
|
||||
}
|
||||
Reference in New Issue
Block a user