Skip to content

Commit 1d6e5d3

Browse files
committed
Added Meta-Attribute support and documentation
1 parent d68404d commit 1d6e5d3

4 files changed

Lines changed: 92 additions & 0 deletions

File tree

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ See the JSON API Spec here: https://jsonapi.org/format/
5252
- [`JSONAPI.RawIdType`](#jsonapirawidtype)
5353
- [Custom Attribute or Relationship Key Mapping](#custom-attribute-or-relationship-key-mapping)
5454
- [Custom Attribute Encode/Decode](#custom-attribute-encodedecode)
55+
- [Meta-attributes](#meta-attributes)
5556
- [Example](#example)
5657
- [Preamble (Setup shared by server and client)](#preamble-setup-shared-by-server-and-client)
5758
- [Server Pseudo-example](#server-pseudo-example)
@@ -526,6 +527,51 @@ extension EntityDescription1.Attributes {
526527
}
527528
```
528529

530+
### Meta-attributes
531+
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.
532+
533+
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.
534+
535+
```
536+
enum UserDescription: EntityDescription {
537+
public static var jsonType: String { return "users" }
538+
539+
struct Attributes: JSONAPI.Attributes {
540+
var createdAt: (User) -> Date {
541+
return { user in
542+
let components = user.id.rawValue.split(separator: "-")
543+
544+
guard components.count == 2 else {
545+
assertionFailure()
546+
return Date()
547+
}
548+
549+
let timestamp = TimeInterval(components[1])
550+
551+
guard let date = timestamp.map(Date.init(timeIntervalSince1970:)) else {
552+
assertionFailure()
553+
return Date()
554+
}
555+
556+
return date
557+
}
558+
}
559+
}
560+
561+
typealias Relationships = NoRelationships
562+
}
563+
564+
typealias User = JSONAPI.Entity<UserDescription, NoMetadata, NoLinks, String>
565+
```
566+
567+
Given a value `user` of the above entity type, you can access the `createdAt` attribute just like you would any other:
568+
569+
```
570+
let createdAt = user[\.createdAt]
571+
```
572+
573+
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.
574+
529575
## Example
530576
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.
531577

Sources/JSONAPI/Resource/Entity.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,15 @@ public extension EntityProxy {
471471
}
472472
}
473473

474+
// MARK: Meta-Attribute Access
475+
public extension EntityProxy {
476+
/// Access an attribute requiring a transformation on the RawValue _and_
477+
/// a secondary transformation on this entity (self).
478+
subscript<T>(_ path: KeyPath<Description.Attributes, (Self) -> T>) -> T {
479+
return attributes[keyPath: path](self)
480+
}
481+
}
482+
474483
// MARK: Relationship Access
475484
public extension EntityProxy {
476485
/// Access to an Id of a `ToOneRelationship`.

Tests/JSONAPITests/Entity/EntityTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,26 @@ extension EntityTests {
609609
}
610610
}
611611

612+
// MARK: With a Meta Attribute
613+
614+
extension EntityTests {
615+
func test_MetaEntityAccessWorks() {
616+
let entity1 = TestEntityWithMetaAttribute(id: "even",
617+
attributes: .init(),
618+
relationships: .none,
619+
meta: .none,
620+
links: .none)
621+
let entity2 = TestEntityWithMetaAttribute(id: "odd",
622+
attributes: .init(),
623+
relationships: .none,
624+
meta: .none,
625+
links: .none)
626+
627+
XCTAssertEqual(entity1[\.metaAttribute], true)
628+
XCTAssertEqual(entity2[\.metaAttribute], false)
629+
}
630+
}
631+
612632
// MARK: - Test Types
613633
extension EntityTests {
614634

@@ -790,6 +810,22 @@ extension EntityTests {
790810

791811
typealias UnidentifiedTestEntityWithMetaAndLinks = NewEntity<UnidentifiedTestEntityType, TestEntityMeta, TestEntityLinks>
792812

813+
enum TestEntityWithMetaAttributeDescription: EntityDescription {
814+
public static var jsonType: String { return "meta_attribute_entity" }
815+
816+
struct Attributes: JSONAPI.Attributes {
817+
var metaAttribute: (TestEntityWithMetaAttribute) -> Bool {
818+
return { entity in
819+
(entity.id.rawValue.count % 2) == 0
820+
}
821+
}
822+
}
823+
824+
typealias Relationships = NoRelationships
825+
}
826+
827+
typealias TestEntityWithMetaAttribute = BasicEntity<TestEntityWithMetaAttributeDescription>
828+
793829
enum IntToString: Transformer {
794830
public static func transform(_ from: Int) -> String {
795831
return String(from)

Tests/JSONAPITests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ extension EntityTests {
228228
("test_IntOver10_success", test_IntOver10_success),
229229
("test_IntToString", test_IntToString),
230230
("test_IntToString_encode", test_IntToString_encode),
231+
("test_MetaEntityAccessWorks", test_MetaEntityAccessWorks),
231232
("test_NonNullOptionalNullableAttribute", test_NonNullOptionalNullableAttribute),
232233
("test_NonNullOptionalNullableAttribute_encode", test_NonNullOptionalNullableAttribute_encode),
233234
("test_nullableRelationshipIsNull", test_nullableRelationshipIsNull),

0 commit comments

Comments
 (0)