From 13c650d66d59f7404905ba6df765a5e5174dbf26 Mon Sep 17 00:00:00 2001 From: Ward van Teijlingen Date: Sat, 22 Oct 2016 15:56:02 +0200 Subject: [PATCH] - Updated SpineError.serializerError to wrap underlying SerializerError. - Updated filtering methods of Query. - Documentation and code cleanup. --- Spine/DeserializeOperation.swift | 107 ++++----- Spine/Errors.swift | 6 +- Spine/KeyFormatter.swift | 20 +- Spine/Logging.swift | 11 +- Spine/Operation.swift | 18 +- Spine/Query.swift | 383 ++++++++++--------------------- Spine/ResourceCollection.swift | 2 +- Spine/ResourceFactory.swift | 67 +++--- Spine/ResourceField.swift | 55 ++--- Spine/SerializeOperation.swift | 85 +++---- Spine/Serializing.swift | 146 +++++------- Spine/Spine.swift | 269 ++++++++++------------ Spine/ValueFormatter.swift | 115 ++++------ SpineTests/QueryTests.swift | 42 ++-- SpineTests/RoutingTests.swift | 2 +- SpineTests/SpineTests.swift | 4 +- 16 files changed, 515 insertions(+), 817 deletions(-) diff --git a/Spine/DeserializeOperation.swift b/Spine/DeserializeOperation.swift index 3ee5f1a1..f4e9224f 100644 --- a/Spine/DeserializeOperation.swift +++ b/Spine/DeserializeOperation.swift @@ -144,14 +144,14 @@ class DeserializeOperation: Operation { // MARK: Deserializing - /** - Maps a single resource representation into a resource object of the given type. - - - parameter representation: The JSON representation of a single resource. - - parameter mappingTargetIndex: The index of the matching mapping target. - - - returns: A Resource object with values mapped from the representation. - */ + /// Maps a single resource representation into a resource object of the given type. + /// + /// - parameter representation: The JSON representation of a single resource. + /// - parameter mappingTargetIndex: The index of the matching mapping target. + /// + /// - throws: A SerializerError when an error occurs in serializing. + /// + /// - returns: A Resource object with values mapped from the representation. fileprivate func deserializeSingleRepresentation(_ representation: JSON, mappingTargetIndex: Int? = nil) throws -> Resource { guard representation.dictionary != nil else { throw SerializerError.invalidResourceStructure @@ -183,16 +183,10 @@ class DeserializeOperation: Operation { // MARK: Attributes - /** - Extracts the attributes from the given data into the given resource. - - This method loops over all the attributes in the passed resource, maps the attribute name - to the key for the serialized form and invokes `extractAttribute`. It then formats the extracted - attribute and sets the formatted value on the resource. - - - parameter serializedData: The data from which to extract the attributes. - - parameter resource: The resource into which to extract the attributes. - */ + /// Extracts the attributes from the given data into the given resource. + /// + /// - parameter serializedData: The data from which to extract the attributes. + /// - parameter resource: The resource into which to extract the attributes. fileprivate func extractAttributes(from serializedData: JSON, intoResource resource: Resource) { for case let field as Attribute in resource.fields { let key = keyFormatter.format(field) @@ -203,14 +197,12 @@ class DeserializeOperation: Operation { } } - /** - Extracts the value for the given key from the passed serialized data. - - - parameter serializedData: The data from which to extract the attribute. - - parameter key: The key for which to extract the value from the data. - - - returns: The extracted value or nil if no attribute with the given key was found in the data. - */ + /// Extracts the value for the given key from the passed serialized data. + /// + /// - parameter key: The data from which to extract the attribute. + /// - parameter serializedData: The key for which to extract the value from the data. + /// + /// - returns: The extracted value or nil if no attribute with the given key was found in the data. fileprivate func extractAttribute(_ key: String, from serializedData: JSON) -> Any? { let value = serializedData["attributes"][key] @@ -224,17 +216,10 @@ class DeserializeOperation: Operation { // MARK: Relationships - /** - Extracts the relationships from the given data into the given resource. - - This method loops over all the relationships in the passed resource, maps the relationship name - to the key for the serialized form and invokes `extractToOneRelationship` or `extractToManyRelationship`. - It then sets the extracted ResourceRelationship on the resource. - It also sets `relationships` dictionary on parent resource containing the links to all related resources. - - - parameter serializedData: The data from which to extract the relationships. - - parameter resource: The resource into which to extract the relationships. - */ + /// Extracts the relationships from the given data into the given resource. + /// + /// - parameter serializedData: The data from which to extract the relationships. + /// - parameter resource: The resource into which to extract the relationships. fileprivate func extractRelationships(from serializedData: JSON, intoResource resource: Resource) { for field in resource.fields { let key = keyFormatter.format(field) @@ -257,17 +242,15 @@ class DeserializeOperation: Operation { } } } - - /** - Extracts the to-one relationship for the given key from the passed serialized data. - - This method supports both the single ID form and the resource object forms. - - - parameter serializedData: The data from which to extract the relationship. - - parameter key: The key for which to extract the relationship from the data. - - - returns: The extracted relationship or nil if no relationship with the given key was found in the data. - */ + + /// Extracts the to-one relationship for the given key from the passed serialized data. + /// This method supports both the single ID form and the resource object forms. + /// + /// - parameter key: The key for which to extract the relationship from the data. + /// - parameter serializedData: The data from which to extract the relationship. + /// - parameter linkedType: The type of the linked resource as it is defined on the parent resource. + /// + /// - returns: The extracted relationship or nil if no relationship with the given key was found in the data. fileprivate func extractToOneRelationship(_ key: String, from serializedData: JSON, linkedType: ResourceType) -> Resource? { var resource: Resource? = nil @@ -295,17 +278,14 @@ class DeserializeOperation: Operation { return resource } - - /** - Extracts the to-many relationship for the given key from the passed serialized data. - - This method supports both the array of IDs form and the resource object forms. - - - parameter serializedData: The data from which to extract the relationship. - - parameter key: The key for which to extract the relationship from the data. - - - returns: The extracted relationship or nil if no relationship with the given key was found in the data. - */ + + /// Extracts the to-many relationship for the given key from the passed serialized data. + /// This method supports both the array of IDs form and the resource object forms. + /// + /// - parameter key: The key for which to extract the relationship from the data. + /// - parameter serializedData: The data from which to extract the relationship. + /// + /// - returns: The extracted relationship or nil if no relationship with the given key was found in the data. fileprivate func extractToManyRelationship(_ key: String, from serializedData: JSON) -> LinkedResourceCollection? { var resourceCollection: LinkedResourceCollection? = nil @@ -324,6 +304,11 @@ class DeserializeOperation: Operation { return resourceCollection } + /// Extract the relationship data from the given JSON. + /// + /// - parameter linkData: The JSON from which to extract relationship data. + /// + /// - returns: A RelationshipData object. fileprivate func extractRelationshipData(_ linkData: JSON) -> RelationshipData { let selfURL = linkData["links"]["self"].URL let relatedURL = linkData["links"]["related"].URL @@ -342,9 +327,7 @@ class DeserializeOperation: Operation { return RelationshipData(selfURL: selfURL, relatedURL: relatedURL, data: data) } - /** - Resolves the relations of the fetched resources. - */ + /// Resolves the relations of the fetched resources. fileprivate func resolveRelationships() { for resource in resourcePool { for case let field as ToManyRelationship in resource.fields { diff --git a/Spine/Errors.swift b/Spine/Errors.swift index dc181408..e4cd63b9 100644 --- a/Spine/Errors.swift +++ b/Spine/Errors.swift @@ -49,7 +49,7 @@ public enum SpineError: Error, Equatable { case resourceNotFound /// An error occured during (de)serializing. - case serializerError + case serializerError(SerializerError) /// A error response was received from the API. case serverError(statusCode: Int, apiErrors: [APIError]?) @@ -97,8 +97,8 @@ public func ==(lhs: SpineError, rhs: SpineError) -> Bool { return true case (.resourceNotFound, .resourceNotFound): return true - case (.serializerError, .serializerError): - return true + case (let .serializerError(lhsError), let .serializerError(rhsError)): + return lhsError == rhsError case (let .serverError(lhsStatusCode, lhsApiErrors), let .serverError(rhsStatusCode, rhsApiErrors)): if lhsStatusCode != rhsStatusCode { return false } if lhsApiErrors == nil && rhsApiErrors == nil { return true } diff --git a/Spine/KeyFormatter.swift b/Spine/KeyFormatter.swift index 626c2df3..eb060b91 100644 --- a/Spine/KeyFormatter.swift +++ b/Spine/KeyFormatter.swift @@ -8,10 +8,8 @@ import Foundation -/** -The KeyFormatter protocol declares methods and properties that a key formatter must implement. -A key formatter transforms field names as they appear in Resources to keys as they appear in a JSONAPI document. -*/ +/// The KeyFormatter protocol declares methods and properties that a key formatter must implement. +/// A key formatter transforms field names as they appear in Resources to keys as they appear in a JSONAPI document. public protocol KeyFormatter { func format(_ name: String) -> String } @@ -22,10 +20,8 @@ extension KeyFormatter { } } -/** -AsIsKeyFormatter does not format anything, i.e. it returns the field name as it. Use this if your field names correspond to -keys in a JSONAPI document one to one. -*/ +/// AsIsKeyFormatter does not format anything, i.e. it returns the field name as it. Use this if your field names correspond to +/// keys in a JSONAPI document one to one. public struct AsIsKeyFormatter: KeyFormatter { public func format(_ name: String) -> String { return name; @@ -34,9 +30,7 @@ public struct AsIsKeyFormatter: KeyFormatter { public init() { } } -/** -DasherizedKeyFormatter formats field names as dasherized keys. Eg. someFieldName -> some-field-name. -*/ +/// DasherizedKeyFormatter formats field names as dasherized keys. Eg. someFieldName -> some-field-name. public struct DasherizedKeyFormatter: KeyFormatter { let regex: NSRegularExpression @@ -50,9 +44,7 @@ public struct DasherizedKeyFormatter: KeyFormatter { } } -/* -UnderscoredKeyFormatter formats field names as underscored keys. Eg. someFieldName -> some_field_name. -*/ +/// UnderscoredKeyFormatter formats field names as underscored keys. Eg. someFieldName -> some_field_name. public struct UnderscoredKeyFormatter: KeyFormatter { let regex: NSRegularExpression diff --git a/Spine/Logging.swift b/Spine/Logging.swift index 91ecab64..374e4b32 100644 --- a/Spine/Logging.swift +++ b/Spine/Logging.swift @@ -46,12 +46,11 @@ public enum LogLevel: Int { } } -/** -Logging domains -- Spine: The main Spine component. -- Networking: The networking component, requests, responses etc. -- Serializing: The (de)serializing component. -*/ +/// Logging domains +/// +/// - spine: The main Spine component. +/// - networking: The networking component, requests, responses etc. +/// - serializing: The (de)serializing component. public enum LogDomain { case spine, networking, serializing } diff --git a/Spine/Operation.swift b/Spine/Operation.swift index a7928af5..ab2414f2 100644 --- a/Spine/Operation.swift +++ b/Spine/Operation.swift @@ -13,14 +13,14 @@ fileprivate func statusCodeIsSuccess(_ statusCode: Int?) -> Bool { } fileprivate extension Error { - /// Promotes an ErrorType to a higher level SpineError. - /// Errors that cannot be represented as a SpineError will be returned as SpineError.UnknownError + /// Promotes the rror to a SpineError. + /// Errors that cannot be represented as a SpineError will be returned as SpineError.unknownError var asSpineError: SpineError { switch self { case is SpineError: return self as! SpineError case is SerializerError: - return .serializerError + return .serializerError(self as! SerializerError) default: return .unknownError } @@ -195,7 +195,7 @@ class DeleteOperation: ConcurrentOperation { self.result = .success() } else if let data = responseData , data.count > 0 { do { - let document = try self.serializer.deserializeData(data, mappingTargets: nil) + let document = try self.serializer.deserializeData(data) self.result = .failure(.serverError(statusCode: statusCode!, apiErrors: document.errors)) } catch let error { self.result = .failure(error.asSpineError) @@ -380,14 +380,12 @@ private class RelationshipOperation: ConcurrentOperation { if statusCodeIsSuccess(statusCode) { self.result = .success() - } else if let data = responseData , data.count > 0 { + } else if let data = responseData, data.count > 0 { do { - let document = try serializer.deserializeData(data, mappingTargets: nil) + let document = try serializer.deserializeData(data) self.result = .failure(.serverError(statusCode: statusCode!, apiErrors: document.errors)) - } catch let error as SpineError { - self.result = .failure(error) - } catch { - self.result = .failure(.serializerError) + } catch let error { + self.result = .failure(error.asSpineError) } } else { self.result = .failure(.serverError(statusCode: statusCode!, apiErrors: nil)) diff --git a/Spine/Query.swift b/Spine/Query.swift index 6e806ac5..4d02cf72 100644 --- a/Spine/Query.swift +++ b/Spine/Query.swift @@ -8,19 +8,16 @@ import Foundation -/** -A Query defines search criteria used to retrieve data from an API. - -Custom query URL -================ -Usually Query objects are turned into URLs by the Router. The Router decides how the query configurations -are translated to URL components. However, queries can also be instantiated with a custom URL. -This is used when the API returns hrefs for example. Custom URL components will not be 'parsed' -into their respective configuration variables, so the query configuration may not correspond to -the actual URL generated by the Router. -*/ +/// A Query defines search criteria used to retrieve data from an API. +/// +/// Custom query URL +/// ================ +/// Usually Query objects are turned into URLs by the Router. The Router decides how the query configurations +/// are translated to URL components. However, queries can also be instantiated with a custom URL. +/// This is used when the API returns hrefs for example. Custom URL components will not be 'parsed' +/// into their respective configuration variables, so the query configuration may not correspond to +/// the actual URL generated by the Router. public struct Query { - /// The type of resource to fetch. This can be nil if in case of an expected heterogenous response. var resourceType: ResourceType? @@ -47,26 +44,22 @@ public struct Query { //MARK: Init - /** - Inits a new query for the given resource type and optional resource IDs. - - - parameter resourceType: The type of resource to query. - - parameter resourceIDs: The IDs of the resources to query. Pass nil to fetch all resources of the given type. + /// Inits a new query for the given resource type and optional resource IDs. + /// + /// - parameter resourceType: The type of resource to query. + /// - parameter resourceIDs: The IDs of the resources to query. Pass nil to fetch all resources of the given type. - - returns: Query - */ + /// - returns: Query public init(resourceType: T.Type, resourceIDs: [String]? = nil) { self.resourceType = T.resourceType self.resourceIDs = resourceIDs } - /** - Inits a new query that fetches the given resource. - - - parameter resource: The resource to fetch. - - - returns: Query - */ + /// Inits a new query that fetches the given resource. + /// + /// - parameter resource: The resource to fetch. + /// + /// - returns: Query public init(resource: T) { assert(resource.id != nil, "Cannot instantiate query for resource, id is nil.") self.resourceType = resource.resourceType @@ -74,59 +67,47 @@ public struct Query { self.resourceIDs = [resource.id!] } - /** - Inits a new query that fetches resources from the given resource collection. - - - parameter resourceCollection: The resource collection whose resources to fetch. - - - returns: Query - */ + /// Inits a new query that fetches resources from the given resource collection. + /// + /// - parameter resourceType: The type of resource to query. + /// - parameter resourceCollection: The resource collection whose resources to fetch. + /// + /// - returns: Query public init(resourceType: T.Type, resourceCollection: ResourceCollection) { self.resourceType = T.resourceType self.url = resourceCollection.resourcesURL as URL? } - - /** - Inits a new query that fetches resource of type `resourceType`, by using the given URL. - - - parameter resourceType: The type of resource to query. - - parameter path: The URL path used to fetch the resources. - - - returns: Query - */ + + /// Inits a new query that fetches resource of type `resourceType`, by using the given URL. + /// + /// - parameter resourceType: The type of resource to query. + /// - parameter path: The URL path used to fetch the resources. + /// + /// - returns: Query public init(resourceType: T.Type, path: String) { self.resourceType = T.resourceType self.url = URL(string: path) } - internal init(url: URL) { + init(url: URL) { self.url = url } - // MARK: Sideloading - - /** - Includes the given relation in the query. This will fetch resources that are in that relationship. - A relation should be specified using the serialized name. + // MARK: Including - - parameter relationshipNames: The serialized names of the relation to include. - - - returns: The query. - */ + /// Includes the given relationships in the query. + /// + /// - parameter relationshipNames: The names of the relationships to include. public mutating func include(_ relationshipNames: String...) { for relationshipName in relationshipNames { includes.append(relationshipName) } } - /** - Removes a previously included relation. A relation should be specified using the serialized name. - - - parameter relationshipNames: The names of the included relationships to remove. - - - returns: The query - */ + /// Removes previously included relationships. + /// + /// - parameter relationshipNames: The names of the included relationships to remove. public mutating func removeInclude(_ relationshipNames: String...) { includes = includes.filter { !relationshipNames.contains($0) } } @@ -134,7 +115,12 @@ public struct Query { // MARK: Filtering - fileprivate mutating func addPredicateWithField(_ fieldName: String, value: Any, type: NSComparisonPredicate.Operator) { + /// Adds a predicate to filter on a field. + /// + /// - parameter fieldName: The name of the field to filter on. + /// - parameter value: The value to check for. + /// - parameter type: The comparison operator to use + mutating func addPredicateWithField(_ fieldName: String, value: Any, type: NSComparisonPredicate.Operator) { if let field = T.fields.filter({ $0.name == fieldName }).first { addPredicateWithKey(field.name, value: value, type: type) } else { @@ -142,7 +128,13 @@ public struct Query { } } - fileprivate mutating func addPredicateWithKey(_ key: String, value: Any, type: NSComparisonPredicate.Operator) { + /// Adds a predicate to filter on a key. The key does not have to correspond + /// to a field defined on the resource. + /// + /// - parameter key: The key of the field to filter on. + /// - parameter value: The value to check for. + /// - parameter type: The comparison operator to use + mutating func addPredicateWithKey(_ key: String, value: Any, type: NSComparisonPredicate.Operator) { let predicate = NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: key), rightExpression: NSExpression(forConstantValue: value), @@ -150,171 +142,62 @@ public struct Query { type: type, options: []) - addPredicate(predicate) - } - - /** - Adds the given predicate as a filter. - - - parameter predicate: The predicate to add. - */ - public mutating func addPredicate(_ predicate: NSComparisonPredicate) { filters.append(predicate) } - - /** - Adds a filter where the given attribute should be equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter equals: The value to check for. - - - returns: The query - */ + + /// Adds a filter where the given attribute should be equal to the given value. + /// + /// - parameter attributeName: The name of the attribute to filter on. + /// - parameter equalTo: The value to check for. public mutating func whereAttribute(_ attributeName: String, equalTo: Any) { addPredicateWithField(attributeName, value: equalTo, type: .equalTo) } - - /** - Adds a filter where the given attribute should not be equal to the given value. - - parameter attributeName: The name of the attribute to filter on. - - parameter equals: The value to check for. - - - returns: The query - */ + /// Adds a filter where the given attribute should not be equal to the given value. + /// + /// - parameter attributeName: The name of the attribute to filter on. + /// - parameter notEqualTo: The value to check for. public mutating func whereAttribute(_ attributeName: String, notEqualTo: Any) { addPredicateWithField(attributeName, value: notEqualTo, type: .notEqualTo) } - /** - Adds a filter where the given attribute should be smaller than the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter smallerThen: The value to check for. - - - returns: The query - */ + /// Adds a filter where the given attribute should be smaller than the given value. + /// + /// - parameter attributeName: The name of the attribute to filter on. + /// - parameter lessThan: The value to check for. public mutating func whereAttribute(_ attributeName: String, lessThan: Any) { addPredicateWithField(attributeName, value: lessThan, type: .lessThan) } - /** - Adds a filter where the given attribute should be less then or equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter smallerThen: The value to check for. - - - returns: The query - */ + /// Adds a filter where the given attribute should be less then or equal to the given value. + /// + /// - parameter attributeName: The name of the attribute to filter on. + /// - parameter lessThanOrEqualTo: The value to check for. public mutating func whereAttribute(_ attributeName: String, lessThanOrEqualTo: Any) { addPredicateWithField(attributeName, value: lessThanOrEqualTo, type: .lessThanOrEqualTo) } - /** - Adds a filter where the given attribute should be greater then the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter greaterThen: The value to check for. - - - returns: The query - */ + /// Adds a filter where the given attribute should be greater then the given value. + /// + /// - parameter attributeName: The name of the attribute to filter on. + /// - parameter greaterThan: The value to check for. public mutating func whereAttribute(_ attributeName: String, greaterThan: Any) { addPredicateWithField(attributeName, value: greaterThan, type: .greaterThan) } - /** - Adds a filter where the given attribute should be greater than or equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter greaterThen: The value to check for. - - - returns: The query - */ + /// Adds a filter where the given attribute should be greater than or equal to the given value. + /// + /// - parameter attributeName: The name of the attribute to filter on. + /// - parameter greaterThanOrEqualTo: The value to check for. public mutating func whereAttribute(_ attributeName: String, greaterThanOrEqualTo: Any) { addPredicateWithField(attributeName, value: greaterThanOrEqualTo, type: .greaterThanOrEqualTo) } - - /** - Adds a filter where the given attribute should be equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter equals: The value to check for. - - - returns: The query - */ - public mutating func filterOn(_ attributeName: String, equalTo: Any) { - addPredicateWithKey(attributeName, value: equalTo, type: .equalTo) - } - - /** - Adds a filter where the given attribute should not be equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter equals: The value to check for. - - - returns: The query - */ - public mutating func filterOn(_ attributeName: String, notEqualTo: Any) { - addPredicateWithKey(attributeName, value: notEqualTo, type: .notEqualTo) - } - - /** - Adds a filter where the given attribute should be smaller than the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter smallerThen: The value to check for. - - - returns: The query - */ - public mutating func filterOn(_ attributeName: String, lessThan: Any) { - addPredicateWithKey(attributeName, value: lessThan, type: .lessThan) - } - - /** - Adds a filter where the given attribute should be less then or equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter smallerThen: The value to check for. - - - returns: The query - */ - public mutating func filterOn(_ attributeName: String, lessThanOrEqualTo: Any) { - addPredicateWithKey(attributeName, value: lessThanOrEqualTo, type: .lessThanOrEqualTo) - } - - /** - Adds a filter where the given attribute should be greater then the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter greaterThen: The value to check for. - - - returns: The query - */ - public mutating func filterOn(_ attributeName: String, greaterThan: Any) { - addPredicateWithKey(attributeName, value: greaterThan, type: .greaterThan) - } - - /** - Adds a filter where the given attribute should be greater than or equal to the given value. - - - parameter attributeName: The name of the attribute to filter on. - - parameter greaterThen: The value to check for. - - - returns: The query - */ - public mutating func filterOn(_ attributeName: String, greaterThanOrEqualTo: Any) { - addPredicateWithKey(attributeName, value: greaterThanOrEqualTo, type: .greaterThanOrEqualTo) - } - - /** - Adds a filter where the given relationship should point to the given resource, or the given - resource should be present in the related resources. - - - parameter relationshipName: The name of the relationship to filter on. - - parameter resource: The resource that should be related. - - returns: The query - */ + /// Adds a filter where the given relationship should point to the given resource, or the given + /// resource should be present in the related resources. + /// + /// - parameter relationshipName: The name of the relationship to filter on. + /// - parameter resource: The resource that should be related. public mutating func whereRelationship(_ relationshipName: String, isOrContains resource: Resource) { assert(resource.id != nil, "Attempt to add a where filter on a relationship, but the target resource does not have an id.") addPredicateWithField(relationshipName, value: resource.id! as AnyObject, type: .equalTo) @@ -323,14 +206,10 @@ public struct Query { // MARK: Sparse fieldsets - /** - Restricts the fields that should be requested. When not set, all fields will be requested. - Note: the server may still choose to return only of a select set of fields. - - - parameter fieldNames: Names of fields to fetch. - - - returns: The query - */ + /// Restricts the fields that should be requested. When not set, all fields will be requested. + /// Note: the server may still choose to return only of a select set of fields. + /// + /// - parameter fieldNames: Names of fields to fetch. public mutating func restrictFieldsTo(_ fieldNames: String...) { assert(resourceType != nil, "Cannot restrict fields for query without resource type, use `restrictFieldsOfResourceType` or set a resource type.") @@ -339,17 +218,13 @@ public struct Query { } } - /** - Restricts the fields of a specific resource type that should be requested. - This method can be used to restrict fields of included resources. When not set, all fields will be requested. - - Note: the server may still choose to return only of a select set of fields. - - - parameter type: The resource type for which to restrict the properties. - - parameter fieldNames: Names of fields to fetch. - - - returns: The query - */ + /// Restricts the fields of a specific resource type that should be requested. + /// This method can be used to restrict fields of included resources. When not set, all fields will be requested. + /// + /// Note: the server may still choose to return only of a select set of fields. + /// + /// - parameter type: The resource type for which to restrict the properties. + /// - parameter fieldNames: Names of fields to fetch. public mutating func restrictFieldsOfResourceType(_ type: Resource.Type, to fieldNames: String...) { for fieldName in fieldNames { guard let field = type.field(named: fieldName) else { @@ -368,13 +243,9 @@ public struct Query { // MARK: Sorting - /** - Sort in ascending order by the the given field. Previously added field take precedence over this field. - - - parameter fieldName: The name of the field which to order by. - - - returns: The query - */ + /// Sort in ascending order by the the given field. Previously added field take precedence over this field. + /// + /// - parameter fieldName: The name of the field which to order by. public mutating func addAscendingOrder(_ fieldName: String) { if let _ = T.field(named: fieldName) { sortDescriptors.append(NSSortDescriptor(key: fieldName, ascending: true)) @@ -383,13 +254,9 @@ public struct Query { } } - /** - Sort in descending order by the the given field. Previously added field take precedence over this property. - - - parameter property: The name of the field which to order by. - - - returns: The query - */ + /// Sort in descending order by the the given field. Previously added field take precedence over this property. + /// + /// - parameter property: The name of the field which to order by. public mutating func addDescendingOrder(_ fieldName: String) { if let _ = T.field(named: fieldName) { sortDescriptors.append(NSSortDescriptor(key: fieldName, ascending: false)) @@ -401,13 +268,9 @@ public struct Query { // MARK: Pagination - /** - Paginate the result using the given pagination configuration. Pass nil to remove pagination. - - - parameter pagination: The pagination configuration to use. - - - returns: The query - */ + /// Paginate the result using the given pagination configuration. Pass nil to remove pagination. + /// + /// - parameter pagination: The pagination configuration to use. public mutating func paginate(_ pagination: Pagination?) { self.pagination = pagination } @@ -416,47 +279,37 @@ public struct Query { // MARK: - Pagination -/** -The Pagination protocol is an empty protocol to which pagination configurations must adhere. -*/ +/// The Pagination protocol is an empty protocol to which pagination configurations must adhere. public protocol Pagination { } -/** -Page based pagination is a pagination strategy that returns results based on pages of a fixed size. -*/ +/// Page based pagination is a pagination strategy that returns results based on pages of a fixed size. public struct PageBasedPagination: Pagination { var pageNumber: Int var pageSize: Int - /** - Instantiates a new PageBasedPagination struct. - - - parameter pageNumber: The number of the page to return. - - parameter pageSize: The size of each page. - - - returns: PageBasedPagination - */ + /// Instantiates a new PageBasedPagination struct. + /// + /// - parameter pageNumber: The number of the page to return. + /// - parameter pageSize: The size of each page. + /// + /// - returns: PageBasedPagination public init(pageNumber: Int, pageSize: Int) { self.pageNumber = pageNumber self.pageSize = pageSize } } -/** -Offet based pagination is a pagination strategy that returns results based on an offset from the beginning of the result set. -*/ +/// Offet based pagination is a pagination strategy that returns results based on an offset from the beginning of the result set. public struct OffsetBasedPagination: Pagination { var offset: Int var limit: Int - /** - Instantiates a new OffsetBasedPagination struct. - - - parameter offset: The offset from the beginning of the result set. - - parameter limit: The number of resources to return. - - - returns: OffsetBasedPagination - */ + /// Instantiates a new OffsetBasedPagination struct. + /// + /// - parameter offset: The offset from the beginning of the result set. + /// - parameter limit: The number of resources to return. + /// + /// - returns: OffsetBasedPagination public init(offset: Int, limit: Int) { self.offset = offset self.limit = limit diff --git a/Spine/ResourceCollection.swift b/Spine/ResourceCollection.swift index a948f914..e37576da 100644 --- a/Spine/ResourceCollection.swift +++ b/Spine/ResourceCollection.swift @@ -11,7 +11,7 @@ import BrightFutures /// A ResourceCollection represents a collection of resources. public class ResourceCollection: NSObject, NSCoding { - /// Whether the resources for this collection are loaded + /// Whether the resources for this collection are loaded. public var isLoaded: Bool = false /// The URL of the current page in this collection. diff --git a/Spine/ResourceFactory.swift b/Spine/ResourceFactory.swift index 5248ff00..0cfec6c2 100644 --- a/Spine/ResourceFactory.swift +++ b/Spine/ResourceFactory.swift @@ -8,52 +8,49 @@ import Foundation -/** -A ResourceFactory creates resources from given factory funtions. -*/ + +/// A ResourceFactory creates resources from given factory funtions. struct ResourceFactory { fileprivate var resourceTypes: [ResourceType: Resource.Type] = [:] - - /** - Registers a given factory function that creates resource with a given type. - Registering a function for an already registered resource type will override that factory function. - - - parameter type: The resource type for which to register a factory function. - - parameter factory: The factory function that returns a resource. - */ - mutating func registerResource(_ resourceClass: Resource.Type) { - resourceTypes[resourceClass.resourceType] = resourceClass + + /// Registers a given resource type so it can be instantiated by the factory. + /// Registering a type that was alsreay registered will override it. + /// + /// - parameter resourceClass: <#resourceClass description#> + mutating func registerResource(_ type: Resource.Type) { + resourceTypes[type.resourceType] = type } - - /** - Instantiates a resource with the given type, by using a registered factory function. - - - parameter type: The resource type to instantiate. - - - returns: An instantiated resource. - */ + + /// Instantiates a resource with the given type, by using a registered factory function. + /// + /// - parameter type: The resource type to instantiate. + /// + /// - throws: A SerializerError.resourceTypeUnregistered erro when the type is not registered. + /// + /// - returns: An instantiated resource. func instantiate(_ type: ResourceType) throws -> Resource { if resourceTypes[type] == nil { throw SerializerError.resourceTypeUnregistered } return resourceTypes[type]!.init() } + - /** - Dispenses a resource with the given type and id, optionally by finding it in a pool of existing resource instances. - - This methods tries to find a resource with the given type and id in the pool. If no matching resource is found, - it tries to find the nth resource, indicated by `index`, of the given type from the pool. If still no resource is found, - it instantiates a new resource with the given id and adds this to the pool. - - - parameter type: The resource type to dispense. - - parameter id: The id of the resource to dispense. - - parameter pool: An array of resources in which to find exisiting matching resources. - - parameter index: Optional index of the resource in the pool. - - - returns: A resource with the given type and id. - */ + /// Dispenses a resource with the given type and id, optionally by finding it in a pool of existing resource instances. + /// + /// This methods tries to find a resource with the given type and id in the pool. If no matching resource is found, + /// it tries to find the nth resource, indicated by `index`, of the given type from the pool. If still no resource is found, + /// it instantiates a new resource with the given id and adds this to the pool. + /// + /// - parameter type: The resource type to dispense. + /// - parameter id: The id of the resource to dispense. + /// - parameter pool: An array of resources in which to find exisiting matching resources. + /// - parameter index: Optional index of the resource in the pool. + /// + /// - throws: A SerializerError.resourceTypeUnregistered erro when the type is not registered. + /// + /// - returns: A resource with the given type and id. func dispense(_ type: ResourceType, id: String, pool: inout [Resource], index: Int? = nil) throws -> Resource { var resource: Resource! = pool.filter { $0.resourceType == type && $0.id == id }.first diff --git a/Spine/ResourceField.swift b/Spine/ResourceField.swift index 15ec954e..5feabac9 100644 --- a/Spine/ResourceField.swift +++ b/Spine/ResourceField.swift @@ -15,10 +15,8 @@ public func fieldsFromDictionary(_ dictionary: [String: Field]) -> [Field] { } } -/** - * Base field. - * Do not use this field type directly, instead use a specific subclass. - */ +/// Base field. +/// Do not use this field type directly, instead use a specific subclass. open class Field { /// The name of the field as it appears in the model class. /// This is declared as an implicit optional to support the `fieldsFromDictionary` function, @@ -41,12 +39,11 @@ open class Field { fileprivate init() {} - /** - Sets the serialized name. - - - parameter name: The serialized name to use. - - returns: The field. - */ + /// Sets the serialized name. + /// + /// - parameter name: The serialized name to use. + /// + /// - returns: The field. public func serializeAs(_ name: String) -> Self { serializedName = name return self @@ -60,18 +57,14 @@ open class Field { // MARK: - Built in fields -/** - * A basic attribute field. - */ +/// A basic attribute field. open class Attribute: Field { override public init() {} } -/** - * A URL attribute that maps to an URL property. - * You can optionally specify a base URL to which relative - * URLs will be made absolute. - */ +/// A URL attribute that maps to an URL property. +/// You can optionally specify a base URL to which relative +/// URLs will be made absolute. public class URLAttribute: Attribute { let baseURL: URL? @@ -80,11 +73,9 @@ public class URLAttribute: Attribute { } } -/** - * A date attribute that maps to an NSDate property. - * By default, it uses ISO8601 format `yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ`. - * You can specify a custom format by passing it to the initializer. - */ +/// A date attribute that maps to an NSDate property. +/// By default, it uses ISO8601 format `yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ`. +/// You can specify a custom format by passing it to the initializer. public class DateAttribute: Attribute { let format: String @@ -93,15 +84,11 @@ public class DateAttribute: Attribute { } } -/** -* A boolean attribute that maps to an NSNumber property. -*/ +/// A boolean attribute that maps to an NSNumber property. public class BooleanAttribute: Attribute {} -/** - * A basic relationship field. - * Do not use this field type directly, instead use either `ToOneRelationship` or `ToManyRelationship`. - */ +/// A basic relationship field. +/// Do not use this field type directly, instead use either `ToOneRelationship` or `ToManyRelationship`. public class Relationship: Field { let linkedType: Resource.Type @@ -110,12 +97,8 @@ public class Relationship: Field { } } -/** - * A to-one relationship field. - */ +/// A to-one relationship field. public class ToOneRelationship: Relationship { } -/** - * A to-many relationship field. - */ +/// A to-many relationship field. public class ToManyRelationship: Relationship { } diff --git a/Spine/SerializeOperation.swift b/Spine/SerializeOperation.swift index 2102813c..8d73235c 100644 --- a/Spine/SerializeOperation.swift +++ b/Spine/SerializeOperation.swift @@ -9,9 +9,7 @@ import Foundation import SwiftyJSON -/** -A SerializeOperation serializes a JSONAPIDocument to JSON data in the form of NSData. -*/ +/// A SerializeOperation serializes a JSONAPIDocument to JSON data in the form of Data. class SerializeOperation: Operation { fileprivate let resources: [Resource] let valueFormatters: ValueFormatterRegistry @@ -41,7 +39,7 @@ class SerializeOperation: Operation { } do { - let serialized = try JSONSerialization.data(withJSONObject: ["data": serializedData], options: JSONSerialization.WritingOptions(rawValue: 0)) + let serialized = try JSONSerialization.data(withJSONObject: ["data": serializedData], options: []) result = Failable.success(serialized) } catch let error as NSError { result = Failable.failure(SerializerError.jsonSerializationError(error)) @@ -73,17 +71,11 @@ class SerializeOperation: Operation { // MARK: Attributes - - /** - Adds the attributes of the the given resource to the passed serialized data. - - This method loops over all the attributes in the passed resource, maps the attribute name - to the key for the serialized form and formats the value of the attribute. It then passes - the key and value to the addAttribute method. - - - parameter serializedData: The data to add the attributes to. - - parameter resource: The resource whose attributes to add. - */ + + /// Adds the attributes of the the given resource to the passed serialized data. + /// + /// - parameter resource: The resource whose attributes to add. + /// - parameter serializedData: The data to add the attributes to. fileprivate func addAttributes(from resource: Resource, to serializedData: inout [String: Any]) { var attributes = [String: Any](); @@ -92,42 +84,23 @@ class SerializeOperation: Operation { Spine.logDebug(.serializing, "Serializing attribute \(field) as '\(key)'") - //TODO: Dirty checking if let unformattedValue = resource.value(forField: field.name) { - addAttribute(&attributes, key: key, value: valueFormatters.formatValue(unformattedValue, forAttribute: field)) + attributes[key] = valueFormatters.formatValue(unformattedValue, forAttribute: field) } else if(!options.contains(.OmitNullValues)){ - addAttribute(&attributes, key: key, value: NSNull()) + attributes[key] = NSNull() } } - serializedData["attributes"] = attributes as AnyObject? + serializedData["attributes"] = attributes } - - /** - Adds the given key/value pair to the passed serialized data. - - - parameter serializedData: The data to add the key/value pair to. - - parameter key: The key to add to the serialized data. - - parameter value: The value to add to the serialized data. - */ - fileprivate func addAttribute(_ serializedData: inout [String: Any], key: String, value: Any) { - serializedData[key] = value - } - + // MARK: Relationships - - /** - Adds the relationships of the the given resource to the passed serialized data. - - This method loops over all the relationships in the passed resource, maps the attribute name - to the key for the serialized form and gets the related attributes. It then passes the key and - related resources to either the addToOneRelationship or addToManyRelationship method. - - - - parameter serializedData: The data to add the relationships to. - - parameter resource: The resource whose relationships to add. - */ + + /// Adds the relationships of the the given resource to the passed serialized data. + /// + /// - parameter resource: The resource whose relationships to add. + /// - parameter serializedData: The data to add the relationships to. fileprivate func addRelationships(from resource: Resource, to serializedData: inout [String: Any]) { for case let field as Relationship in resource.fields where field.isReadOnly == false { let key = keyFormatter.format(field) @@ -148,13 +121,12 @@ class SerializeOperation: Operation { } } - /** - Adds the given resource as a to to-one relationship to the serialized data. - - - parameter serializedData: The data to add the related resource to. - - parameter key: The key to add to the serialized data. - - parameter relatedResource: The related resource to add to the serialized data. - */ + /// Adds the given resource as a to to-one relationship to the serialized data. + /// + /// - parameter linkedResource: The linked resource to add to the serialized data. + /// - parameter serializedData: The data to add the related resource to. + /// - parameter key: The key to add to the serialized data. + /// - parameter type: The resource type of the linked resource as defined on the parent resource. fileprivate func addToOneRelationship(_ linkedResource: Resource?, to serializedData: inout [String: Any], key: String, type: ResourceType) { let serializedId: Any if let resourceId = linkedResource?.id { @@ -179,13 +151,12 @@ class SerializeOperation: Operation { } } - /** - Adds the given resources as a to to-many relationship to the serialized data. - - - parameter serializedData: The data to add the related resources to. - - parameter key: The key to add to the serialized data. - - parameter relatedResources: The related resources to add to the serialized data. - */ + /// Adds the given resources as a to to-many relationship to the serialized data. + /// + /// - parameter linkedResources: The linked resources to add to the serialized data. + /// - parameter serializedData: The data to add the related resources to. + /// - parameter key: The key to add to the serialized data. + /// - parameter type: The resource type of the linked resource as defined on the parent resource. fileprivate func addToManyRelationship(_ linkedResources: ResourceCollection?, to serializedData: inout [String: Any], key: String, type: ResourceType) { var resourceIdentifiers: [ResourceIdentifier] = [] diff --git a/Spine/Serializing.swift b/Spine/Serializing.swift index 84fc74a0..c1a30ef9 100644 --- a/Spine/Serializing.swift +++ b/Spine/Serializing.swift @@ -8,9 +8,7 @@ import Foundation -/** -Serializer (de)serializes according to the JSON:API specification. -*/ +/// Serializer (de)serializes according to the JSON:API specification. public class Serializer { /// The resource factory used for dispensing resources. fileprivate var resourceFactory = ResourceFactory() @@ -22,17 +20,15 @@ public class Serializer { public var keyFormatter: KeyFormatter = AsIsKeyFormatter() public init() {} - - /** - Deserializes the given data into a JSONAPIDocument. - - - parameter data: The data to deserialize. - - parameter mappingTargets: Optional resources onto which data will be deserialized. - - - throws: SerializerError that can occur in the deserialization. - - - returns: A JSONAPIDocument. - */ + + /// Deserializes the given data into a JSONAPIDocument. + /// + /// - parameter data: The data to deserialize. + /// - parameter mappingTargets: Optional resources onto which data will be deserialized. + /// + /// - throws: SerializerError that can occur in the deserialization. + /// + /// - returns: A JSONAPIDocument public func deserializeData(_ data: Data, mappingTargets: [Resource]? = nil) throws -> JSONAPIDocument { let deserializeOperation = DeserializeOperation(data: data, resourceFactory: resourceFactory, valueFormatters: valueFormatters, keyFormatter: keyFormatter) @@ -50,16 +46,14 @@ public class Serializer { } } - /** - Serializes the given JSON:API document into NSData. Currently only the main data is serialized. - - - parameter document: The JSONAPIDocument to serialize. - - parameter options: The serialization options to use. - - - throws: SerializerError that can occur in the serialization. - - - returns: Serialized data. - */ + /// Serializes the given JSON:API document into NSData. Only the main data is serialized. + /// + /// - parameter document: The JSONAPIDocument to serialize. + /// - parameter options: Serialization options to use. + /// + /// - throws: SerializerError that can occur in the serialization. + /// + /// - returns: Serialized data public func serializeDocument(_ document: JSONAPIDocument, options: SerializationOptions = [.IncludeID]) throws -> Data { let serializeOperation = SerializeOperation(document: document, valueFormatters: valueFormatters, keyFormatter: keyFormatter) serializeOperation.options = options @@ -73,41 +67,32 @@ public class Serializer { return data } } - - /** - Serializes the given Resources into NSData. - - - parameter resources: The resources to serialize. - - parameter options: The serialization options to use. - - - throws: SerializerError that can occur in the serialization. - - - returns: Serialized data. - */ + + /// Serializes the given Resources into NSData. + /// + /// - parameter resources: The resources to serialize. + /// - parameter options: The serialization options to use. + /// + /// - throws: SerializerError that can occur in the serialization. + /// + /// - returns: Serialized data. public func serializeResources(_ resources: [Resource], options: SerializationOptions = [.IncludeID]) throws -> Data { let document = JSONAPIDocument(data: resources, included: nil, errors: nil, meta: nil, links: nil, jsonapi: nil) return try serializeDocument(document, options: options) } + - /** - Converts the given resource to link data, and serializes it into NSData. - ```json - { - "data": { "type": "people", "id": "12" } - } - ``` - - If no resource is passed, `null` is used: - ```json - { "data": null } - ``` - - - parameter resource: The resource to serialize link data for. - - - throws: SerializerError that can occur in the serialization. - - - returns: Serialized data. - */ + /// Converts the given resource to link data, and serializes it into NSData. + /// `{"data": { "type": "people", "id": "12" }}` + /// + /// If no resource is passed, `null` is used: + /// `{ "data": null }` + /// + /// - parameter resource: The resource to serialize link data for. + /// + /// - throws: SerializerError that can occur in the serialization. + /// + /// - returns: Serialized data. public func serializeLinkData(_ resource: Resource?) throws -> Data { let payloadData: Any @@ -125,23 +110,21 @@ public class Serializer { } } - /** - Converts the given resources to link data, and serializes it into NSData. - ```json - { - "data": [ - { "type": "comments", "id": "12" }, - { "type": "comments", "id": "13" } - ] - } - ``` - - - parameter resources: The resource to serialize link data for. - - - throws: SerializerError that can occur in the serialization. - - - returns: Serialized data. - */ + /// Converts the given resources to link data, and serializes it into NSData. + /// ```json + /// { + /// "data": [ + /// { "type": "comments", "id": "12" }, + /// { "type": "comments", "id": "13" } + /// ] + /// } + /// ``` + /// + /// - parameter resources: The resource to serialize link data for. + /// + /// - throws: SerializerError that can occur in the serialization. + /// + /// - returns: Serialized data. public func serializeLinkData(_ resources: [Resource]) throws -> Data { let payloadData: Any @@ -160,29 +143,24 @@ public class Serializer { } } - /** - Registers a resource class. - - - parameter resourceClass: The resource class to register. - */ + /// Registers a resource class. + /// + /// - parameter resourceClass: The resource class to register. public func registerResource(_ resourceClass: Resource.Type) { resourceFactory.registerResource(resourceClass) } - /** - Registers transformer `transformer`. - - parameter transformer: The Transformer to register. - */ + /// Registers transformer `transformer`. + /// + /// - parameter transformer: The Transformer to register. public func registerValueFormatter(_ formatter: T) { valueFormatters.registerFormatter(formatter) } } -/** -A JSONAPIDocument represents a JSON API document containing -resources, errors, metadata, links and jsonapi data. -*/ +/// A JSONAPIDocument represents a JSON API document containing +/// resources, errors, metadata, links and jsonapi data. public struct JSONAPIDocument { /// Primary resources extracted from the response. public var data: [Resource]? diff --git a/Spine/Spine.swift b/Spine/Spine.swift index 390722ad..cf58198f 100644 --- a/Spine/Spine.swift +++ b/Spine/Spine.swift @@ -42,9 +42,13 @@ open class Spine { // MARK: Initializers - /** - Creates a new Spine instance using the given router and network client. - */ + /// Creates a new Spine instance using the given router and network client. + /// Use this initializer if you want to use a custom router and network client. + /// + /// - parameter router: The Router to use. + /// - parameter networkClient: The NetworkClient to use + /// + /// - returns: The Spine instance. public init(router: Router, networkClient: NetworkClient) { self.router = router self.networkClient = networkClient @@ -54,27 +58,34 @@ open class Spine { self.serializer.keyFormatter = keyFormatter } - /** - Creates a new Spine instance using the default Router and HTTPClient classes. - */ + /// Creates a new Spine instance using a JSONAPIRouter and HTTPClient. + /// + /// - parameter baseURL: The base URL to use for routing. + /// + /// - returns: Spine public convenience init(baseURL: URL) { let router = JSONAPIRouter() router.baseURL = baseURL self.init(router: router, networkClient: HTTPClient()) } - - /** - Creates a new Spine instance using a specific router and the default HTTPClient class. - Use this initializer to specify a custom router. - */ + + /// Creates a new Spine instance using a specific router and HTTPClient. + /// Use this initializer if you want to use a custom router. + /// + /// - parameter router: The Router to use. + /// + /// - returns: Spine public convenience init(router: Router) { self.init(router: router, networkClient: HTTPClient()) } - /** - Creates a new Spine instance using a specific network client and the default Router class. - Use this initializer to specify a custom network client. - */ + /// Creates a new Spine instance using a specific network client and the default Router class. + /// Use this initializer if you want to use a custom network client. + /// + /// - parameter baseURL: The base URL to use for routing. + /// - parameter networkClient: The NetworkClient to use. + /// + /// - returns: Spine public convenience init(baseURL: URL, networkClient: NetworkClient) { let router = JSONAPIRouter() router.baseURL = baseURL @@ -83,13 +94,11 @@ open class Spine { // MARK: Operations - - /** - Adds the given operation to the operation queue. - This sets the spine property of the operation to this Spine instance. - - - parameter operation: The operation to enqueue. - */ + + /// Adds the given operation to the operation queue. + /// This sets the spine property of the operation to this Spine instance. + /// + /// - parameter operation: The operation to enqueue. func addOperation(_ operation: ConcurrentOperation) { operation.spine = self operationQueue.addOperation(operation) @@ -98,13 +107,11 @@ open class Spine { // MARK: Fetching - /** - Fetch multiple resources using the given query. - - - parameter query: The query describing which resources to fetch. - - - returns: A future that resolves to a tuple containing the fetched ResourceCollection, the document meta, and the document jsonapi object. - */ + /// Fetch multiple resources using the given query. + /// + /// - parameter query: The query describing which resources to fetch. + /// + /// - returns: A future that resolves to a tuple containing the fetched ResourceCollection, the document meta, and the document jsonapi object. open func find(_ query: Query) -> Future<(resources: ResourceCollection, meta: Metadata?, jsonapi: JSONAPIData?), SpineError> { let promise = Promise<(resources: ResourceCollection, meta: Metadata?, jsonapi: JSONAPIData?), SpineError>() @@ -126,28 +133,24 @@ open class Spine { return promise.future } - /** - Fetch multiple resources with the given IDs and type. - - - parameter IDs: Array containing IDs of resources to fetch. - - parameter type: The type of resource to fetch. - - - returns: A future that resolves to a tuple containing the fetched ResourceCollection, the document meta, and the document jsonapi object. - */ + /// Fetch multiple resources with the given IDs and type. + /// + /// - parameter ids: Array containing IDs of resources to fetch. + /// - parameter type: The type of resource to fetch. + /// + /// - returns: A future that resolves to a tuple containing the fetched ResourceCollection, the document meta, and the document jsonapi object. open func find(_ ids: [String], ofType type: T.Type) -> Future<(resources: ResourceCollection, meta: Metadata?, jsonapi: JSONAPIData?), SpineError> { let query = Query(resourceType: type, resourceIDs: ids) return find(query) } - /** - Fetch one resource using the given query. - If the response contains multiple resources, the first resource is returned. - If the response indicates success but doesn't contain any resources, the returned future fails. - - - parameter query: The query describing which resource to fetch. - - - returns: A future that resolves to a tuple containing the fetched resource, the document meta, and the document jsonapi object. - */ + /// Fetch one resource using the given query. + /// If the response contains multiple resources, the first resource is returned. + /// If the response indicates success but doesn't contain any resources, the returned future fails. + /// + /// - parameter query: The query describing which resource to fetch. + /// + /// - returns: A future that resolves to a tuple containing the fetched resource, the document meta, and the document jsonapi object. open func findOne(_ query: Query) -> Future<(resource: T, meta: Metadata?, jsonapi: JSONAPIData?), SpineError> { let promise = Promise<(resource: T, meta: Metadata?, jsonapi: JSONAPIData?), SpineError>() @@ -171,29 +174,25 @@ open class Spine { return promise.future } - /** - Fetch one resource with the given ID and type. - If the response contains multiple resources, the first resource is returned. - If the response indicates success but doesn't contain any resources, the returned future fails. - - - parameter id: ID of resource to fetch. - - parameter type: The type of resource to fetch. - - - returns: A future that resolves to a tuple containing the fetched resource, the document meta, and the document jsonapi object. - */ + /// Fetch one resource with the given ID and type. + /// If the response contains multiple resources, the first resource is returned. + /// If the response indicates success but doesn't contain any resources, the returned future fails. + /// + /// - parameter id: ID of resource to fetch. + /// - parameter type: The type of resource to fetch. + /// + /// - returns: A future that resolves to a tuple containing the fetched resource, the document meta, and the document jsonapi object. open func findOne(_ id: String, ofType type: T.Type) -> Future<(resource: T, meta: Metadata?, jsonapi: JSONAPIData?), SpineError> { let query = Query(resourceType: type, resourceIDs: [id]) return findOne(query) } - /** - Fetch all resources with the given type. - This does not explicitly impose any limit, but the server may choose to limit the response. - - - parameter type: The type of resource to fetch. - - - returns: A future that resolves to a tuple containing the fetched ResourceCollection, the document meta, and the document jsonapi object. - */ + /// Fetch all resources with the given type. + /// This does not explicitly impose any limit, but the server may choose to limit the response. + /// + /// - parameter type: The type of resource to fetch. + /// + /// - returns: A future that resolves to a tuple containing the fetched ResourceCollection, the document meta, and the document jsonapi object. open func findAll(_ type: T.Type) -> Future<(resources: ResourceCollection, meta: Metadata?, jsonapi: JSONAPIData?), SpineError> { let query = Query(resourceType: type) return find(query) @@ -202,22 +201,20 @@ open class Spine { // MARK: Loading - /** - Load the given resource if needed. If its `isLoaded` property is true, it returns the resource as is. - Otherwise it loads the resource using the passed query. - - The `queryCallback` parameter can be used if the resource should be loaded by using a custom query. - For example, in case you want to include a relationship or load a sparse fieldset. - - Spine creates a basic query to load the resource and passes it to the queryCallback. - From this callback you return a modified query, or a whole new query if desired. This returned - query is then used when loading the resource. - - - parameter resource: The resource to ensure. - - parameter queryCallback: A optional function that returns the query used to load the resource. - - - returns: A future that resolves to the loaded resource. - */ + /// Load the given resource if needed. If its `isLoaded` property is true, it returns the resource as is. + /// Otherwise it loads the resource using the passed query. + /// + /// The `queryCallback` parameter can be used if the resource should be loaded by using a custom query. + /// For example, in case you want to include a relationship or load a sparse fieldset. + /// + /// Spine creates a basic query to load the resource and passes it to the queryCallback. + /// From this callback you return a modified query, or a whole new query if desired. This returned + /// query is then used when loading the resource. + /// + /// - parameter resource: The resource to ensure. + /// - parameter queryCallback: A optional function that returns the query used to load the resource. + /// + /// - returns: A future that resolves to the loaded resource. open func load(_ resource: T, queryCallback: ((Query) -> Query)? = nil) -> Future { var query = Query(resource: resource) if let callback = queryCallback { @@ -226,22 +223,20 @@ open class Spine { return loadResourceByExecutingQuery(resource, query: query) } - /** - Reload the given resource even when it's already loaded. The returned resource will be the same - instance as the passed resource. - - The `queryCallback` parameter can be used if the resource should be loaded by using a custom query. - For example, in case you want to include a relationship or load a sparse fieldset. - - Spine creates a basic query to load the resource and passes it to the queryCallback. - From this callback you return a modified query, or a whole new query if desired. This returned - query is then used when loading the resource. - - - parameter resource: The resource to reload. - - parameter queryCallback: A optional function that returns the query used to load the resource. - - - returns: A future that resolves to the reloaded resource. - */ + /// Reload the given resource even when it's already loaded. The returned resource will be the same + /// instance as the passed resource. + /// + /// The `queryCallback` parameter can be used if the resource should be loaded by using a custom query. + /// For example, in case you want to include a relationship or load a sparse fieldset. + /// + /// Spine creates a basic query to load the resource and passes it to the queryCallback. + /// From this callback you return a modified query, or a whole new query if desired. This returned + /// query is then used when loading the resource. + /// + /// - parameter resource: The resource to reload. + /// - parameter queryCallback: A optional function that returns the query used to load the resource. + /// + /// - returns: A future that resolves to the reloaded resource. open func reload(_ resource: T, queryCallback: ((Query) -> Query)? = nil) -> Future { var query = Query(resource: resource) if let callback = queryCallback { @@ -276,14 +271,12 @@ open class Spine { // MARK: Paginating - /** - Loads the next page of the given resource collection. The newly loaded resources are appended to the passed collection. - When the next page is not available, the returned future will fail with a `NextPageNotAvailable` error code. - - - parameter collection: The collection for which to load the next page. - - - returns: A future that resolves to the ResourceCollection including the newly loaded resources. - */ + /// Loads the next page of the given resource collection. The newly loaded resources are appended to the passed collection. + /// When the next page is not available, the returned future will fail with a `NextPageNotAvailable` error code. + /// + /// - parameter collection: The collection for which to load the next page. + /// + /// - returns: A future that resolves to the ResourceCollection including the newly loaded resources. open func loadNextPageOfCollection(_ collection: ResourceCollection) -> Future { let promise = Promise() @@ -315,14 +308,12 @@ open class Spine { return promise.future } - /** - Loads the previous page of the given resource collection. The newly loaded resources are prepended to the passed collection. - When the previous page is not available, the returned future will fail with a `PreviousPageNotAvailable` error code. - - - parameter collection: The collection for which to load the previous page. - - - returns: A future that resolves to the ResourceCollection including the newly loaded resources. - */ + /// Loads the previous page of the given resource collection. The newly loaded resources are prepended to the passed collection. + /// When the previous page is not available, the returned future will fail with a `PreviousPageNotAvailable` error code. + /// + /// - parameter collection: The collection for which to load the previous page. + /// + /// - returns: A future that resolves to the ResourceCollection including the newly loaded resources. open func loadPreviousPageOfCollection(_ collection: ResourceCollection) -> Future { let promise = Promise() @@ -357,13 +348,11 @@ open class Spine { // MARK: Persisting - /** - Saves the given resource. - - - parameter resource: The resource to save. - - - returns: A future that resolves to the saved resource. - */ + /// Saves the given resource. + /// + /// - parameter resource: The resource to save. + /// + /// - returns: A future that resolves to the saved resource. open func save(_ resource: T) -> Future { let promise = Promise() let operation = SaveOperation(resource: resource, spine: self) @@ -381,13 +370,12 @@ open class Spine { return promise.future } - /** - Deletes the given resource. - - - parameter resource: The resource to delete. - - returns: A future - */ + /// Deletes the given resource. + /// + /// - parameter resource: The resource to delete. + /// + /// - returns: A future open func delete(_ resource: T) -> Future { let promise = Promise() let operation = DeleteOperation(resource: resource, spine: self) @@ -406,38 +394,27 @@ open class Spine { } } -/** -Extension regarding registration. -*/ public extension Spine { - /** - Registers a resource class. - - - parameter resourceClass: The resource class to register. - */ + /// Registers a resource class. + /// + /// - parameter resourceClass: The resource class to register. func registerResource(_ resourceClass: Resource.Type) { serializer.registerResource(resourceClass) } - /** - Registers transformer `transformer`. - - - parameter transformer: The Transformer to register. - */ + /// Registers a value formatter. + /// + /// - parameter formatter: The formatter to register. func registerValueFormatter(_ formatter: T) { serializer.registerValueFormatter(formatter) } } -// MARK: - Failable - -/** -Represents the result of a failable operation. - -- Success: The operation succeeded with the given result. -- Failure: The operation failed with the given error. -*/ +/// Represents the result of a failable operation. +/// +/// - success: The operation succeeded with the given result. +/// - failure: The operation failed with the given error. enum Failable { case success(T) case failure(E) diff --git a/Spine/ValueFormatter.swift b/Spine/ValueFormatter.swift index 94a60840..1f6cac6a 100644 --- a/Spine/ValueFormatter.swift +++ b/Spine/ValueFormatter.swift @@ -9,10 +9,9 @@ import Foundation -/** -The ValueFormatter protocol declares methods and properties that a value formatter must implement. -A value formatter transforms values between the serialized and deserialized form. -*/ + +/// The ValueFormatter protocol declares methods and properties that a value formatter must implement. +/// A value formatter transforms values between the serialized and deserialized form. public protocol ValueFormatter { /// The type as it appears in serialized form (JSON). associatedtype FormattedType @@ -23,31 +22,26 @@ public protocol ValueFormatter { /// The attribute type for which this formatter formats values. associatedtype AttributeType - /** - Returns the deserialized form of the given value for the given attribute. - - - parameter value: The value to deserialize. - - parameter attribute: The attribute to which the value belongs. - - returns: The deserialized form of `value`. - */ + /// Returns the deserialized form of the given value for the given attribute. + /// + /// - parameter value: The value to deserialize. + /// - parameter forAttribute: The attribute to which the value belongs. + /// + /// - returns: The deserialized form of `value`. func unformatValue(_ value: FormattedType, forAttribute: AttributeType) -> UnformattedType - /** - Returns the serialized form of the given value for the given attribute. - - - parameter value: The value to serialize. - - parameter attribute: The attribute to which the value belongs. - - - returns: The serialized form of `value`. - */ + /// Returns the serialized form of the given value for the given attribute. + /// + /// - parameter value: The value to serialize. + /// - parameter forAttribute: The attribute to which the value belongs. + /// + /// - returns: The serialized form of `value`. func formatValue(_ value: UnformattedType, forAttribute: AttributeType) -> FormattedType } -/** -A value formatter Registry keeps a list of value formatters, and chooses between these value formatters -to transform values between the serialized and deserialized form. -*/ +/// A value formatter Registry keeps a list of value formatters, and chooses between these value formatters +/// to transform values between the serialized and deserialized form. struct ValueFormatterRegistry { /// Registered serializer functions. fileprivate var formatters: [(Any, Attribute) -> Any?] = [] @@ -55,11 +49,9 @@ struct ValueFormatterRegistry { /// Registered deserializer functions. fileprivate var unformatters: [(Any, Attribute) -> Any?] = [] - /** - Returns a new value formatter directory configured with the built in default value formatters. - - - returns: ValueFormatterRegistry - */ + /// Returns a new value formatter directory configured with the built in default value formatters. + /// + /// - returns: ValueFormatterRegistry static func defaultRegistry() -> ValueFormatterRegistry { var directory = ValueFormatterRegistry() directory.registerFormatter(URLValueFormatter()) @@ -67,12 +59,10 @@ struct ValueFormatterRegistry { directory.registerFormatter(BooleanValueFormatter()) return directory } - - /** - Registers the given value formatter. - - - parameter formatter: The value formatter to register. - */ + + /// Registers the given value formatter. + /// + /// - parameter formatter: The value formatter to register. mutating func registerFormatter(_ formatter: T) { formatters.append { (value: Any, attribute: Attribute) -> Any? in if let typedAttribute = attribute as? T.AttributeType { @@ -95,17 +85,15 @@ struct ValueFormatterRegistry { } } - /** - Returns the deserialized form of the given value for the given attribute. - - The actual value formatter used is the first registered formatter that supports the given - value type for the given attribute type. - - - parameter value: The value to deserialize. - - parameter attribute: The attribute to which the value belongs. - - - returns: The deserialized form of `value`. - */ + /// Returns the deserialized form of the given value for the given attribute. + /// + /// The actual value formatter used is the first registered formatter that supports the given + /// value type for the given attribute type. + /// + /// - parameter value: The value to deserialize. + /// - parameter attribute: The attribute to which the value belongs. + /// + /// - returns: The deserialized form of `value`. func unformatValue(_ value: Any, forAttribute attribute: Attribute) -> Any { for unformatter in unformatters { if let unformatted = unformatter(value, attribute) { @@ -116,18 +104,16 @@ struct ValueFormatterRegistry { return value } - /** - Returns the serialized form of the given value for the given attribute. - - The actual value formatter used is the first registered formatter that supports the given - value type for the given attribute type. If no suitable value formatter is found, - a string representation is returned. - - - parameter value: The value to serialize. - - parameter attribute: The attribute to which the value belongs. - - - returns: The serialized form of `value`. - */ + /// Returns the serialized form of the given value for the given attribute. + /// + /// The actual value formatter used is the first registered formatter that supports the given + /// value type for the given attribute type. If no suitable value formatter is found, + /// a string representation is returned. + /// + /// - parameter value: The value to serialize. + /// - parameter forAttribute: The attribute to which the value belongs. + /// + /// - returns: The serialized form of `value`. func formatValue(_ value: Any, forAttribute attribute: Attribute) -> Any { for formatter in formatters { if let formatted = formatter(value, attribute) { @@ -143,11 +129,9 @@ struct ValueFormatterRegistry { // MARK: - Built in value formatters -/** -URLValueFormatter is a value formatter that transforms between URL and String, and vice versa. -If a baseURL has been configured in the URLAttribute, and the given String is not an absolute URL, -it will return an absolute URL, relative to the baseURL. -*/ +/// URLValueFormatter is a value formatter that transforms between URL and String, and vice versa. +/// If a baseURL has been configured in the URLAttribute, and the given String is not an absolute URL, +/// it will return an absolute URL, relative to the baseURL. private struct URLValueFormatter: ValueFormatter { func unformatValue(_ value: String, forAttribute attribute: URLAttribute) -> URL { return URL(string: value, relativeTo: attribute.baseURL as URL?)! @@ -158,10 +142,8 @@ private struct URLValueFormatter: ValueFormatter { } } -/** -DateValueFormatter is a value formatter that transforms between NSDate and String, and vice versa. -It uses the date format configured in the DateAttribute. -*/ +/// DateValueFormatter is a value formatter that transforms between NSDate and String, and vice versa. +/// It uses the date format configured in the DateAttribute. private struct DateValueFormatter: ValueFormatter { func formatter(_ attribute: DateAttribute) -> DateFormatter { let formatter = DateFormatter() @@ -182,6 +164,7 @@ private struct DateValueFormatter: ValueFormatter { } } +/// BooleanValueformatter is a value formatter that formats NSNumber to Bool, and vice versa. private struct BooleanValueFormatter: ValueFormatter { func unformatValue(_ value: Bool, forAttribute: BooleanAttribute) -> NSNumber { return NSNumber(booleanLiteral: value) diff --git a/SpineTests/QueryTests.swift b/SpineTests/QueryTests.swift index 8f2cc599..1b1259fe 100644 --- a/SpineTests/QueryTests.swift +++ b/SpineTests/QueryTests.swift @@ -61,21 +61,6 @@ class QueryIncludeTests: XCTestCase { class QueryFilterTests: XCTestCase { - func testAddPredicate() { - var query = Query(resourceType: Foo.self) - - let predicate = NSComparisonPredicate( - leftExpression: NSExpression(forKeyPath: "property"), - rightExpression: NSExpression(forConstantValue: "value"), - modifier: .direct, - type: .equalTo, - options: NSComparisonPredicate.Options()) - - query.addPredicate(predicate) - - XCTAssertEqual(query.filters, [predicate], "Filters not as expected") - } - func testWherePropertyEqualTo() { var query = Query(resourceType: Foo.self) query.whereAttribute("stringAttribute", equalTo: "value") @@ -176,21 +161,20 @@ class QueryFilterTests: XCTestCase { XCTAssertEqual(query.filters, [predicate], "Filters not as expected") } - - func testFilterOnANonAttribute() { - var query = Query(resourceType: Foo.self) - query.filterOn("notAnAttribute", equalTo: "value") - - let predicate = NSComparisonPredicate( - leftExpression: NSExpression(forKeyPath: "notAnAttribute"), - rightExpression: NSExpression(forConstantValue: "value"), - modifier: .direct, - type: .equalTo, - options: NSComparisonPredicate.Options()) - - XCTAssertEqual(query.filters, [predicate], "Filters not as expected") - } + func testAddPredicateWithKey() { + var query = Query(resourceType: Foo.self) + query.addPredicateWithKey("notAnAttribute", value: "value", type: .equalTo) + + let predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: "notAnAttribute"), + rightExpression: NSExpression(forConstantValue: "value"), + modifier: .direct, + type: .equalTo, + options: NSComparisonPredicate.Options()) + + XCTAssertEqual(query.filters, [predicate], "Filters not as expected") + } } class QuerySparseFieldsetsTests: XCTestCase { diff --git a/SpineTests/RoutingTests.swift b/SpineTests/RoutingTests.swift index 0b09807a..9484d36a 100644 --- a/SpineTests/RoutingTests.swift +++ b/SpineTests/RoutingTests.swift @@ -34,7 +34,7 @@ class RoutingTests: XCTestCase { func testURLForQueryWithNonAttributeFilter() { var query = Query(resourceType: Foo.self, resourceIDs: ["1", "2"]) - query.filterOn("notAnAttribute", equalTo: "stringValue") + query.addPredicateWithKey("notAnAttribute", value: "stringValue", type: .equalTo) let url = spine.router.urlForQuery(query) let expectedURL = URL(string: "http://example.com/foos?filter[id]=1,2&filter[notAnAttribute]=stringValue")! diff --git a/SpineTests/SpineTests.swift b/SpineTests/SpineTests.swift index 22debc0a..83ebabe9 100644 --- a/SpineTests/SpineTests.swift +++ b/SpineTests/SpineTests.swift @@ -403,8 +403,8 @@ class SaveTests: SpineTests { foo = Foo() - spine.idGenerator = { r in - return "some id" + spine.idGenerator = { resource in + "some id" } let future = spine.save(foo)