mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
136 lines
4.2 KiB
Swift
136 lines
4.2 KiB
Swift
//
|
|
// PrimaryResourceBody.swift
|
|
// JSONAPI
|
|
//
|
|
// Created by Mathew Polzin on 11/10/18.
|
|
//
|
|
|
|
public protocol OptionalEncodablePrimaryResource: Equatable, Encodable {}
|
|
|
|
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: EncodablePrimaryResource, OptionalPrimaryResource {}
|
|
|
|
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: Decodable, EncodableResourceBody {}
|
|
|
|
/// A `ResourceBody` that has the ability to take on more primary
|
|
/// resources by appending another similarly typed `ResourceBody`.
|
|
public protocol AppendableResourceBody {
|
|
func appending(_ other: Self) -> Self
|
|
}
|
|
|
|
public func +<R: AppendableResourceBody>(_ left: R, right: R) -> R {
|
|
return left.appending(right)
|
|
}
|
|
|
|
public struct SingleResourceBody<Entity: JSONAPI.OptionalEncodablePrimaryResource>: EncodableResourceBody {
|
|
public let value: Entity
|
|
|
|
public init(resourceObject: Entity) {
|
|
self.value = resourceObject
|
|
}
|
|
}
|
|
|
|
public struct ManyResourceBody<Entity: JSONAPI.EncodablePrimaryResource>: EncodableResourceBody, AppendableResourceBody {
|
|
public let values: [Entity]
|
|
|
|
public init(resourceObjects: [Entity]) {
|
|
values = resourceObjects
|
|
}
|
|
|
|
public func appending(_ other: ManyResourceBody) -> ManyResourceBody {
|
|
return ManyResourceBody(resourceObjects: values + other.values)
|
|
}
|
|
}
|
|
|
|
/// Use NoResourceBody to indicate you expect a JSON API document to not
|
|
/// contain a "data" top-level key.
|
|
public struct NoResourceBody: ResourceBody {
|
|
public static var none: NoResourceBody { return NoResourceBody() }
|
|
}
|
|
|
|
// MARK: Codable
|
|
extension SingleResourceBody {
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
|
|
let anyNil: Any? = nil
|
|
let nilValue = anyNil as? Entity
|
|
guard value != nilValue else {
|
|
try container.encodeNil()
|
|
return
|
|
}
|
|
|
|
try container.encode(value)
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
for value in values {
|
|
try container.encode(value)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
public var description: String {
|
|
return "PrimaryResourceBody(\(String(describing: value)))"
|
|
}
|
|
}
|
|
|
|
extension ManyResourceBody: CustomStringConvertible {
|
|
public var description: String {
|
|
return "PrimaryResourceBody(\(String(describing: values)))"
|
|
}
|
|
}
|