Compare commits

...

4 Commits

Author SHA1 Message Date
Mathew Polzin be2530f41e Add parsing invariant structure even though I do not have a solid enough use-case to implement an invariant fully just yet. 2018-12-26 14:19:54 -08:00
Mathew Polzin fc962f9a0d Lift the constraint that Attributes and Relationships are Codable for EntityProxies. 2018-12-24 07:05:35 -08:00
Mathew Polzin 52eb123166 update documentation 2018-12-22 13:57:38 -08:00
Mathew Polzin 4ef147ec45 Update linuxmain 2018-12-22 13:49:10 -08:00
5 changed files with 119 additions and 10 deletions
+17
View File
@@ -273,6 +273,23 @@ public var fullName: Attribute<String> {
}
```
### Copying `Entities`
`Entity` is a value type, so copying is its default behavior. There are two common mutations you might want to make when copying an `Entity`:
1. Assigning a new `Identifier` to the copy of an identified `Entity`.
2. Assigning a new `Identifier` to the copy of an unidentified `Entity`.
The above can be accomplished with code like the following:
```
// use case 1
let person1 = person.withNewIdentifier()
// use case 2
let newlyIdentifiedPerson1 = unidentifiedPerson.identified(byType: String.self)
let newlyIdentifiedPerson2 = unidentifiedPerson.identified(by: "2232")
```
### `JSONAPI.Document`
The entirety of a JSON API request or response is encoded or decoded from- or to a `Document`. As an example, a JSON API response containing one `Person` and no included entities could be decoded as follows:
@@ -0,0 +1,76 @@
//
// ParsingInvariant.swift
// JSONAPI
//
// Created by Mathew Polzin on 12/24/18.
//
/// An Error produced when an invariant fails.
public enum InvariantError: Swift.Error {
case failure(description: String)
}
/// Set a reaction to a particular type of invariant failure.
/// `Warning` and `Quiet` will take a default strategy for handling
/// the invariant and parsing will not fail. `Error` will cause parsing
/// to fail due to the given invariant failing.
public protocol IReaction {
/// Perform the given Invariant strategy.
static func trigger(invariant: InvariantType.Type) throws
}
public enum Invariant {
/// Used to indicate an invariant failure shoud be considered an error.
/// Parsing will fail if an Error invariant fails.
public enum Error: IReaction {
public static func trigger(invariant: InvariantType.Type) throws {
throw InvariantError.failure(description: String(describing: invariant))
}
}
/// Used to indicate an invariant failure should be considered a warning.
/// The invariant description will be printed to the console but parsing
/// will not fail. A default strategy for addressing the invariant will
/// be taken. See the given invariant's documentation for details.
public enum Warning: IReaction {
public static func trigger(invariant: InvariantType.Type) throws {
print("**WARN** \(String(describing: invariant))")
}
}
/// Used to indicate an invariant failures should be considered inconsequential.
/// Parsing will continue and a default strategy for addressing the invariant
/// will be taken. See the given invariant's documentation for details.
public enum Quiet: IReaction {
public static func trigger(invariant: InvariantType.Type) throws {}
}
/// A strategy pairs an invariant type with the desired reaction.
public enum Strategy<IType: InvariantType, IReaction: JSONAPI.IReaction>: IStrategy {}
}
public protocol InvariantType {}
public protocol IStrategy {
associatedtype IType: InvariantType
associatedtype IReaction: JSONAPI.IReaction
}
// TODO: Does this invariant make sense? I can't actually know whether arbitrary
// Id types are "empty" so I think I started down this path without having fully
// thought through it. Perhaps there are still other obvious invariants to add
// though.
public protocol RelationshipNonEmptyIdInvariant: IStrategy where IType == Invariant.Relationships.NonEmptyId {}
extension Invariant {
public enum Relationships {
/// The Empty Id Invariant holds as long as a relationships's Id
/// is not "empty"
public enum NonEmptyId: InvariantType {}
public enum Strategies<NonEmptyId: RelationshipNonEmptyIdInvariant> {}
}
}
// Would be used like `ToOneRelationship<OtherEntity, Invariant.Relationships.Strategies<Invariant.Strategy<Invariant.Relationships.NonEmptyId, Invariant.Error>>>`
+10 -6
View File
@@ -32,21 +32,25 @@ public protocol JSONTyped {
static var type: String { get }
}
/// An `EntityProxyDescription` is an `EntityDescription`
/// without Codable conformance.
public protocol EntityProxyDescription: JSONTyped {
associatedtype Attributes: Equatable
associatedtype Relationships: Equatable
}
/// 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: JSONTyped {
associatedtype Attributes: JSONAPI.Attributes
associatedtype Relationships: JSONAPI.Relationships
}
public protocol EntityDescription: EntityProxyDescription where Attributes: JSONAPI.Attributes, Relationships: JSONAPI.Relationships {}
/// 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, JSONTyped {
associatedtype Description: EntityDescription
associatedtype Description: EntityProxyDescription
associatedtype EntityRawIdType: JSONAPI.MaybeRawId
typealias Id = JSONAPI.Id<EntityRawIdType, Self>
@@ -75,7 +79,7 @@ extension EntityProxy {
/// EntityType is the protocol that Entity conforms to. This
/// protocol lets other types accept any Entity as a generic
/// specialization.
public protocol EntityType: EntityProxy, PrimaryResource {
public protocol EntityType: EntityProxy, PrimaryResource where Description: EntityDescription {
associatedtype Meta: JSONAPI.Meta
associatedtype Links: JSONAPI.Links
}
+11 -4
View File
@@ -23,6 +23,7 @@ public class PolyProxyTests: XCTestCase {
XCTAssertEqual(polyUserA[\.name], "Ken Moore")
XCTAssertEqual(polyUserA.id, "1")
XCTAssertEqual(polyUserA.relationships, .none)
XCTAssertEqual(polyUserA[\.x], .init(x: "y"))
}
func test_UserAAndBEncodeEquality() {
@@ -57,6 +58,7 @@ public class PolyProxyTests: XCTestCase {
XCTAssertEqual(polyUserB[\.name], "Ken Less")
XCTAssertEqual(polyUserB.id, "2")
XCTAssertEqual(polyUserB.relationships, .none)
XCTAssertEqual(polyUserB[\.x], .init(x: "y"))
}
}
@@ -111,9 +113,9 @@ extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == Po
public var attributes: SharedUserDescription.Attributes {
switch self {
case .a(let a):
return .init(name: .init(value: "\(a[\.firstName]) \(a[\.lastName])"))
return .init(name: .init(value: "\(a[\.firstName]) \(a[\.lastName])"), x: .init(x: "y"))
case .b(let b):
return .init(name: .init(value: b[\.name].joined(separator: " ")))
return .init(name: .init(value: b[\.name].joined(separator: " ")), x: .init(x: "y"))
}
}
@@ -121,14 +123,19 @@ extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == Po
return .none
}
public enum SharedUserDescription: EntityDescription {
public enum SharedUserDescription: EntityProxyDescription {
public static var type: String { return A.type }
public struct Attributes: JSONAPI.Attributes {
public struct Attributes: Equatable {
let name: Attribute<String>
let x: SomeRandomThingThatIsNotCodable
}
public typealias Relationships = NoRelationships
public struct SomeRandomThingThatIsNotCodable: Equatable {
let x: String
}
}
public typealias Description = SharedUserDescription
+5
View File
@@ -15,6 +15,7 @@ extension AttributeTests {
static let __allTests = [
("test_AttributeIsTransformedAttribute", test_AttributeIsTransformedAttribute),
("test_AttributeNonThrowingConstructor", test_AttributeNonThrowingConstructor),
("test_EncodedPrimitives", test_EncodedPrimitives),
("test_NullableIsEqualToNonNullableIfNotNil", test_NullableIsEqualToNonNullableIfNotNil),
("test_NullableIsNullIfNil", test_NullableIsNullIfNil),
("test_TransformedAttributeNoThrow", test_TransformedAttributeNoThrow),
@@ -181,6 +182,9 @@ extension EntityCheckTests {
extension EntityTests {
static let __allTests = [
("test_copyIdentifiedByType", test_copyIdentifiedByType),
("test_copyIdentifiedByValue", test_copyIdentifiedByValue),
("test_copyWithNewId", test_copyWithNewId),
("test_entityAllAttribute", test_entityAllAttribute),
("test_entityAllAttribute_encode", test_entityAllAttribute_encode),
("test_entityBrokenNullableOmittedAttribute", test_entityBrokenNullableOmittedAttribute),
@@ -235,6 +239,7 @@ extension EntityTests {
("test_toMany_relationship_operator_access", test_toMany_relationship_operator_access),
("test_UnidentifiedEntity", test_UnidentifiedEntity),
("test_UnidentifiedEntity_encode", test_UnidentifiedEntity_encode),
("test_unidentifiedEntityAttributeAccess", test_unidentifiedEntityAttributeAccess),
("test_UnidentifiedEntityWithAttributes", test_UnidentifiedEntityWithAttributes),
("test_UnidentifiedEntityWithAttributes_encode", test_UnidentifiedEntityWithAttributes_encode),
("test_UnidentifiedEntityWithAttributesAndLinks", test_UnidentifiedEntityWithAttributesAndLinks),