Added Meta-Attribute support and documentation

This commit is contained in:
Mathew Polzin
2019-01-02 22:49:38 -08:00
parent d68404db36
commit 1d6e5d3810
4 changed files with 92 additions and 0 deletions
+46
View File
@@ -52,6 +52,7 @@ See the JSON API Spec here: https://jsonapi.org/format/
- [`JSONAPI.RawIdType`](#jsonapirawidtype)
- [Custom Attribute or Relationship Key Mapping](#custom-attribute-or-relationship-key-mapping)
- [Custom Attribute Encode/Decode](#custom-attribute-encodedecode)
- [Meta-attributes](#meta-attributes)
- [Example](#example)
- [Preamble (Setup shared by server and client)](#preamble-setup-shared-by-server-and-client)
- [Server Pseudo-example](#server-pseudo-example)
@@ -526,6 +527,51 @@ extension EntityDescription1.Attributes {
}
```
### Meta-attributes
This advanced feature may not ever be useful, but if you find yourself in the situation of dealing with an API that does not 100% follow the **SPEC** then you might find meta-attributes are just the thing to make your entities more natural to work with.
Suppose, for example, you are presented with the unfortunate situation where a piece of information you need is only available as part of the `Id` of an entity. Perhaps a user's `Id` is formatted "{integer}-{createdAt}" where "createdAt" is the unix timestamp when the user account was created. The following `UserDescription` will expose what you need as an attribute. Realistically, this code is still terrible for its error handling. Using a `Result` type and/or invariants would clean things up substantially.
```
enum UserDescription: EntityDescription {
public static var jsonType: String { return "users" }
struct Attributes: JSONAPI.Attributes {
var createdAt: (User) -> Date {
return { user in
let components = user.id.rawValue.split(separator: "-")
guard components.count == 2 else {
assertionFailure()
return Date()
}
let timestamp = TimeInterval(components[1])
guard let date = timestamp.map(Date.init(timeIntervalSince1970:)) else {
assertionFailure()
return Date()
}
return date
}
}
}
typealias Relationships = NoRelationships
}
typealias User = JSONAPI.Entity<UserDescription, NoMetadata, NoLinks, String>
```
Given a value `user` of the above entity type, you can access the `createdAt` attribute just like you would any other:
```
let createdAt = user[\.createdAt]
```
This works because `createdAt` is defined in the form: `var {name}: ({Entity}) -> {Value}` where `{Entity}` is the `JSONAPI.Entity` described by the `EntityDescription` containing the meta-attribute.
## Example
The following serves as a sort of pseudo-example. It skips server/client implementation details not related to JSON:API but still gives a more complete picture of what an implementation using this framework might look like. You can play with this example code in the Playground provided with this repo.
+9
View File
@@ -471,6 +471,15 @@ public extension EntityProxy {
}
}
// MARK: Meta-Attribute Access
public extension EntityProxy {
/// Access an attribute requiring a transformation on the RawValue _and_
/// a secondary transformation on this entity (self).
subscript<T>(_ path: KeyPath<Description.Attributes, (Self) -> T>) -> T {
return attributes[keyPath: path](self)
}
}
// MARK: Relationship Access
public extension EntityProxy {
/// Access to an Id of a `ToOneRelationship`.
@@ -609,6 +609,26 @@ extension EntityTests {
}
}
// MARK: With a Meta Attribute
extension EntityTests {
func test_MetaEntityAccessWorks() {
let entity1 = TestEntityWithMetaAttribute(id: "even",
attributes: .init(),
relationships: .none,
meta: .none,
links: .none)
let entity2 = TestEntityWithMetaAttribute(id: "odd",
attributes: .init(),
relationships: .none,
meta: .none,
links: .none)
XCTAssertEqual(entity1[\.metaAttribute], true)
XCTAssertEqual(entity2[\.metaAttribute], false)
}
}
// MARK: - Test Types
extension EntityTests {
@@ -790,6 +810,22 @@ extension EntityTests {
typealias UnidentifiedTestEntityWithMetaAndLinks = NewEntity<UnidentifiedTestEntityType, TestEntityMeta, TestEntityLinks>
enum TestEntityWithMetaAttributeDescription: EntityDescription {
public static var jsonType: String { return "meta_attribute_entity" }
struct Attributes: JSONAPI.Attributes {
var metaAttribute: (TestEntityWithMetaAttribute) -> Bool {
return { entity in
(entity.id.rawValue.count % 2) == 0
}
}
}
typealias Relationships = NoRelationships
}
typealias TestEntityWithMetaAttribute = BasicEntity<TestEntityWithMetaAttributeDescription>
enum IntToString: Transformer {
public static func transform(_ from: Int) -> String {
return String(from)
+1
View File
@@ -228,6 +228,7 @@ extension EntityTests {
("test_IntOver10_success", test_IntOver10_success),
("test_IntToString", test_IntToString),
("test_IntToString_encode", test_IntToString_encode),
("test_MetaEntityAccessWorks", test_MetaEntityAccessWorks),
("test_NonNullOptionalNullableAttribute", test_NonNullOptionalNullableAttribute),
("test_NonNullOptionalNullableAttribute_encode", test_NonNullOptionalNullableAttribute_encode),
("test_nullableRelationshipIsNull", test_nullableRelationshipIsNull),