From b46f5166eaf08b1e41f3f4572c532d899e7750c3 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 31 Dec 2018 17:19:51 -0800 Subject: [PATCH] APIDescription gets a public initializer. A new playground page explores the full potential of the Framework, in a sense. --- .../Contents.swift | 213 ++++++++++++++++++ JSONAPI.playground/contents.xcplayground | 1 + Sources/JSONAPI/Document/APIDescription.swift | 5 + 3 files changed, 219 insertions(+) create mode 100644 JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift diff --git a/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..d9c3095 --- /dev/null +++ b/JSONAPI.playground/Pages/Full Document Verbose Generation.xcplaygroundpage/Contents.swift @@ -0,0 +1,213 @@ + +import Foundation +import JSONAPI + +/* + +This is not realistically what JSONAPI code will look like. It makes +every bit of sense to typealias the long-winded generics of this +framework to make working with them more concise and readable. + +The purpose of this example, then, is not to show "good" JSONAPI +code, but rather to generate a JSONAPI example document that uses +all the various (often optional) parts of a JSONAPI document. + +The JSON example produced in this manner can be used to compare the +nomenclature of this library with the nomenclature of the JSON:API Spec. + +*/ + +// MARK: - Extensions +extension Foundation.URL: JSONAPIURL {} + +// extension String: CreatableRawIdType {} found in playground Entities.swift file. + +// MARK: - Definitions +/// Metadata associated with entities +struct EntityMetadata: JSONAPI.Meta { + let creationDate: Date + let updatedDate: Date +} + +/// Metadata associated with links +struct LinkMeta: JSONAPI.Meta { + /// Don't trust this link past the expiry date. + let expiry: Date +} + +/// Links associated with entities +struct EntityLinks: JSONAPI.Links { + let `self`: Link + + init(selfLink: Link) { + self.`self` = selfLink + } +} + +/// Metadata associated with relationships +struct ToManyRelationshipMetadata: JSONAPI.Meta { + /// Pagination for the relationship (to support really large + /// to-many relationships) + let pagination: Pagination + + struct Pagination: Codable, Equatable { + let total: Int + let limit: Int + let offset: Int + } +} + +/// Links associated with relationships +struct ToManyRelationshipLinks: JSONAPI.Links { + /// next page of relationships. + let next: Link +} + +/// Description of an Author entity. +enum AuthorDescription: EntityDescription { + static var type: String { return "authors" } + + struct Attributes: JSONAPI.Attributes { + let name: Attribute + } + + struct Relationships: JSONAPI.Relationships { + let articles: ToManyRelationship + } +} + +typealias Author = JSONAPI.Entity + +/// Description of an Article entity. +enum ArticleDescription: EntityDescription { + static var type: String { return "articles" } + + struct Attributes: JSONAPI.Attributes { + let title: Attribute + } + + struct Relationships: JSONAPI.Relationships { + /// The primary attributed author of the article. + let primaryAuthor: ToOneRelationship + /// All authors excluding the primary author. + /// It is customary to print the primary author's + /// name first, followed by the other authors. + let otherAuthors: ToManyRelationship + } +} + +typealias Article = JSONAPI.Entity + +/// Metadata associated with the API Description +struct APIDescriptionMetadata: JSONAPI.Meta { + /// A clever codename for this API version. + let codename: String +} + +/// Metadata associated with any Document +struct DocumentMetadata: JSONAPI.Meta { + /// Assuming caching is in play, this is when + /// the cached document being returned was created. + let updatedDate: Date +} + +/// Links associated with an Article Document +struct SingleArticleDocumentLinks: JSONAPI.Links { + let otherArticlesByPrimaryAuthor: Link +} + +/// Error that can occur when retrieving a Single Article Document +enum ArticleDocumentError: String, JSONAPIError, Codable { + case unknownError = "unknown" + case missing = "missing" + + static var unknown: ArticleDocumentError { + return .unknownError + } +} + +typealias SingleArticleDocument = JSONAPI.Document, DocumentMetadata, SingleArticleDocumentLinks, Include1, APIDescription, ArticleDocumentError> + +// MARK: - Instantiations +let authorId1 = Author.Identifier() +let authorId2 = Author.Identifier() +let authorId3 = Author.Identifier() + +let now = Date() +let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: now)! + +let tomorrowEntityMeta = EntityMetadata(creationDate: Calendar.current.date(byAdding: .day, value: -10, to: now)!, + updatedDate: Calendar.current.date(byAdding: .day, value: -2, to: now)!) +let articleLinks = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/article/1234")!, + meta: .init(expiry: tomorrow))) +let article = Article(attributes: .init(title: .init(value: "How to read JSONAPI")), + relationships: .init(primaryAuthor: .init(id: authorId1), + otherAuthors: .init(ids: [authorId2, authorId3], + meta: .init(pagination: .init(total: 2, + limit: 50, + offset: 0)), + links: .init(next: .init(url: URL(string: "https://articles.com/article/1234?otherAuthors[offset]=50")!, + meta: .init(expiry: tomorrow))))), + meta: tomorrowEntityMeta, + links: articleLinks) + +let author1Links = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/author/\(authorId1.rawValue)")!, + meta: .init(expiry: tomorrow))) +let author1 = Author(id: authorId1, + attributes: .init(name: .init(value: "James Kinney")), + relationships: .init(articles: .init(ids: [article.id, Article.Identifier(), Article.Identifier()], + meta: .init(pagination: .init(total: 3, + limit: 50, + offset: 0)), + links: .init(next: .init(url: URL(string: "https://articles.com/author/\(authorId1.rawValue)?articles[offset]=50")!, meta: .init(expiry: tomorrow))))), + meta: tomorrowEntityMeta, + links: author1Links) + +let author2Links = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/author/\(authorId2.rawValue)")!, + meta: .init(expiry: tomorrow))) +let author2 = Author(id: authorId2, + attributes: .init(name: .init(value: "James Kinney")), + relationships: .init(articles: .init(ids: [article.id, Article.Identifier()], + meta: .init(pagination: .init(total: 2, + limit: 50, + offset: 0)), + links: .init(next: .init(url: URL(string: "https://articles.com/author/\(authorId2.rawValue)?articles[offset]=50")!, meta: .init(expiry: tomorrow))))), + meta: tomorrowEntityMeta, + links: author2Links) + +let author3Links = EntityLinks(selfLink: .init(url: URL(string: "https://articles.com/author/\(authorId3.rawValue)")!, + meta: .init(expiry: tomorrow))) +let author3 = Author(id: authorId3, + attributes: .init(name: .init(value: "James Kinney")), + relationships: .init(articles: .init(ids: [article.id], + meta: .init(pagination: .init(total: 1, + limit: 50, + offset: 0)), + links: .init(next: .init(url: URL(string: "https://articles.com/author/\(authorId3.rawValue)?articles[offset]=50")!, meta: .init(expiry: tomorrow))))), + meta: tomorrowEntityMeta, + links: author3Links) + +let apiDescription = APIDescription(version: "1.2", + meta: APIDescriptionMetadata(codename: "cobra")) + +let documentMetadata = DocumentMetadata(updatedDate: Date()) + +let documentLinks = SingleArticleDocumentLinks(otherArticlesByPrimaryAuthor: .init(url: URL(string: "https://articles.com/articles?author=\(authorId1.rawValue)")!, + meta: .init(expiry: tomorrow))) + +let singleArticleDocument = SingleArticleDocument(apiDescription: apiDescription, + body: .init(entity: article), + includes: .init(values: [.init(author1), .init(author2), .init(author3)]), + meta: documentMetadata, + links: documentLinks) + +// MARK: - Encoding +let encoder = JSONEncoder() +encoder.keyEncodingStrategy = .convertToSnakeCase +encoder.dateEncodingStrategy = .iso8601 +encoder.outputFormatting = .prettyPrinted + +let jsonData = try! encoder.encode(singleArticleDocument) + +// MARK: - Printing JSON +print(String(data: jsonData, encoding: .utf8)!) diff --git a/JSONAPI.playground/contents.xcplayground b/JSONAPI.playground/contents.xcplayground index 8df8fa3..45f30de 100644 --- a/JSONAPI.playground/contents.xcplayground +++ b/JSONAPI.playground/contents.xcplayground @@ -3,5 +3,6 @@ + \ No newline at end of file diff --git a/Sources/JSONAPI/Document/APIDescription.swift b/Sources/JSONAPI/Document/APIDescription.swift index 676aca6..a0ba2ff 100644 --- a/Sources/JSONAPI/Document/APIDescription.swift +++ b/Sources/JSONAPI/Document/APIDescription.swift @@ -14,6 +14,11 @@ public protocol APIDescriptionType: Codable, Equatable { public struct APIDescription: APIDescriptionType { public let version: String public let meta: Meta + + public init(version: String, meta: Meta) { + self.version = version + self.meta = meta + } } /// Can be used as `APIDescriptionType` for Documents that do not