Added an APIDescription type to Document that supports the JSON API Spec's JSON:API Object.

This commit is contained in:
Mathew Polzin
2018-12-02 21:35:09 -08:00
parent 482174f5b4
commit e4eb7816d7
7 changed files with 1009 additions and 32 deletions
@@ -11,7 +11,7 @@ Please enjoy these examples, but allow me the forced casting and the lack of err
// MARK: - Create a request or response body with one Dog in it
let dogFromCode = try! Dog(name: "Buddy", owner: nil)
typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoJSONAPIDescription, UnknownJSONAPIError>
typealias SingleDogDocument = JSONAPI.Document<SingleResourceBody<Dog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
let singleDogDocument = SingleDogDocument(body: SingleResourceBody(entity: dogFromCode))
@@ -27,7 +27,7 @@ let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner
let houses = [House(), House()]
let people = try! [Person(id: personIds[0], name: ["Gary", "Doe"], favoriteColor: "Orange-Red", friends: [], dogs: [dogs[0], dogs[1]], home: houses[0]), Person(id: personIds[1], name: ["Elise", "Joy"], favoriteColor: "Red", friends: [], dogs: [dogs[2]], home: houses[1])]
typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoJSONAPIDescription, UnknownJSONAPIError>
typealias BatchPeopleDocument = JSONAPI.Document<ManyResourceBody<Person>, NoMetadata, NoLinks, Include2<Dog, House>, NoAPIDescription, UnknownJSONAPIError>
let includes = dogs.map { BatchPeopleDocument.Include($0) } + houses.map { BatchPeopleDocument.Include($0) }
let batchPeopleDocument = BatchPeopleDocument(body: .init(entities: people), includes: .init(values: includes))
+12 -28
View File
@@ -33,39 +33,13 @@ Note that Playground support for importing non-system Frameworks is still a bit
## Project Status
### Decoding
### Encoding/Decoding
#### Document
- [x] `data`
- [x] `included`
- [x] `errors`
- [x] `meta`
- [ ] `jsonapi`
- [x] `links`
#### Resource Object
- [x] `id`
- [x] `type`
- [x] `attributes`
- [x] `relationships`
- [ ] `links`
- [ ] `meta`
#### Relationship Object
- [x] `data`
- [ ] `links`
- [ ] `meta`
#### Links Object
- [x] `href`
- [x] `meta`
### Encoding
#### Document
- [x] `data`
- [x] `included`
- [x] `errors`
- [x] `meta`
- [ ] `jsonapi`
- [x] `jsonapi`
- [x] `links`
#### Resource Object
@@ -346,6 +320,16 @@ The fourth generic type of a `JSONAPIDocument` is an `Include`. This type contro
To specify that we expect friends of a person to be included in the above example `JSONAPIDocument`, we would use `Include1<Person>` instead of `NoIncludes`.
#### `APIDescriptionType`
The fifth generic type of a `JSONAPIDocument` is an `APIDescription`. The type represents the "JSON:API Object" described by the **SPEC**. This type describes the highest version of the **SPEC** supported and can carry additional metadata to describe the API.
You can specify this is not part of the document by using the `NoAPIDescription` type.
You can describe the API by a version with no metadata by using `APIDescription<NoMetadata>`.
You can supply any `JSONAPI.Meta` type as the metadata type of the API description.
#### `Error`
The final generic type of a `JSONAPIDocument` is the `Error`. You should create an error type that can decode all the errors you expect your `JSONAPIDocument` to be able to decode. As prescribed by the **SPEC**, these errors will be found in the root document member `errors`.
@@ -12,7 +12,7 @@ public protocol APIDescriptionType: Codable, Equatable {
/// This is what the JSON API Spec calls the "JSON:API Object"
public struct APIDescription<Meta: JSONAPI.Meta>: APIDescriptionType {
public let version: String?
public let version: String
public let meta: Meta
}
@@ -35,7 +35,8 @@ extension APIDescription {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(String?.self, forKey: .version)
// The spec says that if a version is not specified, it should be assumed to be at least 1.0
version = (try? container.decode(String.self, forKey: .version)) ?? "1.0"
if let metaVal = NoMetadata() as? Meta {
meta = metaVal
@@ -0,0 +1,52 @@
//
// APIDescriptionTests.swift
// JSONAPITests
//
// Created by Mathew Polzin on 12/2/18.
//
import XCTest
import JSONAPI
class APIDescriptionTests: XCTestCase {
func test_empty() {
let description = decoded(type: APIDescription<NoMetadata>.self, data: api_description_empty)
XCTAssertEqual(description.version, "1.0")
}
func test_WithVersion() {
let description = decoded(type: APIDescription<NoMetadata>.self, data: api_description_with_version)
XCTAssertEqual(description.version, "1.5")
}
func test_WithMeta() {
let description = decoded(type: APIDescription<TestMetadata>.self, data: api_description_with_meta)
XCTAssertEqual(description.version, "1.0")
XCTAssertEqual(description.meta.hello, "world")
XCTAssertEqual(description.meta.number, 10)
}
func test_WithVersionAndMeta() {
let description = decoded(type: APIDescription<TestMetadata>.self, data: api_description_with_version_and_meta)
XCTAssertEqual(description.version, "2.0")
XCTAssertEqual(description.meta.hello, "world")
XCTAssertEqual(description.meta.number, 10)
}
func test_failsMissingMeta() {
XCTAssertThrowsError(try JSONDecoder().decode(APIDescription<TestMetadata>.self, from: api_description_with_version))
}
}
// MARK: - Test types
extension APIDescriptionTests {
struct TestMetadata: JSONAPI.Meta {
let hello: String
let number: Int
}
}
@@ -0,0 +1,35 @@
//
// APIDescriptionsStubs.swift
// JSONAPITests
//
// Created by Mathew Polzin on 12/2/18.
//
let api_description_empty = """
{}
""".data(using: .utf8)!
let api_description_with_version = """
{
"version": "1.5"
}
""".data(using: .utf8)!
let api_description_with_meta = """
{
"meta": {
"hello": "world",
"number": 10
}
}
""".data(using: .utf8)!
let api_description_with_version_and_meta = """
{
"version": "2.0",
"meta": {
"hello": "world",
"number": 10
}
}
""".data(using: .utf8)!
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff