mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Add Sparse Fieldset support for Attributes
This commit is contained in:
@@ -15,6 +15,12 @@ public protocol Relationships: Codable & Equatable {}
|
||||
/// properties of any types that are JSON encodable.
|
||||
public protocol Attributes: Codable & Equatable {}
|
||||
|
||||
/// Attributes containing publicly accessible and `Equatable`
|
||||
/// CodingKeys are required to support Sparse Fieldsets.
|
||||
public protocol SparsableAttributes: Attributes {
|
||||
associatedtype CodingKeys: CodingKey & Equatable
|
||||
}
|
||||
|
||||
/// Can be used as `Relationships` Type for Entities that do not
|
||||
/// have any Relationships.
|
||||
public struct NoRelationships: Relationships {
|
||||
@@ -48,7 +54,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
|
||||
@@ -566,7 +572,8 @@ public extension ResourceObject {
|
||||
}
|
||||
|
||||
if Description.Attributes.self != NoAttributes.self {
|
||||
try container.encode(attributes, forKey: .attributes)
|
||||
let nestedEncoder = container.superEncoder(forKey: .attributes)
|
||||
try attributes.encode(to: nestedEncoder)
|
||||
}
|
||||
|
||||
if Description.Relationships.self != NoRelationships.self {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// SparseField.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 8/4/19.
|
||||
//
|
||||
|
||||
public struct SparseField<
|
||||
Description: JSONAPI.ResourceObjectDescription,
|
||||
MetaType: JSONAPI.Meta,
|
||||
LinksType: JSONAPI.Links,
|
||||
EntityRawIdType: JSONAPI.MaybeRawId
|
||||
>: Encodable where Description.Attributes: SparsableAttributes {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// SparseEncoder.swift
|
||||
//
|
||||
//
|
||||
// Created by Mathew Polzin on 8/4/19.
|
||||
//
|
||||
|
||||
public 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 container(keyedBy type: SparseKey.Type) -> KeyedEncodingContainer<SparseKey> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
public 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
|
||||
}
|
||||
|
||||
private 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(
|
||||
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: Seems like this might not work as expected... maybe need an empty unkeyed container
|
||||
return wrappedContainer.nestedUnkeyedContainer(forKey: key)
|
||||
}
|
||||
|
||||
return wrappedContainer.nestedUnkeyedContainer(forKey: key)
|
||||
}
|
||||
|
||||
public mutating func superEncoder() -> Encoder {
|
||||
return wrappedContainer.superEncoder()
|
||||
}
|
||||
|
||||
public mutating func superEncoder(forKey key: Key) -> Encoder {
|
||||
guard shouldAllow(key: key) else {
|
||||
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key), encoding: [SparseKey]())
|
||||
}
|
||||
|
||||
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key), encoding: allowedKeys)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user