mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Added Meta-Attribute support and documentation
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user