Compare commits

...

10 Commits

Author SHA1 Message Date
Mathew Polzin c04d3301b6 Add Meta-Relationship access. 2019-01-08 21:23:17 -08:00
Mathew Polzin 5a64bebc99 Merge branch 'master' of github.com:mattpolzin/JSONAPI 2019-01-08 20:13:11 -08:00
Mathew Polzin 790b0fbf52 Change capitalization 2019-01-06 18:20:18 -08:00
Mathew Polzin 539ecc451a Remove swift syntax highlighting from JSON snippet. 2019-01-03 22:26:25 -08:00
Mathew Polzin 7eb1b05eae Add swift syntax highlighting to README. 2019-01-03 22:23:17 -08:00
Mathew Polzin 3408263c2a Add warning note to README 2019-01-03 22:09:43 -08:00
Mathew Polzin 1d6e5d3810 Added Meta-Attribute support and documentation 2019-01-02 22:49:38 -08:00
Mathew Polzin d68404db36 update README to reflect change made in v0.12.0 2019-01-02 19:46:30 -08:00
Mathew Polzin 4f7db98a87 Update playground files to work with v0.12.0 changes 2019-01-02 19:42:32 -08:00
Mathew Polzin 072b081ac3 Breaking - Rename static var type: String to static var jsonType:String to avoid unnecessary conflict with Swift.type(of:) 2019-01-02 19:35:50 -08:00
23 changed files with 283 additions and 109 deletions
@@ -40,7 +40,7 @@ typealias Document<PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: JSONA
// MARK: Entity Definitions
enum AuthorDescription: EntityDescription {
public static var type: String { return "authors" }
public static var jsonType: String { return "authors" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
@@ -52,7 +52,7 @@ enum AuthorDescription: EntityDescription {
typealias Author = JSONEntity<AuthorDescription>
enum ArticleDescription: EntityDescription {
public static var type: String { return "articles" }
public static var jsonType: String { return "articles" }
public struct Attributes: JSONAPI.Attributes {
public let title: Attribute<String>
@@ -65,7 +65,7 @@ struct ToManyRelationshipLinks: JSONAPI.Links {
/// Description of an Author entity.
enum AuthorDescription: EntityDescription {
static var type: String { return "authors" }
static var jsonType: String { return "authors" }
struct Attributes: JSONAPI.Attributes {
let name: Attribute<String>
@@ -80,7 +80,7 @@ typealias Author = JSONAPI.Entity<AuthorDescription, EntityMetadata, EntityLinks
/// Description of an Article entity.
enum ArticleDescription: EntityDescription {
static var type: String { return "articles" }
static var jsonType: String { return "articles" }
struct Attributes: JSONAPI.Attributes {
let title: Attribute<String>
+4 -4
View File
@@ -31,7 +31,7 @@ public typealias ToMany<E: Relatable> = ToManyRelationship<E, NoMetadata, NoLink
// MARK: - A few resource objects (entities)
public enum PersonDescription: EntityDescription {
public static var type: String { return "people" }
public static var jsonType: String { return "people" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<[String]>
@@ -70,7 +70,7 @@ public extension Entity where Description == PersonDescription, MetaType == NoMe
public enum DogDescription: EntityDescription {
public static var type: String { return "dogs" }
public static var jsonType: String { return "dogs" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
@@ -93,7 +93,7 @@ public typealias Dog = ExampleEntity<DogDescription>
public enum AlternativeDogDescription: EntityDescription {
public static var type: String { return "dogs" }
public static var jsonType: String { return "dogs" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
@@ -131,7 +131,7 @@ public extension Entity where Description == DogDescription, MetaType == NoMetad
public enum HouseDescription: EntityDescription {
public static var type: String { return "houses" }
public static var jsonType: String { return "houses" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
-2
View File
@@ -3,7 +3,5 @@
<pages>
<page name='Test Library'/>
<page name='Usage'/>
<page name='Full Document Verbose Generation'/>
<page name='Full Client &amp; Server Example'/>
</pages>
</playground>
+113 -31
View File
@@ -5,6 +5,8 @@ A Swift package for encoding to- and decoding from **JSON API** compliant reques
See the JSON API Spec here: https://jsonapi.org/format/
:warning: Although I find the type-safety of this framework appealing, the Swift compiler currently has enough trouble with it that it can become difficult to reason about errors produced by small typos. Similarly, auto-complete fails to provide reasonable suggestions much of the time. If you get the code right, everything compiles, otherwise it can suck to figure out what is wrong. This is mostly a concern when creating entities in-code (servers and test suites must do this). Writing a client that uses this framework to ingest JSON API Compliant API responses is much less painful. :warning:
## Table of Contents
<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
@@ -52,6 +54,8 @@ 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)
- [Meta-Relationships](#meta-relationships)
- [Example](#example)
- [Preamble (Setup shared by server and client)](#preamble-setup-shared-by-server-and-client)
- [Server Pseudo-example](#server-pseudo-example)
@@ -141,9 +145,9 @@ In this documentation, in order to draw attention to the difference between the
An `EntityDescription` is the `JSONAPI` framework's representation of what the **SPEC** calls a *Resource Object*. You might create the following `EntityDescription` to represent a person in a network of friends:
```
```swift
enum PersonDescription: IdentifiedEntityDescription {
static var type: String { return "people" }
static var jsonType: String { return "people" }
struct Attributes: JSONAPI.Attributes {
let name: Attribute<[String]>
@@ -157,7 +161,7 @@ enum PersonDescription: IdentifiedEntityDescription {
```
The requirements of an `EntityDescription` are:
1. A static `var` "type" that matches the JSON type; The **SPEC** requires every *Resource Object* to have a "type".
1. A static `var` "jsonType" that matches the JSON type; The **SPEC** requires every *Resource Object* to have a "type".
2. A `struct` of `Attributes` **- OR -** `typealias Attributes = NoAttributes`
3. A `struct` of `Relationships` **- OR -** `typealias Relationships = NoRelationships`
@@ -224,14 +228,14 @@ A `RawIdType` is the underlying type that uniquely identifies an `Entity`. This
#### Convenient `typealiases`
Often you can use one `RawIdType` for many if not all of your `Entities`. That means you can save yourself some boilerplate by using `typealias`es like the following:
```
```swift
public typealias Entity<Description: JSONAPI.EntityDescription, Meta: JSONAPI.Meta, Links: JSONAPI.Links> = JSONAPI.Entity<Description, Meta, Links, String>
public typealias NewEntity<Description: JSONAPI.EntityDescription, Meta: JSONAPI.Meta, Links: JSONAPI.Links> = JSONAPI.Entity<Description, Meta, Links, Unidentified>
```
It can also be nice to create a `typealias` for each type of entity you want to work with:
```
```swift
typealias Person = Entity<PersonDescription, NoMetadata, NoLinks>
typealias NewPerson = NewEntity<PersonDescription, NoMetadata, NoLinks>
@@ -246,17 +250,17 @@ There are two types of `Relationships`: `ToOneRelationship` and `ToManyRelations
In addition to identifying entities by Id and type, `Relationships` can contain `Meta` or `Links` that follow the same rules as [`Meta`](#jsonapimeta) and [`Links`](#jsonapilinks) elsewhere in the JSON API Document.
To describe a relationship that may be omitted (i.e. the key is not even present in the JSON object), you make the entire `ToOneRelationship` or `ToManyRelationship` optional. However, this is not recommended because you can also represent optional relationships as nullable which means the key is always present. A `ToManyRelationship` can naturally represent the absence of related values with an empty array, so `ToManyRelationship` does not support nullability at all. A `ToOneRelationship` can be marked as nullable (i.e. the value could be either `null` or a resource identifier) like this:
```
```swift
let nullableRelative: ToOneRelationship<Person?, NoMetadata, NoLinks>
```
An entity that does not have relationships can be described by adding the following to an `EntityDescription`:
```
```swift
typealias Relationships = NoRelationships
```
`Relationship` values boil down to `Ids` of other entities. To access the `Id` of a related `Entity`, you can use the custom `~>` operator with the `KeyPath` of the `Relationship` from which you want the `Id`. The friends of the above `Person` `Entity` can be accessed as follows (type annotations for clarity):
```
```swift
let friendIds: [Person.Identifier] = person ~> \.friends
```
@@ -265,27 +269,27 @@ let friendIds: [Person.Identifier] = person ~> \.friends
The `Attributes` of an `EntityDescription` can contain any JSON encodable/decodable types as long as they are wrapped in an `Attribute`, `ValidatedAttribute`, or `TransformedAttribute` `struct`.
To describe an attribute that may be omitted (i.e. the key might not even be in the JSON object), you make the entire `Attribute` optional:
```
```swift
let optionalAttribute: Attribute<String>?
```
To describe an attribute that is expected to exist but might have a `null` value, you make the value within the `Attribute` optional:
```
```swift
let nullableAttribute: Attribute<String?>
```
An entity that does not have attributes can be described by adding the following to an `EntityDescription`:
```
```swift
typealias Attributes = NoAttributes
```
`Attributes` can be accessed via the `subscript` operator of the `Entity` type as follows:
```
```swift
let favoriteColor: String = person[\.favoriteColor]
```
NOTE: Because of support for computed properties that are not wrapped in `Attribute`, `TransformedAttribute`, or `ValidatedAttribute`, the compiler cannot always infer the type of thing you want back when using subscript attribute access. The following code is ambiguous about whether it should return a `String` or an `Attribute<String>`:
```
```swift
let favoriteColor = person[\.favoriteColor]
```
@@ -294,7 +298,7 @@ let favoriteColor = person[\.favoriteColor]
Sometimes you need to use a type that does not encode or decode itself in the way you need to represent it as a serialized JSON object. For example, the Swift `Foundation` type `Date` can encode/decode itself to `Double` out of the box, but you might want to represent dates as ISO 8601 compliant `String`s instead. The Foundation library `JSONDecoder` has a setting to make this adjustment, but for the sake of an example, you could create a `Transformer`.
A `Transformer` just provides one static function that transforms one type to another. You might define one for an ISO 8601 compliant `Date` like this:
```
```swift
enum ISODateTransformer: Transformer {
public static func transform(_ value: String) throws -> Date {
// parse Date out of input and return
@@ -303,14 +307,14 @@ enum ISODateTransformer: Transformer {
```
Then you define the attribute as a `TransformedAttribute` instead of an `Attribute`:
```
```swift
let date: TransformedAttribute<String, ISODateTransformer>
```
Note that the first generic parameter of `TransformAttribute` is the type you expect to decode from JSON, not the type you want to end up with after transformation.
If you make your `Transformer` a `ReversibleTransformer` then your life will be a bit easier when you construct `TransformedAttributes` because you have access to initializers for both the pre- and post-transformed value types. Continuing with the above example of a `ISODateTransformer`:
```
```swift
extension ISODateTransformer: ReversibleTransformer {
public static func reverse(_ value: Date) throws -> String {
// serialize Date to a String
@@ -329,7 +333,7 @@ You can also creator `Validators` and `ValidatedAttribute`s. A `Validator` is ju
You can add computed properties to your `EntityDescription.Attributes` struct if you would like to expose attributes that are not explicitly represented by the JSON. These computed properties do not have to be wrapped in `Attribute`, `ValidatedAttribute`, or `TransformedAttribute`. This allows computed attributes to be of types that are not `Codable`. Here's an example of how you might take the `Person[\.name]` attribute from the example above and create a `fullName` computed property.
```
```swift
public var fullName: Attribute<String> {
return name.map { $0.joined(separator: " ") }
}
@@ -342,7 +346,7 @@ public var fullName: Attribute<String> {
The above can be accomplished with code like the following:
```
```swift
// use case 1
let person1 = person.withNewIdentifier()
@@ -355,7 +359,7 @@ 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:
```
```swift
let decoder = JSONDecoder()
let responseStructure = JSONAPI.Document<SingleResourceBody<Person>, NoMetadata, NoLinks, NoIncludes, UnknownJSONAPIError>.self
@@ -391,7 +395,7 @@ The second generic type of a `JSONAPIDocument` is a `Meta`. This `Meta` follows
```
You would then create the following `Meta` type:
```
```swift
struct PageMetadata: JSONAPI.Meta {
let total: Int
let limit: Int
@@ -442,7 +446,7 @@ You can specify `NoLinks` if the part of the document being described should not
### `JSONAPI.RawIdType`
If you want to create new `JSONAPI.Entity` values and assign them Ids then you will need to conform at least one type to `CreatableRawIdType`. Doing so is easy; here are two example conformances for `UUID` and `String` (via `UUID`):
```
```swift
extension UUID: CreatableRawIdType {
public static func unique() -> UUID {
return UUID()
@@ -458,9 +462,9 @@ extension String: CreatableRawIdType {
### Custom Attribute or Relationship Key Mapping
There is not anything special going on at the `JSONAPI.Attributes` and `JSONAPI.Relationships` levels, so you can easily provide custom key mappings by taking advantage of `Codable`'s `CodingKeys` pattern. Here are two models that will encode/decode equivalently but offer different naming in your codebase:
```
```swift
public enum EntityDescription1: JSONAPI.EntityDescription {
public static var type: String { return "entity" }
public static var jsonType: String { return "entity" }
public struct Attributes: JSONAPI.Attributes {
public let coolProperty: Attribute<String>
@@ -470,7 +474,7 @@ public enum EntityDescription1: JSONAPI.EntityDescription {
}
public enum EntityDescription2: JSONAPI.EntityDescription {
public static var type: String { return "entity" }
public static var jsonType: String { return "entity" }
public struct Attributes: JSONAPI.Attributes {
public let wholeOtherThing: Attribute<String>
@@ -484,9 +488,9 @@ public enum EntityDescription2: JSONAPI.EntityDescription {
### Custom Attribute Encode/Decode
You can safely provide your own encoding or decoding functions for your Attributes struct if you need to as long as you are careful that your encode operation correctly reverses your decode operation. Although this is generally not necessary, `AttributeType` provides a convenience method to make your decoding a bit less boilerplate ridden. This is what it looks like:
```
```swift
public enum EntityDescription1: JSONAPI.EntityDescription {
public static var type: String { return "entity" }
public static var jsonType: String { return "entity" }
public struct Attributes: JSONAPI.Attributes {
public let property1: Attribute<String>
@@ -526,11 +530,89 @@ 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.
```swift
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:
```swift
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.
### Meta-Relationships
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-relationships are just the thing to make your entities more natural to work with.
Similarly to Meta-Attributes, Meta-Relationships allow you to represent non-compliant relationships as computed relationship properties. In the following example, a relationship is created from some attributes on the JSON model.
```swift
enum UserDescription: EntityDescription {
public static var jsonType: String { return "users" }
struct Attributes: JSONAPI.Attributes {
let friend_id: Attribute<String>
}
struct Relationships: JSONAPI.Relationships {
public var friend: (User) -> User.Identifier {
return { user in
return User.Identifier(rawValue: user[\.friend_id])
}
}
}
}
typealias User = JSONAPI.Entity<UserDescription, NoMetadata, NoLinks, String>
```
Given a value `user` of the above entity type, you can access the `friend` relationship just like you would any other:
```swift
let friendId = user ~> \.friend
```
This works because `friend` is defined in the form: `var {name}: ({Entity}) -> {Identifier}` where `{Entity}` is the `JSONAPI.Entity` described by the `EntityDescription` containing the meta-relationship.
## 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.
### Preamble (Setup shared by server and client)
```
```swift
// We make String a CreatableRawIdType.
var GlobalStringId: Int = 0
extension String: CreatableRawIdType {
@@ -565,7 +647,7 @@ typealias Document<PrimaryResourceBody: JSONAPI.ResourceBody, IncludeType: JSONA
// MARK: Entity Definitions
enum AuthorDescription: EntityDescription {
public static var type: String { return "authors" }
public static var jsonType: String { return "authors" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
@@ -577,7 +659,7 @@ enum AuthorDescription: EntityDescription {
typealias Author = JSONEntity<AuthorDescription>
enum ArticleDescription: EntityDescription {
public static var type: String { return "articles" }
public static var jsonType: String { return "articles" }
public struct Attributes: JSONAPI.Attributes {
public let title: Attribute<String>
@@ -602,7 +684,7 @@ typealias SingleArticleDocumentWithIncludes = Document<SingleResourceBody<Articl
typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoIncludes>
```
### Server Pseudo-example
```
```swift
// Skipping over all the API and database stuff, here's a chunk of code
// that creates a document. Note that this document is the entirety
// of a JSON:API response body.
@@ -662,7 +744,7 @@ print(String(data: otherResponseData, encoding: .utf8)!)
```
### Client Pseudo-example
```
```swift
enum NetworkError: Swift.Error {
case serverError
case quantityMismatch
+32 -6
View File
@@ -37,7 +37,7 @@ extension NoAttributes: CustomStringConvertible {
/// Something that is JSONTyped provides a String representation
/// of its type.
public protocol JSONTyped {
static var type: String { get }
static var jsonType: String { get }
}
/// An `EntityProxyDescription` is an `EntityDescription`
@@ -81,7 +81,7 @@ public protocol EntityProxy: Equatable, JSONTyped {
extension EntityProxy {
/// The JSON API compliant "type" of this `Entity`.
public static var type: String { return Description.type }
public static var jsonType: String { return Description.jsonType }
}
/// EntityType is the protocol that Entity conforms to. This
@@ -136,7 +136,7 @@ extension Entity: Identifiable, IdentifiableEntityType, Relatable where EntityRa
extension Entity: CustomStringConvertible {
public var description: String {
return "Entity<\(Entity.type)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))"
return "Entity<\(Entity.jsonType)>(id: \(String(describing: id)), attributes: \(String(describing: attributes)), relationships: \(String(describing: relationships)))"
}
}
@@ -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`.
@@ -513,6 +522,23 @@ public extension EntityProxy {
}
}
// MARK: Meta-Relationship Access
public extension EntityProxy {
/// Access to an Id of a `ToOneRelationship`.
/// This allows you to write `entity ~> \.other` instead
/// of `entity.relationships.other.id`.
public static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> Identifier>) -> Identifier {
return entity.relationships[keyPath: path](entity)
}
/// Access to all Ids of a `ToManyRelationship`.
/// This allows you to write `entity ~> \.others` instead
/// of `entity.relationships.others.ids`.
public static func ~><Identifier: IdType>(entity: Self, path: KeyPath<Description.Relationships, (Self) -> [Identifier]>) -> [Identifier] {
return entity.relationships[keyPath: path](entity)
}
}
infix operator ~>
// MARK: - Codable
@@ -529,7 +555,7 @@ public extension Entity {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ResourceObjectCodingKeys.self)
try container.encode(Entity.type, forKey: .type)
try container.encode(Entity.jsonType, forKey: .type)
if EntityRawIdType.self != Unidentified.self {
try container.encode(id, forKey: .id)
@@ -558,8 +584,8 @@ public extension Entity {
let type = try container.decode(String.self, forKey: .type)
guard Entity.type == type else {
throw JSONAPIEncodingError.typeMismatch(expected: Description.type, found: type)
guard Entity.jsonType == type else {
throw JSONAPIEncodingError.typeMismatch(expected: Description.jsonType, found: type)
}
let maybeUnidentified = Unidentified() as? EntityRawIdType
+7 -7
View File
@@ -134,7 +134,7 @@ public protocol OptionalRelatable: Identifiable where Identifier == Wrapped.Iden
extension Optional: Identifiable, OptionalRelatable, JSONTyped where Wrapped: JSONAPI.Relatable {
public typealias Identifier = Wrapped.Identifier?
public static var type: String { return Wrapped.type }
public static var jsonType: String { return Wrapped.jsonType }
}
// MARK: Codable
@@ -180,8 +180,8 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Identifiable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.type, found: type)
guard type == Identifiable.jsonType else {
throw JSONAPIEncodingError.typeMismatch(expected: Identifiable.jsonType, found: type)
}
id = Identifiable.Identifier(rawValue: try identifier.decode(Identifiable.Identifier.RawType.self, forKey: .id))
@@ -214,7 +214,7 @@ extension ToOneRelationship: Codable where Identifiable.Identifier: OptionalId {
var identifier = container.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self, forKey: .data)
try identifier.encode(id.rawValue, forKey: .id)
try identifier.encode(Identifiable.type, forKey: .entityType)
try identifier.encode(Identifiable.jsonType, forKey: .entityType)
}
}
@@ -242,8 +242,8 @@ extension ToManyRelationship: Codable {
let type = try identifier.decode(String.self, forKey: .entityType)
guard type == Relatable.type else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.type, found: type)
guard type == Relatable.jsonType else {
throw JSONAPIEncodingError.typeMismatch(expected: Relatable.jsonType, found: type)
}
newIds.append(Relatable.Identifier(rawValue: try identifier.decode(Relatable.Identifier.RawType.self, forKey: .id)))
@@ -268,7 +268,7 @@ extension ToManyRelationship: Codable {
var identifier = identifiers.nestedContainer(keyedBy: ResourceIdentifierCodingKeys.self)
try identifier.encode(id.rawValue, forKey: .id)
try identifier.encode(Relatable.type, forKey: .entityType)
try identifier.encode(Relatable.jsonType, forKey: .entityType)
}
}
}
@@ -38,7 +38,7 @@ class Attribute_FunctorTests: XCTestCase {
// MARK: Test types
extension Attribute_FunctorTests {
enum TestTypeDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public struct Attributes: JSONAPI.Attributes {
let name: Attribute<String>
@@ -46,7 +46,7 @@ class ComputedPropertiesTests: XCTestCase {
// MARK: Test types
extension ComputedPropertiesTests {
public enum TestTypeDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public struct Attributes: JSONAPI.Attributes {
public let name: Attribute<String>
@@ -40,7 +40,7 @@ class CustomAttributesTests: XCTestCase {
// MARK: - Test Types
extension CustomAttributesTests {
enum CustomAttributeEntityDescription: EntityDescription {
public static var type: String { return "test1" }
public static var jsonType: String { return "test1" }
public struct Attributes: JSONAPI.Attributes {
let firstName: Attribute<String>
@@ -58,7 +58,7 @@ extension CustomAttributesTests {
typealias CustomAttributeEntity = BasicEntity<CustomAttributeEntityDescription>
enum CustomKeysEntityDescription: EntityDescription {
public static var type: String { return "test1" }
public static var jsonType: String { return "test1" }
public struct Attributes: JSONAPI.Attributes {
public let firstNameSilly: Attribute<String>
@@ -1094,7 +1094,7 @@ extension DocumentTests {
// MARK: - Test Types
extension DocumentTests {
enum AuthorType: EntityDescription {
static var type: String { return "authors" }
static var jsonType: String { return "authors" }
typealias Attributes = NoAttributes
typealias Relationships = NoRelationships
@@ -1103,7 +1103,7 @@ extension DocumentTests {
typealias Author = BasicEntity<AuthorType>
enum ArticleType: EntityDescription {
static var type: String { return "articles" }
static var jsonType: String { return "articles" }
typealias Attributes = NoAttributes
+78 -12
View File
@@ -609,11 +609,45 @@ extension EntityTests {
}
}
// MARK: With a Meta Attribute
extension EntityTests {
func test_MetaEntityAttributeAccessWorks() {
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: With a Meta Relationship
extension EntityTests {
func test_MetaEntityRelationshipAccessWorks() {
let entity1 = TestEntityWithMetaRelationship(id: "even",
attributes: .none,
relationships: .init(),
meta: .none,
links: .none)
XCTAssertEqual(entity1 ~> \.metaRelationship, "hello")
}
}
// MARK: - Test Types
extension EntityTests {
enum TestEntityType1: EntityDescription {
static var type: String { return "test_entities"}
static var jsonType: String { return "test_entities"}
typealias Attributes = NoAttributes
typealias Relationships = NoRelationships
@@ -622,7 +656,7 @@ extension EntityTests {
typealias TestEntity1 = BasicEntity<TestEntityType1>
enum TestEntityType2: EntityDescription {
static var type: String { return "second_test_entities"}
static var jsonType: String { return "second_test_entities"}
typealias Attributes = NoAttributes
@@ -634,7 +668,7 @@ extension EntityTests {
typealias TestEntity2 = BasicEntity<TestEntityType2>
enum TestEntityType3: EntityDescription {
static var type: String { return "third_test_entities"}
static var jsonType: String { return "third_test_entities"}
typealias Attributes = NoAttributes
@@ -646,7 +680,7 @@ extension EntityTests {
typealias TestEntity3 = BasicEntity<TestEntityType3>
enum TestEntityType4: EntityDescription {
static var type: String { return "fourth_test_entities"}
static var jsonType: String { return "fourth_test_entities"}
struct Relationships: JSONAPI.Relationships {
let other: ToOneRelationship<TestEntity2, NoMetadata, NoLinks>
@@ -668,7 +702,7 @@ extension EntityTests {
typealias TestEntity4WithMetaAndLinks = Entity<TestEntityType4, TestEntityMeta, TestEntityLinks>
enum TestEntityType5: EntityDescription {
static var type: String { return "fifth_test_entities"}
static var jsonType: String { return "fifth_test_entities"}
typealias Relationships = NoRelationships
@@ -680,7 +714,7 @@ extension EntityTests {
typealias TestEntity5 = BasicEntity<TestEntityType5>
enum TestEntityType6: EntityDescription {
static var type: String { return "sixth_test_entities" }
static var jsonType: String { return "sixth_test_entities" }
typealias Relationships = NoRelationships
@@ -694,7 +728,7 @@ extension EntityTests {
typealias TestEntity6 = BasicEntity<TestEntityType6>
enum TestEntityType7: EntityDescription {
static var type: String { return "seventh_test_entities" }
static var jsonType: String { return "seventh_test_entities" }
typealias Relationships = NoRelationships
@@ -707,7 +741,7 @@ extension EntityTests {
typealias TestEntity7 = BasicEntity<TestEntityType7>
enum TestEntityType8: EntityDescription {
static var type: String { return "eighth_test_entities" }
static var jsonType: String { return "eighth_test_entities" }
typealias Relationships = NoRelationships
@@ -725,7 +759,7 @@ extension EntityTests {
typealias TestEntity8 = BasicEntity<TestEntityType8>
enum TestEntityType9: EntityDescription {
public static var type: String { return "ninth_test_entities" }
public static var jsonType: String { return "ninth_test_entities" }
typealias Attributes = NoAttributes
@@ -748,7 +782,7 @@ extension EntityTests {
typealias TestEntity9 = BasicEntity<TestEntityType9>
enum TestEntityType10: EntityDescription {
public static var type: String { return "tenth_test_entities" }
public static var jsonType: String { return "tenth_test_entities" }
typealias Attributes = NoAttributes
@@ -761,7 +795,7 @@ extension EntityTests {
typealias TestEntity10 = BasicEntity<TestEntityType10>
enum TestEntityType11: EntityDescription {
public static var type: String { return "eleventh_test_entities" }
public static var jsonType: String { return "eleventh_test_entities" }
public struct Attributes: JSONAPI.Attributes {
let number: ValidatedAttribute<Int, IntOver10>
@@ -773,7 +807,7 @@ extension EntityTests {
typealias TestEntity11 = BasicEntity<TestEntityType11>
enum UnidentifiedTestEntityType: EntityDescription {
public static var type: String { return "unidentified_test_entities" }
public static var jsonType: String { return "unidentified_test_entities" }
struct Attributes: JSONAPI.Attributes {
let me: Attribute<String>?
@@ -790,6 +824,38 @@ 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 TestEntityWithMetaRelationshipDescription: EntityDescription {
public static var jsonType: String { return "meta_relationship_entity" }
typealias Attributes = NoAttributes
struct Relationships: JSONAPI.Relationships {
var metaRelationship: (TestEntityWithMetaRelationship) -> TestEntity1.Identifier {
return { entity in
return TestEntity1.Identifier(rawValue: "hello")
}
}
}
}
typealias TestEntityWithMetaRelationship = BasicEntity<TestEntityWithMetaRelationshipDescription>
enum IntToString: Transformer {
public static func transform(_ from: Int) -> String {
return String(from)
@@ -198,7 +198,7 @@ extension IncludedTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity1" }
public static var jsonType: String { return "test_entity1" }
public struct Attributes: JSONAPI.Attributes {
let foo: Attribute<String>
@@ -210,7 +210,7 @@ extension IncludedTests {
enum TestEntityType2: EntityDescription {
public static var type: String { return "test_entity2" }
public static var jsonType: String { return "test_entity2" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
@@ -228,7 +228,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity3" }
public static var jsonType: String { return "test_entity3" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
@@ -244,7 +244,7 @@ extension IncludedTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity4" }
public static var jsonType: String { return "test_entity4" }
}
typealias TestEntity4 = BasicEntity<TestEntityType4>
@@ -255,7 +255,7 @@ extension IncludedTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity5" }
public static var jsonType: String { return "test_entity5" }
}
typealias TestEntity5 = BasicEntity<TestEntityType5>
@@ -264,7 +264,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity6" }
public static var jsonType: String { return "test_entity6" }
struct Relationships: JSONAPI.Relationships {
let entity4: ToOneRelationship<TestEntity4, NoMetadata, NoLinks>
@@ -277,7 +277,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity7" }
public static var jsonType: String { return "test_entity7" }
typealias Relationships = NoRelationships
}
@@ -288,7 +288,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity8" }
public static var jsonType: String { return "test_entity8" }
typealias Relationships = NoRelationships
}
@@ -299,7 +299,7 @@ extension IncludedTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity9" }
public static var jsonType: String { return "test_entity9" }
typealias Relationships = NoRelationships
}
@@ -41,7 +41,7 @@ class EntityCheckTests: XCTestCase {
// MARK: - Test types
extension EntityCheckTests {
enum OkDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -50,7 +50,7 @@ extension EntityCheckTests {
public typealias OkEntity = BasicEntity<OkDescription>
enum OtherOkDescription: EntityDescription {
public static var type: String { return "hmm" }
public static var jsonType: String { return "hmm" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -59,7 +59,7 @@ extension EntityCheckTests {
public typealias OtherOkEntity = BasicEntity<OtherOkDescription>
enum EnumAttributesDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public enum Attributes: JSONAPI.Attributes {
case hello
@@ -78,7 +78,7 @@ extension EntityCheckTests {
public typealias EnumAttributesEntity = BasicEntity<EnumAttributesDescription>
enum EnumRelationshipsDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public typealias Attributes = NoAttributes
@@ -97,7 +97,7 @@ extension EntityCheckTests {
public typealias EnumRelationshipsEntity = BasicEntity<EnumRelationshipsDescription>
enum BadAttributeDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public struct Attributes: JSONAPI.Attributes {
let x: Attribute<String>
@@ -110,7 +110,7 @@ extension EntityCheckTests {
public typealias BadAttributeEntity = BasicEntity<BadAttributeDescription>
enum BadRelationshipDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public typealias Attributes = NoAttributes
@@ -123,7 +123,7 @@ extension EntityCheckTests {
public typealias BadRelationshipEntity = BasicEntity<BadRelationshipDescription>
enum OptionalArrayAttributeDescription: EntityDescription {
public static var type: String { return "hello" }
public static var jsonType: String { return "hello" }
public struct Attributes: JSONAPI.Attributes {
let x: Attribute<[String]>
@@ -25,7 +25,7 @@ class Id_LiteralTests: XCTestCase {
// MARK: - Test types
extension Id_LiteralTests {
enum TestDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -28,7 +28,7 @@ class Relationship_LiteralTests: XCTestCase {
// MARK: - Test types
extension Relationship_LiteralTests {
enum TestDescription: EntityDescription {
public static var type: String { return "test" }
public static var jsonType: String { return "test" }
public typealias Attributes = NoAttributes
public typealias Relationships = NoRelationships
@@ -53,7 +53,7 @@ class NonJSONAPIRelatableTests: XCTestCase {
// MARK: - Test Types
extension NonJSONAPIRelatableTests {
enum TestEntityDescription: EntityDescription {
static var type: String { return "test" }
static var jsonType: String { return "test" }
typealias Attributes = NoAttributes
@@ -66,7 +66,7 @@ extension NonJSONAPIRelatableTests {
typealias TestEntity = JSONAPI.Entity<TestEntityDescription, NoMetadata, NoLinks, String>
enum TestEntity2Description: EntityDescription {
static var type: String { return "test" }
static var jsonType: String { return "test" }
typealias Attributes = NoAttributes
@@ -81,7 +81,7 @@ extension NonJSONAPIRelatableTests {
typealias TestEntity2 = JSONAPI.Entity<TestEntity2Description, NoMetadata, NoLinks, String>
struct NonJSONAPIEntity: Relatable, JSONTyped {
static var type: String { return "other" }
static var jsonType: String { return "other" }
typealias Identifier = NonJSONAPIEntity.Id
+4 -4
View File
@@ -11,7 +11,7 @@ import JSONAPI
public class PolyProxyTests: XCTestCase {
func test_generalReasonableness() {
XCTAssertNotEqual(decoded(type: User.self, data: poly_user_stub_1), decoded(type: User.self, data: poly_user_stub_2))
XCTAssertEqual(User.type, "users")
XCTAssertEqual(User.jsonType, "users")
}
func test_UserADecode() {
@@ -65,7 +65,7 @@ public class PolyProxyTests: XCTestCase {
// MARK: - Test types
public extension PolyProxyTests {
public enum UserDescription1: EntityDescription {
public static var type: String { return "users" }
public static var jsonType: String { return "users" }
public struct Attributes: JSONAPI.Attributes {
let firstName: Attribute<String>
@@ -76,7 +76,7 @@ public extension PolyProxyTests {
}
public enum UserDescription2: EntityDescription {
public static var type: String { return "users" }
public static var jsonType: String { return "users" }
public struct Attributes: JSONAPI.Attributes {
let name: Attribute<[String]>
@@ -124,7 +124,7 @@ extension Poly2: EntityProxy, JSONTyped where A == PolyProxyTests.UserA, B == Po
}
public enum SharedUserDescription: EntityProxyDescription {
public static var type: String { return A.type }
public static var jsonType: String { return A.jsonType }
public struct Attributes: Equatable {
let name: Attribute<String>
+9 -9
View File
@@ -572,7 +572,7 @@ extension PolyTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity1" }
public static var jsonType: String { return "test_entity1" }
public struct Attributes: JSONAPI.Attributes {
let foo: Attribute<String>
@@ -584,7 +584,7 @@ extension PolyTests {
enum TestEntityType2: EntityDescription {
public static var type: String { return "test_entity2" }
public static var jsonType: String { return "test_entity2" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
@@ -602,7 +602,7 @@ extension PolyTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity3" }
public static var jsonType: String { return "test_entity3" }
public struct Relationships: JSONAPI.Relationships {
let entity1: ToOneRelationship<TestEntity, NoMetadata, NoLinks>
@@ -618,7 +618,7 @@ extension PolyTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity4" }
public static var jsonType: String { return "test_entity4" }
}
typealias TestEntity4 = BasicEntity<TestEntityType4>
@@ -629,7 +629,7 @@ extension PolyTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity5" }
public static var jsonType: String { return "test_entity5" }
}
typealias TestEntity5 = BasicEntity<TestEntityType5>
@@ -638,7 +638,7 @@ extension PolyTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity6" }
public static var jsonType: String { return "test_entity6" }
struct Relationships: JSONAPI.Relationships {
let entity4: ToOneRelationship<TestEntity4, NoMetadata, NoLinks>
@@ -651,7 +651,7 @@ extension PolyTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity7" }
public static var jsonType: String { return "test_entity7" }
typealias Relationships = NoRelationships
}
@@ -662,7 +662,7 @@ extension PolyTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity8" }
public static var jsonType: String { return "test_entity8" }
typealias Relationships = NoRelationships
}
@@ -673,7 +673,7 @@ extension PolyTests {
typealias Attributes = NoAttributes
public static var type: String { return "test_entity9" }
public static var jsonType: String { return "test_entity9" }
typealias Relationships = NoRelationships
}
@@ -177,7 +177,7 @@ extension RelationshipTests {
typealias Relationships = NoRelationships
public static var type: String { return "test_entity1" }
public static var jsonType: String { return "test_entity1" }
}
typealias TestEntity1 = BasicEntity<TestEntityType1>

Some files were not shown because too many files have changed in this diff Show More