Skip to content

Commit

Permalink
Merge pull request #72 from mattpolzin/change-course-slightly
Browse files Browse the repository at this point in the history
Change course slightly
  • Loading branch information
mattpolzin authored May 30, 2020
2 parents 0c48ddf + e9b9dbc commit 5b0a016
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoInclud
func articleDocument(includeAuthor: Bool) -> Either<SingleArticleDocument, SingleArticleDocumentWithIncludes> {
// Let's pretend all of this is coming from a database:

let authorId = Author.ID(rawValue: "1234")
let authorId = Author.Id(rawValue: "1234")

let article = Article(id: .init(rawValue: "5678"),
attributes: .init(title: .init(value: "JSON:API in Swift"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ enum ArticleDocumentError: String, JSONAPIError, Codable {
typealias SingleArticleDocument = JSONAPI.Document<SingleResourceBody<Article>, DocumentMetadata, SingleArticleDocumentLinks, Include1<Author>, APIDescription<APIDescriptionMetadata>, ArticleDocumentError>

// MARK: - Instantiations
let authorId1 = Author.ID()
let authorId2 = Author.ID()
let authorId3 = Author.ID()
let authorId1 = Author.Id()
let authorId2 = Author.Id()
let authorId3 = Author.Id()

let now = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: now)!
Expand All @@ -155,7 +155,7 @@ let author1Links = EntityLinks(selfLink: .init(url: URL(string: "https://article
meta: .init(expiry: tomorrow)))
let author1 = Author(id: authorId1,
attributes: .init(name: .init(value: "James Kinney")),
relationships: .init(articles: .init(ids: [article.id, Article.ID(), Article.ID()],
relationships: .init(articles: .init(ids: [article.id, Article.Id(), Article.Id()],
meta: .init(pagination: .init(total: 3,
limit: 50,
offset: 0)),
Expand All @@ -167,7 +167,7 @@ let author2Links = EntityLinks(selfLink: .init(url: URL(string: "https://article
meta: .init(expiry: tomorrow)))
let author2 = Author(id: authorId2,
attributes: .init(name: .init(value: "James Kinney")),
relationships: .init(articles: .init(ids: [article.id, Article.ID()],
relationships: .init(articles: .init(ids: [article.id, Article.Id()],
meta: .init(pagination: .init(total: 2,
limit: 50,
offset: 0)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ let singleDogData = try! JSONEncoder().encode(singleDogDocument)
// MARK: - Parse a request or response body with one Dog in it
let dogResponse = try! JSONDecoder().decode(SingleDogDocument.self, from: singleDogData)
let dogFromData = dogResponse.body.primaryResource?.value
let dogOwner: Person.ID? = dogFromData.flatMap { $0 ~> \.owner }
let dogOwner: Person.Id? = dogFromData.flatMap { $0 ~> \.owner }


// MARK: - Parse a request or response body with one Dog in it using an alternative model
typealias AltSingleDogDocument = JSONAPI.Document<SingleResourceBody<AlternativeDog>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, BasicJSONAPIError<String>>
let altDogResponse = try! JSONDecoder().decode(AltSingleDogDocument.self, from: singleDogData)
let altDogFromData = altDogResponse.body.primaryResource?.value
let altDogHuman: Person.ID? = altDogFromData.flatMap { $0 ~> \.human }
let altDogHuman: Person.Id? = altDogFromData.flatMap { $0 ~> \.human }


// MARK: - Create a request or response with multiple people and dogs and houses included
let personIds = [Person.ID(), Person.ID()]
let personIds = [Person.Id(), Person.Id()]
let dogs = try! [Dog(name: "Buddy", owner: personIds[0]), Dog(name: "Joy", owner: personIds[0]), Dog(name: "Travis", owner: personIds[1])]
let houses = [House(attributes: .none, relationships: .none, meta: .none, links: .none), House(attributes: .none, relationships: .none, meta: .none, links: .none)]
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])]
Expand Down
6 changes: 3 additions & 3 deletions JSONAPI.playground/Sources/Entities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public enum PersonDescription: ResourceObjectDescription {
public typealias Person = ExampleEntity<PersonDescription>

public extension ResourceObject where Description == PersonDescription, MetaType == NoMetadata, LinksType == NoLinks, EntityRawIdType == String {
init(id: Person.ID? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws {
self = Person(id: id ?? Person.ID(), attributes: .init(name: .init(value: name), favoriteColor: .init(value: favoriteColor)), relationships: .init(friends: .init(resourceObjects: friends), dogs: .init(resourceObjects: dogs), home: .init(resourceObject: home)), meta: .none, links: .none)
init(id: Person.Id? = nil,name: [String], favoriteColor: String, friends: [Person], dogs: [Dog], home: House) throws {
self = Person(id: id ?? Person.Id(), attributes: .init(name: .init(value: name), favoriteColor: .init(value: favoriteColor)), relationships: .init(friends: .init(resourceObjects: friends), dogs: .init(resourceObjects: dogs), home: .init(resourceObject: home)), meta: .none, links: .none)
}
}

Expand Down Expand Up @@ -147,7 +147,7 @@ public extension ResourceObject where Description == DogDescription, MetaType ==
self = Dog(attributes: .init(name: .init(value: name)), relationships: DogDescription.Relationships(owner: .init(resourceObject: owner)), meta: .none, links: .none)
}

init(name: String, owner: Person.ID) throws {
init(name: String, owner: Person.Id) throws {
self = Dog(attributes: .init(name: .init(value: name)), relationships: .init(owner: .init(id: owner)), meta: .none, links: .none)
}
}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ extension String: CreatableRawIdType {

// Create a typealias because we do not expect JSON:API Resource
// Objects for this particular API to have Metadata or Links associated
// with them. We also expect them to have String Identifiers.
// with them. We also expect them to have String Ids.
typealias JSONEntity<Description: ResourceObjectDescription> = JSONAPI.ResourceObject<Description, NoMetadata, NoLinks, String>

// Similarly, create a typealias for unidentified entities. JSON:API
Expand Down Expand Up @@ -220,7 +220,7 @@ typealias SingleArticleDocument = Document<SingleResourceBody<Article>, NoInclud
func articleDocument(includeAuthor: Bool) -> Either<SingleArticleDocument, SingleArticleDocumentWithIncludes> {
// Let's pretend all of this is coming from a database:

let authorId = Author.ID(rawValue: "1234")
let authorId = Author.Id(rawValue: "1234")

let article = Article(id: .init(rawValue: "5678"),
attributes: .init(title: .init(value: "JSON:API in Swift"),
Expand Down
16 changes: 8 additions & 8 deletions Sources/JSONAPI/Resource/Relationship.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,25 @@ extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
}

extension ToOneRelationship {
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.ID == Identifiable.ID {
public init<T: ResourceObjectType>(resourceObject: T, meta: MetaType, links: LinksType) where T.Id == Identifiable.ID {
self.init(id: resourceObject.id, meta: meta, links: links)
}
}

extension ToOneRelationship where MetaType == NoMetadata, LinksType == NoLinks {
public init<T: ResourceObjectType>(resourceObject: T) where T.ID == Identifiable.ID {
public init<T: ResourceObjectType>(resourceObject: T) where T.Id == Identifiable.ID {
self.init(id: resourceObject.id, meta: .none, links: .none)
}
}

extension ToOneRelationship where Identifiable: OptionalRelatable {
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.ID == Identifiable.Wrapped.ID {
public init<T: ResourceObjectType>(resourceObject: T?, meta: MetaType, links: LinksType) where T.Id == Identifiable.Wrapped.ID {
self.init(id: resourceObject?.id, meta: meta, links: links)
}
}

extension ToOneRelationship where Identifiable: OptionalRelatable, MetaType == NoMetadata, LinksType == NoLinks {
public init<T: ResourceObjectType>(resourceObject: T?) where T.ID == Identifiable.Wrapped.ID {
public init<T: ResourceObjectType>(resourceObject: T?) where T.Id == Identifiable.Wrapped.ID {
self.init(id: resourceObject?.id, meta: .none, links: .none)
}
}
Expand All @@ -96,13 +96,13 @@ public struct ToManyRelationship<Relatable: JSONAPI.Relatable, MetaType: JSONAPI
self.links = links
}

public init<T: JSONAPI.JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.ID == Relatable.ID {
public init<T: JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>], meta: MetaType, links: LinksType) where T.ID == Relatable.ID {
ids = pointers.map(\.id)
self.meta = meta
self.links = links
}

public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.ID == Relatable.ID {
public init<T: ResourceObjectType>(resourceObjects: [T], meta: MetaType, links: LinksType) where T.Id == Relatable.ID {
self.init(ids: resourceObjects.map(\.id), meta: meta, links: links)
}

Expand All @@ -121,15 +121,15 @@ extension ToManyRelationship where MetaType == NoMetadata, LinksType == NoLinks
self.init(ids: ids, meta: .none, links: .none)
}

public init<T: JSONAPI.JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.ID == Relatable.ID {
public init<T: JSONAPIIdentifiable>(pointers: [ToOneRelationship<T, NoMetadata, NoLinks>]) where T.ID == Relatable.ID {
self.init(pointers: pointers, meta: .none, links: .none)
}

public static var none: ToManyRelationship {
return .none(withMeta: .none, links: .none)
}

public init<T: ResourceObjectType>(resourceObjects: [T]) where T.ID == Relatable.ID {
public init<T: ResourceObjectType>(resourceObjects: [T]) where T.Id == Relatable.ID {
self.init(resourceObjects: resourceObjects, meta: .none, links: .none)
}
}
Expand Down
20 changes: 10 additions & 10 deletions Sources/JSONAPI/Resource/Resource Object/ResourceObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ public protocol ResourceObjectProxy: Equatable, JSONTyped {
associatedtype Description: ResourceObjectProxyDescription
associatedtype EntityRawIdType: JSONAPI.MaybeRawId

typealias Id = JSONAPI.Id<EntityRawIdType, Self>

typealias Attributes = Description.Attributes
typealias Relationships = Description.Relationships

/// The `Entity`'s Id. This can be of type `Unidentified` if
/// the entity is being created clientside and the
/// server is being asked to create a unique Id. Otherwise,
/// this should be of a type conforming to `IdType`.
var id: JSONAPI.Id<EntityRawIdType, Self> { get }
var id: Id { get }

/// The JSON API compliant attributes of this `Entity`.
var attributes: Attributes { get }
Expand All @@ -89,10 +91,6 @@ public protocol ResourceObjectProxy: Equatable, JSONTyped {
var relationships: Relationships { get }
}

extension ResourceObjectProxy {
public typealias ID = JSONAPI.Id<EntityRawIdType, Self>
}

extension ResourceObjectProxy {
/// The JSON API compliant "type" of this `ResourceObject`.
public static var jsonType: String { return Description.jsonType }
Expand Down Expand Up @@ -130,7 +128,7 @@ public struct ResourceObject<Description: JSONAPI.ResourceObjectDescription, Met
/// the entity is being created clientside and the
/// server is being asked to create a unique Id. Otherwise,
/// this should be of a type conforming to `IdType`.
public let id: ID
public let id: ResourceObject.Id

/// The JSON API compliant attributes of this `ResourceObject`.
public let attributes: Description.Attributes
Expand All @@ -144,7 +142,7 @@ public struct ResourceObject<Description: JSONAPI.ResourceObjectDescription, Met
/// Links related to the entity.
public let links: LinksType

public init(id: ID, attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
public init(id: ResourceObject.Id, attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.id = id
self.attributes = attributes
self.relationships = relationships
Expand All @@ -165,7 +163,9 @@ extension ResourceObject: Hashable where EntityRawIdType: RawIdType {
}
}

extension ResourceObject: JSONAPIIdentifiable, IdentifiableResourceObjectType, Relatable where EntityRawIdType: JSONAPI.RawIdType {}
extension ResourceObject: JSONAPIIdentifiable, IdentifiableResourceObjectType, Relatable where EntityRawIdType: JSONAPI.RawIdType {
public typealias ID = ResourceObject.Id
}

@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension ResourceObject: Swift.Identifiable where EntityRawIdType: JSONAPI.RawIdType {}
Expand All @@ -179,7 +179,7 @@ extension ResourceObject: CustomStringConvertible {
// MARK: - Convenience initializers
extension ResourceObject where EntityRawIdType: CreatableRawIdType {
public init(attributes: Description.Attributes, relationships: Description.Relationships, meta: MetaType, links: LinksType) {
self.id = ResourceObject.ID()
self.id = ResourceObject.Id()
self.attributes = attributes
self.relationships = relationships
self.meta = meta
Expand Down Expand Up @@ -410,7 +410,7 @@ public extension ResourceObject {
}

let maybeUnidentified = Unidentified() as? EntityRawIdType
id = try maybeUnidentified.map { ResourceObject.ID(rawValue: $0) } ?? container.decode(ResourceObject.ID.self, forKey: .id)
id = try maybeUnidentified.map { ResourceObject.Id(rawValue: $0) } ?? container.decode(ResourceObject.Id.self, forKey: .id)

do {
attributes = try (NoAttributes() as? Description.Attributes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ final class DocumentCompoundResourceTests: XCTestCase {
)

let ids = [
DocumentTests.Book.ID(),
DocumentTests.Book.ID(),
DocumentTests.Book.ID()
DocumentTests.Book.Id(),
DocumentTests.Book.Id(),
DocumentTests.Book.Id()
]

let book = DocumentTests.Book(
Expand Down
4 changes: 2 additions & 2 deletions Tests/JSONAPITests/Poly/PolyProxyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ extension Poly2: ResourceObjectProxy, JSONTyped where A == PolyProxyTests.UserA,
public var id: Id<EntityRawIdType, PolyProxyTests.User> {
switch self {
case .a(let a):
return ID(rawValue: a.id.rawValue)
return Id(rawValue: a.id.rawValue)
case .b(let b):
return ID(rawValue: b.id.rawValue)
return Id(rawValue: b.id.rawValue)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/JSONAPITests/ResourceObject/ResourceObjectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ class ResourceObjectTests: XCTestCase {
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(meta: .init(meta: .init(x: "hello", y: 5), links: .none), optionalMeta: nil, one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(resourceObject: entity1, meta: .none, links: .none), optionalMany: nil), meta: .none, links: .none)
let _ = TestEntity9(id: .init(rawValue: "9"), attributes: .none, relationships: .init(meta: .init(meta: .init(x: "hello", y: 5), links: .none), optionalMeta: nil, one: entity1.pointer, nullableOne: nil, optionalOne: nil, optionalNullableOne: .init(resourceObject: entity1, meta: .none, links: .none), optionalMany: .init(resourceObjects: [], meta: .none, links: .none)), meta: .none, links: .none)
let e10id1 = TestEntity10.ID(rawValue: "hello")
let e10id2 = TestEntity10.ID(rawValue: "world")
let e10id3: TestEntity10.ID = "!"
let e10id2 = TestEntity10.Id(rawValue: "world")
let e10id3: TestEntity10.Id = "!"
let _ = TestEntity10(id: .init(rawValue: "10"), attributes: .none, relationships: .init(selfRef: .init(id: e10id1), selfRefs: .init(ids: [e10id2, e10id3])), meta: .none, links: .none)
XCTAssertNoThrow(try TestEntity11(id: .init(rawValue: "11"), attributes: .init(number: .init(rawValue: 11)), relationships: .none, meta: .none, links: .none))
let _ = UnidentifiedTestEntity(attributes: .init(me: .init(value: "hello")), relationships: .none, meta: .none, links: .none)
Expand Down
13 changes: 13 additions & 0 deletions Tests/JSONAPITests/SwiftIdentifiableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ final class SwiftIdentifiableTests: XCTestCase {
XCTAssertEqual(hash[t1.id], String(describing: t1.id))
XCTAssertEqual(hash[t2.id], String(describing: t2.id))
}

func test_Id_ID_equivalence() {
// it's not at all great to have both of these names for
// the Id type, but I could not do better than this and
// still have a typealias for the Id type on the
// ResourceObjectProxy protocol. One protocol's typealias
// will collide with anotehr protocol's associatedtype in
// very ugly ways.

XCTAssert(TestType.ID.self == TestType.Id.self)

XCTAssertEqual(TestType.ID(rawValue: "hello"), TestType.Id(rawValue: "hello"))
}
}

fileprivate enum TestDescription: JSONAPI.ResourceObjectDescription {
Expand Down
12 changes: 6 additions & 6 deletions documentation/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ typealias Relationships = NoRelationships

`Relationship` values boil down to `Ids` of other resource objects. To access the `Id` of a related `ResourceObject`, you can use the custom `~>` operator with the `KeyPath` of the `Relationship` from which you want the `Id`. The friends of the above `Person` `ResourceObject` can be accessed as follows (type annotations for clarity):
```swift
let friendIds: [Person.ID] = person ~> \.friends
let friendIds: [Person.Id] = person ~> \.friends
```

### `JSONAPI.Attributes`
Expand Down Expand Up @@ -244,8 +244,8 @@ If your computed property is wrapped in a `AttributeType` then you can still use

### Copying/Mutating `ResourceObjects`
`ResourceObject` is a value type, so copying is its default behavior. There are three common mutations you might want to make when copying a `ResourceObject`:
1. Assigning a new `ID` to the copy of an identified `ResourceObject`.
2. Assigning a new `ID` to the copy of an unidentified `ResourceObject`.
1. Assigning a new `Id` to the copy of an identified `ResourceObject`.
2. Assigning a new `Id` to the copy of an unidentified `ResourceObject`.
3. Change attribute or relationship values.

The first two can be accomplished with code like the following:
Expand Down Expand Up @@ -595,9 +595,9 @@ enum UserDescription: ResourceObjectDescription {
}

struct Relationships: JSONAPI.Relationships {
public var friend: (User) -> User.ID {
public var friend: (User) -> User.Id {
return { user in
return User.ID(rawValue: user.friend_id)
return User.Id(rawValue: user.friend_id)
}
}
}
Expand All @@ -612,4 +612,4 @@ Given a value `user` of the above resource object type, you can access the `frie
let friendId = user ~> \.friend
```

This works because `friend` is defined in the form: `var {name}: ({ResourceObject}) -> {ID}` where `{ResourceObject}` is the `JSONAPI.ResourceObject` described by the `ResourceObjectDescription` containing the meta-relationship.
This works because `friend` is defined in the form: `var {name}: ({ResourceObject}) -> {Id}` where `{ResourceObject}` is the `JSONAPI.ResourceObject` described by the `ResourceObjectDescription` containing the meta-relationship.

0 comments on commit 5b0a016

Please sign in to comment.