├── .swift-version ├── Cartfile.private ├── Cartfile ├── Feathers.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ ├── Feathers-iOSTests.xcscheme │ │ ├── Feathers-tvOSTests.xcscheme │ │ ├── Feathers-macOSTests.xcscheme │ │ ├── Feathers-watchOS.xcscheme │ │ ├── Feathers-tvOS.xcscheme │ │ ├── Feathers-macOS.xcscheme │ │ └── Feathers-iOS.xcscheme └── project.pbxproj ├── Cartfile.resolved ├── Feathers ├── Core │ ├── SignalProducer+Convenience.swift │ ├── LoggerHooks.swift │ ├── AuthenticationStorage.swift │ ├── AuthenticationConfiguration.swift │ ├── Endpoint.swift │ ├── Provider.swift │ ├── ProviderService.swift │ ├── Response.swift │ ├── ServiceType.swift │ ├── Hooks.swift │ ├── Feathers.swift │ ├── FeathersError.swift │ ├── ServiceWrapper.swift │ ├── Service.swift │ └── Query.swift ├── Feathers.h └── Info.plist ├── FeathersTests ├── Info.plist ├── StubProvider.swift ├── Hooks.swift ├── FeathersSpec.swift ├── QuerySpec.swift └── ServiceSpec.swift ├── .gitignore ├── .github └── stale.yml ├── LICENSE ├── script └── build ├── Feathers.podspec ├── .travis.yml └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "jspahrsummers/xcconfigs" "3d9d996" 2 | github "Quick/Quick" 3 | github "Quick/Nimble" 4 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveCocoa/ReactiveSwift" 2 | github "marketplacer/keychain-swift" 3 | github "antitypical/Result" 4 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v8.0.1" 2 | github "Quick/Quick" "v2.1.0" 3 | github "ReactiveCocoa/ReactiveSwift" "6.0.0" 4 | github "antitypical/Result" "5.0.0" 5 | github "jspahrsummers/xcconfigs" "3d9d99634cae6d586e272543d527681283b33eb0" 6 | github "marketplacer/keychain-swift" "16.0.1" 7 | -------------------------------------------------------------------------------- /Feathers/Core/SignalProducer+Convenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignalProducer+Convenience.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/22/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | 12 | public extension SignalProducer { 13 | 14 | /// Sends only an interrupted event. 15 | static var interrupted: SignalProducer { 16 | return SignalProducer { observer, _ in 17 | observer.sendInterrupted() 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Feathers/Feathers.h: -------------------------------------------------------------------------------- 1 | // 2 | // Feathers.h 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/15/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Feathers. 12 | FOUNDATION_EXPORT double FeathersVersionNumber; 13 | 14 | //! Project version string for Feathers. 15 | FOUNDATION_EXPORT const unsigned char FeathersVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Feathers/Core/LoggerHooks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggerHooks.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/15/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | 12 | /// Simple request logger 13 | public struct RequestLoggerHook: Hook { 14 | 15 | public func run(with hookObject: HookObject) -> SignalProducer { 16 | print("request to \(hookObject.service.path) for method \(hookObject.method)") 17 | return SignalProducer(value: hookObject) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /FeathersTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Feathers/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 5.4.2 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | Example/Pods/* 23 | Example/Pods/Target\ Support\ Files/* 24 | 25 | # Bundler 26 | .bundle 27 | 28 | Carthage 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 32 | # 33 | # Note: if you ignore the Pods directory, make sure to uncomment 34 | # `pod install` in .travis.yml 35 | # 36 | # Pods/ 37 | /.env 38 | /Pods 39 | 40 | Podfile.lock 41 | -------------------------------------------------------------------------------- /Feathers/Core/AuthenticationStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationStorage.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/3/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import KeychainSwift 11 | 12 | /// Authentication storage protocol. 13 | public protocol AuthenticationStorage: class { 14 | 15 | init(storageKey: String) 16 | var accessToken: String? { get set } 17 | 18 | } 19 | 20 | /// An encrypted authentication store. Uses the keychain to store a token. 21 | public final class EncryptedAuthenticationStore: AuthenticationStorage { 22 | 23 | private let keychain = KeychainSwift() 24 | private let storageKey: String 25 | 26 | public var accessToken: String? { 27 | get { return keychain.get(storageKey) } 28 | set { 29 | guard let value = newValue else { return } 30 | keychain.set(value, forKey: storageKey) 31 | } 32 | } 33 | 34 | public init(storageKey: String = "feathers-jwt") { 35 | self.storageKey = storageKey 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 84 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - greenkeeper 8 | - bug 9 | - security 10 | - enhancement 11 | # Label to use when marking an issue as stale 12 | staleLabel: wontfix 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. 17 | Apologies if the issue could not be resolved. FeathersJS ecosystem 18 | modules are community maintained so there may be a chance that there isn't anybody 19 | available to address the issue at the moment. 20 | For other ways to get help [see here](https://docs.feathersjs.com/help/readme.html). 21 | # Comment to post when closing a stale issue. Set to `false` to disable 22 | closeComment: false 23 | # Only close stale issues 24 | only: issues 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Copyright (C) 2015 [Feathers contributors](https://github.com/feathersjs/feathers/graphs/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /script/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BUILD_DIRECTORY="build" 4 | CONFIGURATION=Release 5 | 6 | if [[ -z $TRAVIS_XCODE_PROJECT ]]; then 7 | echo "Error: \$TRAVIS_XCODE_PROJECT is not set." 8 | exit 1 9 | fi 10 | 11 | if [[ -z $TRAVIS_XCODE_SCHEME ]]; then 12 | echo "Error: \$TRAVIS_XCODE_SCHEME is not set!" 13 | exit 1 14 | fi 15 | 16 | if [[ -z $XCODE_ACTION ]]; then 17 | echo "Error: \$XCODE_ACTION is not set!" 18 | exit 1 19 | fi 20 | 21 | if [[ -z $XCODE_SDK ]]; then 22 | echo "Error: \$XCODE_SDK is not set!" 23 | exit 1 24 | fi 25 | 26 | if [[ -z $XCODE_DESTINATION ]]; then 27 | echo "Error: \$XCODE_DESTINATION is not set!" 28 | exit 1 29 | fi 30 | 31 | set -o pipefail 32 | xcodebuild $XCODE_ACTION \ 33 | -project "$TRAVIS_XCODE_PROJECT" \ 34 | -scheme "$TRAVIS_XCODE_SCHEME" \ 35 | -sdk "$XCODE_SDK" \ 36 | -destination "$XCODE_DESTINATION" \ 37 | -derivedDataPath "${BUILD_DIRECTORY}" \ 38 | -configuration $CONFIGURATION \ 39 | ENABLE_TESTABILITY=YES \ 40 | GCC_GENERATE_DEBUGGING_SYMBOLS=NO \ 41 | RUN_CLANG_STATIC_ANALYZER=NO | xcpretty 42 | result=$? 43 | 44 | if [ "$result" -ne 0 ]; then 45 | exit $result 46 | fi 47 | -------------------------------------------------------------------------------- /Feathers/Core/AuthenticationConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationConfiguration.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/16/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct AuthenticationConfiguration { 12 | 13 | /// Authorization header field in requests. 14 | public let header: String 15 | 16 | /// Path for authentication service. 17 | public let path: String 18 | 19 | /// Strategy name for jwt authentication. 20 | public let jwtStrategy: String 21 | 22 | /// The entity you are authenticating. 23 | public let entity: String 24 | 25 | /// The service to look up the entity 26 | public let service: String 27 | 28 | // The key to store the accessToken with. 29 | public let storageKey: String 30 | 31 | public init( 32 | header: String = "Authorization", 33 | path: String = "/authentication", 34 | jwtStrategy: String = "jwt", 35 | entity: String = "user", 36 | service: String = "users", 37 | storageKey: String = "feathers-jwt") { 38 | self.header = header 39 | self.path = path 40 | self.jwtStrategy = jwtStrategy 41 | self.entity = entity 42 | self.service = service 43 | self.storageKey = storageKey 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Feathers.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Feathers" 3 | # Version goes here and will be used to access the git tag later on, once we have a first release. 4 | s.version = "5.4.1" 5 | s.summary = "Swift framework for interacting with Featherjs apis" 6 | s.description = <<-DESC 7 | Swift library for connecting to a FeathersJS backend. 8 | 9 | ReactiveSwift and RxSwift extensions are available. 10 | DESC 11 | s.homepage = "https://github.com/feathersjs-ecosystem/feathers-swift" 12 | s.license = { :type => "MIT", :file => "LICENSE" } 13 | s.author = "startupthekid" 14 | 15 | s.swift_version = "5.0" 16 | s.ios.deployment_target = "8.0" 17 | s.osx.deployment_target = "10.10" 18 | s.tvos.deployment_target = "9.0" 19 | s.watchos.deployment_target = "2.0" 20 | s.source = { :git => "https://github.com/feathersjs-ecosystem/feathers-swift.git", :tag => "#{s.version}" } 21 | 22 | s.default_subspec = "Core" 23 | 24 | s.subspec "Core" do |ss| 25 | ss.source_files = "Feathers/Core/*.{swift}" 26 | ss.framework = "Foundation" 27 | ss.dependency 'KeychainSwift' 28 | ss.dependency 'Result' 29 | ss.dependency "ReactiveSwift" 30 | end 31 | 32 | s.pod_target_xcconfig = {"OTHER_SWIFT_FLAGS[config=Release]" => "-suppress-warnings" } 33 | end 34 | -------------------------------------------------------------------------------- /Feathers/Core/Endpoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Endpoint.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/7/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents an endpoint on the server. 12 | public final class Endpoint { 13 | 14 | /// Base url. 15 | public let baseURL: URL 16 | 17 | /// Endpoint path. 18 | public let path: String 19 | 20 | /// Service method. 21 | public let method: Service.Method 22 | 23 | /// Possible authentication token that can be attached to requests. 24 | public let accessToken: String? 25 | 26 | /// Application authentication configuration. 27 | public let authenticationConfiguration: AuthenticationConfiguration 28 | 29 | /// Creates an endpoint. 30 | /// 31 | /// - Parameters: 32 | /// - baseURL: Base url. 33 | /// - path: Endpoint path. 34 | /// - method: Service method. 35 | /// - accessToken: Authentication token. 36 | /// - authenticationConfiguration: Authentication configuration. 37 | internal init(baseURL: URL, path: String, method: Service.Method, accessToken: String?, authenticationConfiguration: AuthenticationConfiguration) { 38 | self.baseURL = baseURL 39 | self.path = path 40 | self.method = method 41 | self.accessToken = accessToken 42 | self.authenticationConfiguration = authenticationConfiguration 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /FeathersTests/StubProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StubProvider.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/14/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Feathers 10 | import Foundation 11 | import ReactiveSwift 12 | import enum Result.NoError 13 | 14 | class StubProvider: Provider { 15 | 16 | private let stubbedData: [String: Any] 17 | 18 | var supportsRealtimeEvents: Bool { 19 | return false 20 | } 21 | 22 | var baseURL: URL { 23 | return URL(string: "https://myserver.com")! 24 | } 25 | 26 | init(data: [String: Any]) { 27 | stubbedData = data 28 | } 29 | 30 | func setup(app: Feathers) { 31 | // no-op 32 | } 33 | 34 | func request(endpoint: Endpoint) -> SignalProducer { 35 | return SignalProducer(value: Response(pagination: nil, data: .object(stubbedData))) 36 | } 37 | 38 | func authenticate(_ path: String, credentials: [String : Any]) -> SignalProducer { 39 | return SignalProducer(value: Response(pagination: nil, data: .object(["accessToken":"some_token"]))) 40 | } 41 | 42 | func logout(path: String) -> SignalProducer { 43 | return SignalProducer(value: Response(pagination: nil, data: .object([:]))) 44 | } 45 | 46 | public func on(event: String) -> Signal<[String: Any], NoError> { 47 | // no-op 48 | return .empty 49 | } 50 | 51 | public func once(event: String) -> Signal<[String: Any], NoError> { 52 | return .empty 53 | } 54 | 55 | public func off(event: String) { 56 | // no-op 57 | } 58 | 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /FeathersTests/Hooks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hooks.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/14/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Feathers 10 | import Foundation 11 | import ReactiveSwift 12 | import Result 13 | 14 | /// Hook that always errors 15 | struct ErrorHook: Hook { 16 | 17 | let error: FeathersError 18 | 19 | init(error: FeathersError) { 20 | self.error = error 21 | } 22 | 23 | func run(with hookObject: HookObject) -> SignalProducer { 24 | return SignalProducer(error: AnyFeathersError(error)) 25 | } 26 | 27 | } 28 | 29 | // Hook that doesn't reject, just modifies the error 30 | struct ModifyErrorHook: Hook { 31 | let error: FeathersError 32 | 33 | init(error: FeathersError) { 34 | self.error = error 35 | } 36 | 37 | func run(with hookObject: HookObject) -> SignalProducer { 38 | var object = hookObject 39 | object.error = error 40 | return SignalProducer(value: object) 41 | } 42 | 43 | } 44 | 45 | struct StubHook: Hook { 46 | 47 | let data: ResponseData 48 | 49 | init(data: ResponseData) { 50 | self.data = data 51 | } 52 | 53 | func run(with hookObject: HookObject) -> SignalProducer { 54 | var object = hookObject 55 | object.result = Response(pagination: nil, data: data) 56 | return SignalProducer(value: object) 57 | } 58 | 59 | } 60 | 61 | 62 | struct PopuplateDataAfterHook: Hook { 63 | 64 | let data: [String: Any] 65 | 66 | init(data: [String: Any]) { 67 | self.data = data 68 | } 69 | 70 | func run(with hookObject: HookObject) -> SignalProducer { 71 | var object = hookObject 72 | object.result = Response(pagination: nil, data: .object(data)) 73 | return SignalProducer(value: object) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-iOSTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-tvOSTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-macOSTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Feathers/Core/Provider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Provider.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/15/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | import Result 12 | 13 | /// Abstract interface for a provider. 14 | public protocol Provider: class { 15 | 16 | /// Provider's base url. 17 | var baseURL: URL { get } 18 | 19 | /// Whether or not the provider supports real-time events. 20 | var supportsRealtimeEvents: Bool { get } 21 | 22 | /// Used for any extra setup a provider needs. Called by the `Feathers` application. 23 | /// 24 | /// - Parameters: 25 | /// - app: Feathers application object. 26 | func setup(app: Feathers) 27 | 28 | /// Send a request to the server. 29 | /// 30 | /// - Parameters: 31 | /// - endpoint: Endpoint to hit. 32 | /// - completion: Completion block. 33 | func request(endpoint: Endpoint) -> SignalProducer 34 | 35 | /// Authenticate the provider. 36 | /// 37 | /// - Parameters: 38 | /// - path: Authentication path. 39 | /// - credentials: Credentials object for authentication. 40 | /// - completion: Completion block. 41 | func authenticate(_ path: String, credentials: [String: Any]) -> SignalProducer 42 | 43 | /// Logout the provider. 44 | /// 45 | /// - Parameter path: Logout path. 46 | /// - Parameter completion: Completion block. 47 | func logout(path: String) -> SignalProducer 48 | 49 | /// Register to listen for an event. 50 | /// 51 | /// - Parameters: 52 | /// - event: Event name. 53 | /// - callback: Event callback. Called every time an event sends. 54 | /// 55 | /// - warning: Events will continue to emit until `off` is called. 56 | func on(event: String) -> Signal<[String: Any], NoError> 57 | 58 | /// Register for single-use handler for the event. 59 | /// 60 | /// - Parameters: 61 | /// - event: Event name. 62 | /// - callback: Event callback, only called once. 63 | func once(event: String) -> Signal<[String: Any], NoError> 64 | 65 | /// Unregister for an event. Must be called to end the stream. 66 | /// 67 | /// - Parameter event: Event name. 68 | func off(event: String) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | 4 | cache: 5 | - directories: 6 | - Carthage 7 | 8 | before_install: true 9 | install: 10 | - rvm get stable || true 11 | - brew update || true 12 | - brew install carthage || true 13 | - brew outdated carthage || brew upgrade carthage 14 | before_script: 15 | - chmod +x ./script/build 16 | xcode_project: Feathers.xcodeproj 17 | matrix: 18 | include: 19 | - script: 20 | - carthage bootstrap --platform mac 21 | - script/build 22 | xcode_scheme: Feathers-macOS 23 | env: 24 | - XCODE_SDK=macosx 25 | - XCODE_ACTION="build test" 26 | - XCODE_DESTINATION="arch=x86_64" 27 | - script: 28 | - carthage bootstrap --platform iOS 29 | - script/build 30 | xcode_scheme: Feathers-iOS 31 | env: 32 | - XCODE_SDK=iphonesimulator 33 | - XCODE_ACTION="build-for-testing test-without-building" 34 | - XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 7 Plus" 35 | - script: 36 | - carthage bootstrap --platform tvOS 37 | - script/build 38 | xcode_scheme: Feathers-tvOS 39 | env: 40 | - XCODE_SDK=appletvsimulator 41 | - XCODE_ACTION="build-for-testing test-without-building" 42 | - XCODE_DESTINATION="platform=tvOS Simulator,name=Apple TV" 43 | - script: 44 | - carthage bootstrap --platform watchOS 45 | - script/build 46 | xcode_scheme: Feathers-watchOS 47 | env: 48 | - XCODE_SDK=watchsimulator 49 | - XCODE_ACTION=build 50 | - XCODE_DESTINATION="platform=watchOS Simulator,name=Apple Watch - 38mm,OS=4.2" 51 | - script: 52 | - carthage checkout 53 | - carthage build --no-skip-current --platform mac 54 | env: 55 | - JOB=CARTHAGE-macOS 56 | - script: 57 | - carthage checkout 58 | - carthage build --no-skip-current --platform iOS 59 | env: 60 | - JOB=CARTHAGE-iOS 61 | - script: 62 | - carthage checkout 63 | - carthage build --no-skip-current --platform tvOS 64 | env: 65 | - JOB=CARTHAGE-tvOS 66 | - script: 67 | - carthage checkout 68 | - carthage build --no-skip-current --platform watchOS 69 | env: 70 | - JOB=CARTHAGE-watchOS 71 | # - script: 72 | # - pod repo update 73 | # - travis_wait 30 pod lib lint Feathers.podspec 74 | # env: 75 | # - JOB=PODSPEC 76 | -------------------------------------------------------------------------------- /FeathersTests/FeathersSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeathersSpec.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/20/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | import ReactiveSwift 12 | import Feathers 13 | 14 | class FeathersSpec: QuickSpec { 15 | override func spec() { 16 | describe("Feathers") { 17 | 18 | var app: Feathers! 19 | 20 | beforeEach { 21 | app = Feathers(provider: StubProvider(data: ["name": "Bob"])) 22 | } 23 | 24 | it("should authenticte successfully") { 25 | var didDispose = false 26 | var didReceiveValue = false 27 | var didComplete = false 28 | var didInterrupt = false 29 | app.authenticate([:]).on(completed: { 30 | didComplete = true 31 | }, interrupted: { 32 | didInterrupt = true 33 | }, disposed: { 34 | didDispose = true 35 | }, value: { value in 36 | didReceiveValue = true 37 | }) 38 | .start() 39 | expect(didDispose).toEventually(beTrue()) 40 | expect(didInterrupt).toEventually(beFalse()) 41 | expect(didReceiveValue).toEventually(beTrue()) 42 | expect(didComplete).toEventually(beTrue()) 43 | } 44 | 45 | it("should logout successfully") { 46 | var didDispose = false 47 | var didReceiveValue = false 48 | var didComplete = false 49 | var didInterrupt = false 50 | app.logout().on(completed: { 51 | didComplete = true 52 | }, interrupted: { 53 | didInterrupt = true 54 | }, disposed: { 55 | didDispose = true 56 | }, value: { value in 57 | didReceiveValue = true 58 | }) 59 | .start() 60 | expect(didDispose).toEventually(beTrue()) 61 | expect(didInterrupt).toEventually(beFalse()) 62 | expect(didReceiveValue).toEventually(beTrue()) 63 | expect(didComplete).toEventually(beTrue()) 64 | } 65 | 66 | } 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Feathers/Core/ProviderService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProviderService.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/21/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | import Result 12 | 13 | /// Service that wraps a transport provider. 14 | public class ProviderService: Service { 15 | 16 | private let provider: Provider 17 | 18 | public init(provider: Provider) { 19 | self.provider = provider 20 | super.init() 21 | } 22 | 23 | public override func request(_ method: Service.Method) -> SignalProducer { 24 | let endpoint = constructEndpoint(from: method) 25 | return provider.request(endpoint: endpoint) 26 | } 27 | 28 | override public func on(event: String) -> Signal<[String: Any], NoError> { 29 | return app?.provider.on(event: "\(path) \(event)") ?? .never 30 | } 31 | 32 | override public func once(event: String) -> Signal<[String: Any], NoError> { 33 | return app?.provider.once(event: "\(path) \(event)") ?? .never 34 | } 35 | 36 | override public func off(event: String) { 37 | app?.provider.off(event: event) 38 | } 39 | 40 | override public var supportsRealtimeEvents: Bool { 41 | return provider.supportsRealtimeEvents 42 | } 43 | 44 | // MARK: - Helpers 45 | 46 | /// Given a service method, construct an endpoint. 47 | /// 48 | /// - Parameter method: Service method. 49 | /// - Returns: `Endpoint` object. 50 | private func constructEndpoint(from method: Service.Method) -> Endpoint { 51 | guard let provider = app?.provider else { fatalError("provider must be given to the service before making requests") } 52 | var endpoint = Endpoint(baseURL: provider.baseURL, path: path, method: method, accessToken: nil, authenticationConfiguration: app?.authenticationConfiguration ?? AuthenticationConfiguration()) 53 | if let storage = app?.authenticationStorage, 54 | let accessToken = storage.accessToken { 55 | endpoint = Endpoint(baseURL: provider.baseURL, 56 | path: path, 57 | method: method, 58 | accessToken: accessToken, 59 | authenticationConfiguration: app?.authenticationConfiguration ?? AuthenticationConfiguration()) 60 | } 61 | return endpoint 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Feathers/Core/Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Response.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/16/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Describes a request's pagination properties. 12 | public struct Pagination { 13 | let total: Int 14 | let limit: Int 15 | let skip: Int 16 | 17 | public init(total: Int, limit: Int, skip: Int) { 18 | self.total = total 19 | self.limit = limit 20 | self.skip = skip 21 | } 22 | } 23 | 24 | /// Encapsulation around the kinds of response data we can receive. 25 | /// 26 | /// - list: Data is in list format (non-paginated). 27 | /// - object: Data is a JSON object (paginated or single entity request i.e. `get`, `update`, `patch`, or `remove`. 28 | public enum ResponseData: CustomDebugStringConvertible, CustomStringConvertible, Equatable { 29 | case list([Any]) 30 | case object(Any) 31 | 32 | public var value: Any { 33 | switch self { 34 | case .object(let data): return data 35 | case .list(let data): return data 36 | } 37 | } 38 | 39 | public var description: String { 40 | switch self { 41 | case .list(let data): 42 | return data.reduce("") { $0 + "\n\($1)\n" } 43 | case .object(let object): 44 | return "\(object)" 45 | } 46 | } 47 | 48 | public var debugDescription: String { 49 | return description 50 | } 51 | } 52 | 53 | // Only to be used for testing, does not actually compare equality of the json data 54 | public func == (lhs: ResponseData, rhs: ResponseData) -> Bool { 55 | if case .object = lhs, case .object = rhs { 56 | return true 57 | } else if case .list = lhs, case .list = rhs { 58 | return true 59 | } 60 | return false 61 | } 62 | 63 | /// Encapsulates a successful response from the service. 64 | public struct Response: CustomDebugStringConvertible, CustomStringConvertible { 65 | 66 | /// Pagination information. 67 | public let pagination: Pagination? 68 | 69 | /// The response data. 70 | public let data: ResponseData 71 | 72 | public var description: String { 73 | guard let page = pagination else { 74 | return "Data: \(data)" 75 | } 76 | return "Total: \(page.total)\nLimit: \(page.limit),Skip: \(page.skip)\nData: \(data)" 77 | 78 | } 79 | 80 | public init(pagination: Pagination?, data: ResponseData) { 81 | self.pagination = pagination 82 | self.data = data 83 | } 84 | 85 | public var debugDescription: String { 86 | return description 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Feathers/Core/ServiceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceType.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/21/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | import Result 12 | 13 | /// Interface that all services must conform to. 14 | public protocol ServiceType { 15 | 16 | /// The service's path, unmodifable once created. 17 | var path: String { get } 18 | 19 | /// If the service supports real time events or not. 20 | var supportsRealtimeEvents: Bool { get } 21 | 22 | /// Setup up the service with the necessary dependencies to execute requests. 23 | /// 24 | /// - Parameters: 25 | /// - app: Main feathers app. Services maintain a weak reference. 26 | /// - path: The service path. 27 | func setup(app: Feathers, path: String) 28 | 29 | /// Request data using one of the service methods. 30 | /// 31 | /// - Parameter method: Service method. 32 | /// - Returns: `SignalProducer` that emits a response or errors. 33 | func request(_ method: Service.Method) -> SignalProducer 34 | 35 | /// Register before hooks with the service. 36 | /// 37 | /// - Parameter hooks: Before hooks to register. 38 | func before(_ hooks: Service.Hooks) 39 | 40 | /// Register after hooks with the service. 41 | /// 42 | /// - Parameter hooks: After hooks to register. 43 | func after(_ hooks: Service.Hooks) 44 | 45 | /// Register error hooks with the service. 46 | /// 47 | /// - Parameter hooks: Error hooks to register. 48 | func error(_ hooks: Service.Hooks) 49 | 50 | /// Fetch a service's hooks. 51 | /// 52 | /// - Parameter kind: The kind of hook. 53 | /// - Returns: Hooks registered for `kind`, if any exist. 54 | func hooks(for kind: HookObject.Kind) -> Service.Hooks? 55 | 56 | /// Register for a real-time event to listen for changing data. 57 | /// Signal will continue to emit until disposed. 58 | /// 59 | /// - Parameter event: Real-time event to listen for. 60 | /// - Returns: `Signal` that emits any data. Never errors. 61 | func on(event: String) -> Signal<[String: Any], NoError> 62 | 63 | /// Register for a real-time event but only one time. Signal will emit once 64 | /// then dispose. 65 | /// 66 | /// - Parameter event: Real-time event to listen for. 67 | /// - Returns: Signal that emits a value once, if ever. 68 | func once(event: String) -> Signal<[String: Any], NoError> 69 | 70 | /// Unregister for a real-time event. 71 | /// 72 | /// - Parameter event: Event to unregister for. 73 | func off(event: String) 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Feathers/Core/Hooks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hooks.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/13/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Result 11 | import ReactiveSwift 12 | 13 | /// Hook object that gets passed through hook functions 14 | public struct HookObject { 15 | 16 | /// Represents the kind of hook. 17 | /// 18 | /// - before: Hook is run before the request is made. 19 | /// - after: Hook is run after the request is made. 20 | /// - error: Runs when there's an error. 21 | public enum Kind { 22 | case before, after, error 23 | } 24 | 25 | /// The kind of hook. 26 | public let type: Kind 27 | 28 | /// Feathers application, used to retrieve other services. 29 | public let app: Feathers 30 | 31 | /// The service this hook currently runs on. 32 | public let service: ServiceType 33 | 34 | /// The service method. 35 | public var method: Service.Method 36 | 37 | public var result: Response? 38 | 39 | public var error: FeathersError? 40 | 41 | public init( 42 | type: Kind, 43 | app: Feathers, 44 | service: ServiceType, 45 | method: Service.Method) { 46 | self.type = type 47 | self.app = app 48 | self.service = service 49 | self.method = method 50 | } 51 | 52 | } 53 | 54 | public extension HookObject { 55 | 56 | /// Modify the hook object by adding a result. 57 | /// 58 | /// - Parameter result: Result to add. 59 | /// - Returns: Modified hook object. 60 | func objectByAdding(result: Response) -> HookObject { 61 | var object = self 62 | object.result = result 63 | return object 64 | } 65 | 66 | /// Modify the hook object by attaching an error. 67 | /// 68 | /// - Parameter error: Error to attach. 69 | /// - Returns: Modified hook object. 70 | func objectByAdding(error: FeathersError) -> HookObject { 71 | var object = self 72 | object.error = error 73 | return object 74 | } 75 | 76 | /// Create a new hook object with a new type. 77 | /// 78 | /// - Parameter type: New type. 79 | /// - Returns: A new hook object with copied over properties. 80 | func object(with type: Kind) -> HookObject { 81 | var object = HookObject(type: type, app: app, service: service, method: method) 82 | object.result = result 83 | return object 84 | } 85 | 86 | } 87 | 88 | /// Hook protocol. 89 | public protocol Hook { 90 | 91 | /// Function that's called by the middleware system to run the hook. 92 | /// 93 | /// In order to modify the hook, a copy of it has to be made because 94 | /// Swift function parameters are `let` by default. 95 | /// 96 | /// - Parameters: 97 | /// - hookObject: Hook object. 98 | /// - Returns: `SignalProducer` that emits the modified hook object or errors. 99 | func run(with hookObject: HookObject) -> SignalProducer 100 | } 101 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Feathers/Core/Feathers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Feathers.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/15/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | 12 | /// Main application object. Creates services and provides an interface for authentication. 13 | public final class Feathers { 14 | 15 | /// Transport provider. 16 | public let provider: Provider 17 | 18 | /// Authentication store. 19 | private(set) public var authenticationStorage: AuthenticationStorage = EncryptedAuthenticationStore() 20 | 21 | /// Authentication configuration. 22 | private(set) public var authenticationConfiguration = AuthenticationConfiguration() 23 | 24 | private var services: [String: ServiceType] = [:] 25 | 26 | /// Feather's initializer. 27 | /// 28 | /// - Parameter provider: Transport provider. 29 | public init(provider: Provider) { 30 | self.provider = provider 31 | provider.setup(app: self) 32 | } 33 | 34 | /// Create a service for the given path. 35 | /// 36 | /// This method uses some fancy indirection and wraps every service in a `ServiceWrapper` instance 37 | /// that's responsible for running the hook chain and proxying methods to the wrapped service. 38 | /// 39 | /// - Parameter path: Service path. 40 | /// - Returns: Service object. 41 | public func service(path: String) -> ServiceType { 42 | let servicePath = path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) 43 | guard let service = services[servicePath] else { 44 | // If no service has been registered or requested for at this path, 45 | // create one around the transport provider. 46 | let providerService = ProviderService(provider: provider) 47 | providerService.setup(app: self, path: servicePath) 48 | // Store it so the service is retained and hooks can be registered. 49 | services[servicePath] = providerService 50 | // Create the wrapper 51 | let wrapper = ServiceWrapper(service: providerService) 52 | wrapper.setup(app: self, path: servicePath) 53 | return wrapper 54 | } 55 | let wrapper = ServiceWrapper(service: service) 56 | wrapper.setup(app: self, path: servicePath) 57 | return wrapper 58 | } 59 | 60 | public func use(path: String, service: ServiceType) { 61 | let servicePath = path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) 62 | service.setup(app: self, path: servicePath) 63 | services[servicePath] = service 64 | } 65 | 66 | /// Configure any authentication options. 67 | /// 68 | /// - Parameter configuration: Authentication configuration object. 69 | public func configure(auth configuration: AuthenticationConfiguration) { 70 | authenticationConfiguration = configuration 71 | authenticationStorage = EncryptedAuthenticationStore(storageKey: configuration.storageKey) 72 | } 73 | 74 | /// Authenticate the application. 75 | /// 76 | /// - Parameters: 77 | /// - credentials: Credentials to authenticate with. 78 | /// - Returns: Promise that emits an access token. 79 | public func authenticate(_ credentials: [String: Any]) -> SignalProducer<[String: Any], AnyFeathersError> { 80 | return provider.authenticate(authenticationConfiguration.path, credentials: credentials) 81 | .flatMap(.latest) { response -> SignalProducer<[String: Any], AnyFeathersError> in 82 | if case let .object(object) = response.data, 83 | let json = object as? [String: Any] { 84 | return SignalProducer(value: json) 85 | } 86 | return SignalProducer(error: AnyFeathersError(FeathersNetworkError.unknown)) 87 | }.on(failed: { [weak self] _ in 88 | self?.authenticationStorage.accessToken = nil 89 | }, value: { [weak self] value in 90 | guard let token = value["accessToken"] as? String else { return } 91 | self?.authenticationStorage.accessToken = token 92 | }) 93 | } 94 | 95 | /// Log out the application. 96 | /// 97 | /// - Returns: Promise that emits a response. 98 | public func logout() -> SignalProducer { 99 | return provider.logout(path: authenticationConfiguration.path) 100 | .on(value: { [weak self] _ in 101 | self?.authenticationStorage.accessToken = nil 102 | }) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/xcshareddata/xcschemes/Feathers-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 49 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 81 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Feathers/Core/FeathersError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeathersError.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/16/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol FeathersError: Swift.Error { 12 | 13 | var description: String { get } 14 | } 15 | 16 | /// Type erase any errors 17 | public struct AnyFeathersError: Error { 18 | /// The underlying error. 19 | public let error: FeathersError 20 | 21 | public init(_ error: FeathersError) { 22 | if let anyError = error as? AnyFeathersError { 23 | self = anyError 24 | } else { 25 | self.error = error 26 | } 27 | } 28 | } 29 | 30 | public extension FeathersError { 31 | 32 | var description: String { 33 | if let networkError = self as? FeathersNetworkError { 34 | return networkError.errorMessage 35 | } else if let serviceError = self as? JuneFeathersError { 36 | return serviceError.message 37 | } else { 38 | return self.localizedDescription 39 | } 40 | } 41 | } 42 | 43 | public enum JuneFeathersError: FeathersError, Equatable { 44 | 45 | case illegal 46 | 47 | init() { 48 | self = .illegal 49 | } 50 | 51 | } 52 | 53 | extension JuneFeathersError { 54 | 55 | var message: String { 56 | return "Your requests can't be processed at this time" 57 | } 58 | } 59 | 60 | public enum FeathersNetworkError: FeathersError, Equatable { 61 | 62 | case badRequest 63 | case notAuthenticated 64 | case paymentError 65 | case forbidden 66 | case notFound 67 | case methodNotAllowed 68 | case notAcceptable 69 | case timeout 70 | case conflict 71 | case lengthRequired 72 | case unprocessable 73 | case tooManyRequests 74 | case general 75 | case notImplemented 76 | case badGateway 77 | case unavailable 78 | case underlying(Error) 79 | case unknown 80 | 81 | public init?(statusCode: Int) { 82 | switch statusCode { 83 | case 400: self = .badRequest 84 | case 401: self = .notAuthenticated 85 | case 403: self = .forbidden 86 | case 404: self = .notFound 87 | case 405: self = .methodNotAllowed 88 | case 406: self = .notAcceptable 89 | case 408: self = .timeout 90 | case 409: self = .conflict 91 | case 411: self = .lengthRequired 92 | case 422: self = .unprocessable 93 | case 429: self = .tooManyRequests 94 | case 500: self = .general 95 | case 501: self = .notImplemented 96 | case 502: self = .badGateway 97 | case 503: self = .unavailable 98 | default: return nil 99 | } 100 | } 101 | 102 | public init(error: Error) { 103 | self = .underlying(error) 104 | } 105 | 106 | } 107 | 108 | extension FeathersNetworkError { 109 | 110 | var errorMessage: String { 111 | switch self { 112 | case .badRequest: return "Error #400. Failded to execute your requests." 113 | case .notAuthenticated: return "Error #401. Not Authenticated. Please Log In." 114 | case .forbidden: return "Error #403. Failed to execute your request." 115 | case .notFound: return "Error #404. Failed to execute your request." 116 | case .methodNotAllowed: return "Error #405. Failed to execute your request." 117 | case .notAcceptable: return "Error #406. Failed to execute your request." 118 | case .timeout: return "Error #408. Timed out. Try again later." 119 | case .conflict: return "Error #409. Failed to execute your requests." 120 | case .lengthRequired: return "Error #411. Failed to execute your requests." 121 | case .unprocessable: return "Error #422. Failed to execute your requests." 122 | case .tooManyRequests: return "Error #429. You made too many requests. Try again later." 123 | case .general: return "Error #500. Internal Server Error. Try again later." 124 | case .notImplemented: return "Error #501. Internal Server Error." 125 | case .badGateway: return "Error #502. Internal Server Error." 126 | case .unavailable: return "Error #503. Server temporary unavailable. Try again later." 127 | default: return "Unspecified error occured." 128 | } 129 | } 130 | 131 | } 132 | 133 | public func == (lhs: FeathersNetworkError, rhs: FeathersNetworkError) -> Bool { 134 | switch (lhs, rhs) { 135 | case (.badRequest, .badRequest): return true 136 | case (.notAuthenticated, .notAuthenticated): return true 137 | case (.paymentError, .paymentError): return true 138 | case (.forbidden, .forbidden): return true 139 | case (.notFound, .notFound): return true 140 | case (.methodNotAllowed, .methodNotAllowed): return true 141 | case (.notAcceptable, .notAcceptable): return true 142 | case (.timeout, .timeout): return true 143 | case (.conflict, .conflict): return true 144 | case (.lengthRequired, .lengthRequired): return true 145 | case (.unprocessable, .unprocessable): return true 146 | case (.tooManyRequests, .tooManyRequests): return true 147 | case (.general, .general): return true 148 | case (.notImplemented, .notImplemented): return true 149 | case (.badGateway, .badGateway): return true 150 | case (.unavailable, .unavailable): return true 151 | case (.underlying(_), .underlying(_)): return true 152 | case (.unknown, .unknown): return true 153 | default: return false 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Feathers/Core/ServiceWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceWrapper.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/21/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | import enum Result.NoError 12 | 13 | /// Wraps a given service and executes all hooks and the service request. 14 | /// This class exists so that users can write their own services and 15 | /// not have to worry about running before, after, and error hooks themselves. 16 | /// By wrapping the given service, the service wrapper can manage all 17 | /// request execution. Also serves as a proxy to the underlying service. 18 | final public class ServiceWrapper: ServiceType { 19 | 20 | /// Weak reference to main feathers app. 21 | final private weak var app: Feathers? 22 | 23 | /// Internally wrapped service. 24 | final private let service: ServiceType 25 | 26 | // MARK: - Initialization 27 | 28 | internal init(service: ServiceType) { 29 | self.service = service 30 | } 31 | 32 | // MARK: - ServiceType 33 | 34 | final public func setup(app: Feathers, path: String) { 35 | self.app = app 36 | } 37 | 38 | final public var path: String { 39 | return service.path 40 | } 41 | 42 | final public var supportsRealtimeEvents: Bool { 43 | return service.supportsRealtimeEvents 44 | } 45 | 46 | /// Execute a request against the wrapped service and runs all hooks registered. 47 | /// 48 | /// The execution flow of this method is as follows: 49 | /// 50 | /// before hooks -> service request -> after hooks 51 | /// 52 | /// If at any point an error is emitted or `hookObject.error` is set manually, the chain 53 | /// immediately stops and runs the registered error hooks. 54 | /// 55 | /// If `hookObject.result` is set, the service request will be skipped and after hooks will run. 56 | /// - Parameter method: Service method to execute. 57 | /// - Returns: `SignalProducer` that emits a response. 58 | final public func request(_ method: Service.Method) -> SignalProducer { 59 | guard let application = app else { 60 | print("feathers: no application exists on the service. `setup` was not called.") 61 | return .interrupted 62 | } 63 | // Closure that maps all registered hooks into a single producer. 64 | let reduceHooksClosure: (SignalProducer, Hook) -> SignalProducer = { acc, current in 65 | return acc.flatMap(.concat) { value in 66 | return current.run(with: value) 67 | } 68 | } 69 | let beforeHookObject = HookObject(type: .before, app: application, service: service, method: method) 70 | // Get all the hooks 71 | let beforeHooks = service.hooks(for: .before)?.hooks(for: method) ?? [] 72 | let afterHooks = service.hooks(for: .after)?.hooks(for: method) ?? [] 73 | let errorHooks = service.hooks(for: .error)?.hooks(for: method) ?? [] 74 | // Build up the before chains 75 | let beforeChain = beforeHooks.reduce(SignalProducer(value: beforeHookObject), reduceHooksClosure) 76 | 77 | let chain = beforeChain.flatMap(.concat) { [weak self] hook -> SignalProducer in 78 | guard let vSelf = self else { 79 | return .interrupted 80 | } 81 | // If the result is set, skip the service request. 82 | if let _ = hook.result { 83 | let afterHookObject = hook.object(with: .after) 84 | let afterChain = afterHooks.reduce(SignalProducer(value: afterHookObject), reduceHooksClosure) 85 | return afterChain.flatMap(.concat) { 86 | return $0.result != nil ? SignalProducer(value: $0.result!) : SignalProducer(error: AnyFeathersError(FeathersNetworkError.unknown)) 87 | } 88 | // If the error is set, error out. 89 | } else if let error = hook.error { 90 | return SignalProducer(error: AnyFeathersError(error)) 91 | } else { 92 | // Otherwise, execute the service request. 93 | return vSelf.service.request(hook.method) 94 | // When the service request has completed and emitted a response, 95 | // run all the after hooks. 96 | .flatMap(.latest) { response -> SignalProducer in 97 | let afterHookObject = hook.object(with: .after).objectByAdding(result: response) 98 | let afterChain = afterHooks.reduce(SignalProducer(value: afterHookObject), reduceHooksClosure) 99 | return afterChain.flatMap(.concat) { value in 100 | return value.result != nil ? SignalProducer(value: value.result!) : SignalProducer(error: AnyFeathersError(FeathersNetworkError.unknown)) 101 | } 102 | } 103 | } 104 | } 105 | // If at any point along the chain an error is emitted, recover by running all the error hooks. 106 | return chain.flatMapError { [weak self] error -> SignalProducer in 107 | guard let vSelf = self else { return SignalProducer(error: AnyFeathersError(FeathersNetworkError.unknown)) } 108 | let errorHookObject = HookObject(type: .error, app: application, service: vSelf, method: method).objectByAdding(error: error.error) 109 | let errorChain = errorHooks.reduce(SignalProducer(value: errorHookObject), reduceHooksClosure) 110 | return errorChain.flatMap(.concat) { hookObject -> SignalProducer in 111 | // If the hook error exists, send that to the user, otherwise emit the original error that caused error hooks to run. 112 | return hookObject.error != nil ? SignalProducer(error: AnyFeathersError(hookObject.error!)) : SignalProducer(error: AnyFeathersError(error.error)) 113 | } 114 | } 115 | } 116 | 117 | final public func before(_ hooks: Service.Hooks) { 118 | service.before(hooks) 119 | } 120 | 121 | final public func after(_ hooks: Service.Hooks) { 122 | service.after(hooks) 123 | } 124 | 125 | final public func error(_ hooks: Service.Hooks) { 126 | service.error(hooks) 127 | } 128 | 129 | final public func hooks(for kind: HookObject.Kind) -> Service.Hooks? { 130 | return service.hooks(for: kind) 131 | } 132 | 133 | final public func on(event: String) -> Signal<[String: Any], NoError> { 134 | return service.on(event: event) 135 | } 136 | 137 | final public func once(event: String) -> Signal<[String: Any], NoError> { 138 | return service.once(event: event) 139 | } 140 | 141 | final public func off(event: String) { 142 | service.off(event: event) 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Feathers/Core/Service.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Service.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 4/15/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | import Result 12 | 13 | /// Represents a subclassable Feather's service. `Service` is not to be 14 | /// used by itself; it's an abstract base class for all other service to inherit from. 15 | /// To use a service, see `ProviderService` or create your own! 16 | open class Service: ServiceType { 17 | 18 | /// Service methods. 19 | /// 20 | /// - find: Retrieves a list of all resources from the service, can be filtered. 21 | /// - get: Retrieves a single resource with the given id from the service. 22 | /// - create: Creates a new resource with data. 23 | /// - update: Replaces the resource identified by id with data. 24 | /// `id` may be nil when updating a list, indicating multiple entities with query parameters. 25 | /// - patch: Merges the existing data of the resource identified by id with the new data. 26 | /// `id` may be nil when patching a list, indicating multiple entities with query parameters. 27 | /// - remove: Removes the resource with id. `id` may be nil when deleting a list, 28 | /// indicating multiple entities with query parameters. 29 | public enum Method { 30 | 31 | case find(query: Query?) 32 | case get(id: String, query: Query?) 33 | case create(data: [String: Any], query: Query?) 34 | case update(id: String?, data: [String: Any], query: Query?) 35 | case patch(id: String?, data: [String: Any], query: Query?) 36 | case remove(id: String?, query: Query?) 37 | 38 | } 39 | 40 | // MARK: - Hooks 41 | 42 | /// Service hooks that can be registered with the service. 43 | public struct Hooks { 44 | 45 | /// Hooks for all service methods. 46 | public let all: [Hook] 47 | 48 | /// Hooks for `.find` requests. 49 | public let find: [Hook] 50 | 51 | /// Hooks for `.get` requests. 52 | public let get: [Hook] 53 | 54 | /// Hooks for `.create` requests. 55 | public let create: [Hook] 56 | 57 | /// Hooks for `.update` requests. 58 | public let update: [Hook] 59 | 60 | /// Hooks for `.patch` requests. 61 | public let patch: [Hook] 62 | 63 | /// Hooks for `.remove` requests. 64 | public let remove: [Hook] 65 | 66 | /// True if the object contains any hooks, false otherwise. 67 | public var isEmpty: Bool { 68 | return all.isEmpty 69 | && find.isEmpty 70 | && get.isEmpty 71 | && create.isEmpty 72 | && update.isEmpty 73 | && patch.isEmpty 74 | && remove.isEmpty 75 | } 76 | 77 | /// Service hooks initializer. 78 | /// 79 | /// - Parameters: 80 | /// - kind: The kind of hooks. 81 | /// - all: Hooks to run on all service methods. 82 | /// - find: Find hooks. 83 | /// - get: Get hooks. 84 | /// - create: Create hooks. 85 | /// - update: Update hooks. 86 | /// - patch: Patch hooks. 87 | /// - remove: Remove hooks. 88 | public init( 89 | all: [Hook] = [], 90 | find: [Hook] = [], 91 | get: [Hook] = [], 92 | create: [Hook] = [], 93 | update: [Hook] = [], 94 | patch: [Hook] = [], 95 | remove: [Hook] = []) { 96 | self.all = all 97 | self.find = find 98 | self.get = get 99 | self.create = create 100 | self.update = update 101 | self.patch = patch 102 | self.remove = remove 103 | } 104 | 105 | /// Create a new hook object by merging hooks together. 106 | /// 107 | /// - Parameter hooks: Hooks to add. 108 | /// - Returns: New `Hooks` object. 109 | public func add(hooks: Hooks) -> Hooks { 110 | return Hooks( 111 | all: all + hooks.all, 112 | find: find + hooks.find, 113 | get: get + hooks.get, 114 | create: create + hooks.create, 115 | update: update + hooks.update, 116 | patch: patch + hooks.patch, 117 | remove: remove + hooks.remove) 118 | } 119 | 120 | } 121 | 122 | /// Service before hooks. 123 | private var beforeHooks = Hooks() 124 | 125 | /// Servie after hooks. 126 | private var afterHooks = Hooks() 127 | 128 | /// Service error hooks. 129 | private var errorHooks = Hooks() 130 | 131 | /// Weak reference to the main feathers app. 132 | private(set) public weak var app: Feathers? 133 | 134 | // MARK: - Initialization 135 | 136 | public init() {} 137 | 138 | // MARK: - ServiceType 139 | 140 | /// The service path. 141 | private(set) public var path: String = "" 142 | 143 | open func setup(app: Feathers, path: String) { 144 | self.app = app 145 | self.path = path 146 | } 147 | 148 | open func request(_ method: Service.Method) -> SignalProducer { 149 | fatalError("Must be overriden by a subclass") 150 | } 151 | 152 | final public func before(_ hooks: Hooks) { 153 | beforeHooks = beforeHooks.add(hooks: hooks) 154 | } 155 | 156 | final public func after(_ hooks: Hooks) { 157 | afterHooks = afterHooks.add(hooks: hooks) 158 | } 159 | 160 | final public func error(_ hooks: Hooks) { 161 | errorHooks = errorHooks.add(hooks: hooks) 162 | } 163 | 164 | final public func hooks(for kind: HookObject.Kind) -> Service.Hooks? { 165 | switch kind { 166 | case .before: return beforeHooks.isEmpty ? nil : beforeHooks 167 | case .after: return afterHooks.isEmpty ? nil : afterHooks 168 | case .error: return errorHooks.isEmpty ? nil : errorHooks 169 | } 170 | } 171 | 172 | public func on(event: String) -> Signal<[String: Any], NoError> { 173 | // no-op 174 | return .empty 175 | } 176 | 177 | public func once(event: String) -> Signal<[String: Any], NoError> { 178 | return .empty 179 | } 180 | 181 | public func off(event: String) { 182 | // no-op 183 | } 184 | 185 | public var supportsRealtimeEvents: Bool { 186 | return false 187 | } 188 | 189 | } 190 | 191 | internal extension Service.Hooks { 192 | 193 | /// Internal extension for grabbing all the hooks for a given method. 194 | /// 195 | /// - Parameter method: Service method. 196 | /// - Returns: A list of hooks registered for that service method. 197 | func hooks(for method: Service.Method) -> [Hook] { 198 | switch method { 199 | case .find: return all + find 200 | case .get: return all + get 201 | case .create: return all + create 202 | case .update: return all + update 203 | case .patch: return all + patch 204 | case .remove: return all + remove 205 | } 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /FeathersTests/QuerySpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuerySpec.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/25/17. 6 | // Copyright © 2017 Swoopy Studios. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | import Foundation 12 | import Feathers 13 | 14 | class QuerySpec: QuickSpec { 15 | 16 | override func spec() { 17 | 18 | describe("Query") { 19 | 20 | it("should serialize a limit query") { 21 | let query = Query().limit(5) 22 | let serialized = query.serialize() 23 | expect(NSDictionary(dictionary: serialized).isEqual(to: ["$limit": 5])).to(beTrue()) 24 | } 25 | 26 | it("should serialize a skip query") { 27 | let query = Query().skip(5) 28 | let serialized = query.serialize() 29 | expect(NSDictionary(dictionary: serialized).isEqual(to: ["$skip": 5])).to(beTrue()) 30 | } 31 | 32 | it("should serialize a single sort") { 33 | let query = Query().sort(property: "name", ordering: .orderedAscending) 34 | let serialized = query.serialize() 35 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 36 | "$sort": [ 37 | "name": 1 38 | ] 39 | ])).to(beTrue()) 40 | } 41 | 42 | it("should serialize a multiple sorts") { 43 | let query = Query().sort(property: "name", ordering: .orderedAscending).sort(property: "age", ordering: .orderedDescending) 44 | let serialized = query.serialize() 45 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 46 | "$sort": [ 47 | "name": 1, 48 | "age": -1 49 | ] 50 | ])).to(beTrue()) 51 | } 52 | 53 | it("should serialize a gt query") { 54 | let query = Query().gt(property: "age", value: 5) 55 | let serialized = query.serialize() 56 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 57 | "age": [ 58 | "$gt": 5 59 | ] 60 | ])).to(beTrue()) 61 | } 62 | 63 | it("should serialize a gte query") { 64 | let query = Query().gte(property: "age", value: 5) 65 | let serialized = query.serialize() 66 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 67 | "age": [ 68 | "$gte": 5 69 | ] 70 | ])).to(beTrue()) 71 | } 72 | 73 | it("should serialize a lt query") { 74 | let query = Query().lt(property: "age", value: 5) 75 | let serialized = query.serialize() 76 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 77 | "age": [ 78 | "$lt": 5 79 | ] 80 | ])).to(beTrue()) 81 | } 82 | 83 | it("should serialize a lte query") { 84 | let query = Query().lte(property: "age", value: 5) 85 | let serialized = query.serialize() 86 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 87 | "age": [ 88 | "$lte": 5 89 | ] 90 | ])).to(beTrue()) 91 | } 92 | 93 | it("should serialize an in query") { 94 | let query = Query().in(property: "age", values: [5, 10, 15]) 95 | let serialized = query.serialize() 96 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 97 | "age": [ 98 | "$in": [5, 10, 15] 99 | ] 100 | ])).to(beTrue()) 101 | } 102 | 103 | it("should serialize a nin query") { 104 | let query = Query().nin(property: "age", values: [5, 10, 15]) 105 | let serialized = query.serialize() 106 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 107 | "age": [ 108 | "$nin": [5, 10, 15] 109 | ] 110 | ])).to(beTrue()) 111 | } 112 | 113 | it("should serialize a ne query") { 114 | let query = Query().ne(property: "age", value: 10) 115 | let serialized = query.serialize() 116 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 117 | "age": [ 118 | "$ne": 10 119 | ] 120 | ])).to(beTrue()) 121 | } 122 | 123 | it("should serialize an eq query") { 124 | let query = Query().eq(property: "age", value: 10) 125 | let serialized = query.serialize() 126 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 127 | "age": 10 128 | ])).to(beTrue()) 129 | } 130 | 131 | it("should serialize a select query") { 132 | let query = Query().select(property: "name") 133 | let serialized = query.serialize() 134 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 135 | "$select": ["name"] 136 | ])).to(beTrue()) 137 | } 138 | 139 | it("should serialize a select query with multiple fields") { 140 | let query = Query().select(properties: ["name", "age"]) 141 | let serialized = query.serialize() 142 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 143 | "$select": ["name", "age"] 144 | ])).to(beTrue()) 145 | } 146 | 147 | it("should serialize an or query") { 148 | let query = Query().or(subqueries: [ 149 | "name":.ne("bob"), 150 | "age": .`in`([18, 42]) 151 | ]) 152 | let serialized = query.serialize() 153 | print(serialized) 154 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 155 | "$or": [ 156 | [ 157 | "name": [ 158 | "$ne": "bob" 159 | ] 160 | ], 161 | [ 162 | "age": [ 163 | "$in": [18, 42] 164 | ] 165 | ] 166 | ] 167 | ])).to(beTrue()) 168 | } 169 | 170 | it("should serialize multiple queries") { 171 | let query = Query().limit(5).gt(property: "name", value: "bob").lt(property: "age", value: 5) 172 | let serialized = query.serialize() 173 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 174 | "$limit": 5, 175 | "name": [ 176 | "$gt": "bob" 177 | ], 178 | "age": [ 179 | "$lt": 5 180 | ] 181 | ])).to(beTrue()) 182 | } 183 | 184 | it("should serialize multiple subqueries on the same property") { 185 | let query = Query().gt(property: "age", value: 18).lt(property: "age", value: 100) 186 | let serialized = query.serialize() 187 | expect(NSDictionary(dictionary: serialized).isEqual(to: [ 188 | "age": [ 189 | "$gt": 18, 190 | "$lt": 100 191 | ] 192 | ])).to(beTrue()) 193 | } 194 | 195 | 196 | } 197 | 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /Feathers/Core/Query.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Query.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/25/17. 6 | // Copyright © 2017 Swoopy Studios. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Query { 12 | 13 | public struct Sort { 14 | let property: String 15 | let ordering: ComparisonResult 16 | } 17 | 18 | public enum PropertySubquery { 19 | case gt(Any) 20 | case gte(Any) 21 | case lt(Any) 22 | case lte(Any) 23 | case `in`([Any]) 24 | case nin([Any]) 25 | case ne(Any) 26 | case eq(Any) 27 | } 28 | 29 | public let limit: Int? 30 | public let skip: Int? 31 | public let sorts: [Sort] 32 | public let propertyQueries: [String: [PropertySubquery]] 33 | public let selected: [String] 34 | public let orQuery: [String: PropertySubquery] 35 | 36 | public init() { 37 | self.limit = nil 38 | self.skip = nil 39 | self.sorts = [] 40 | self.propertyQueries = [:] 41 | self.selected = [] 42 | self.orQuery = [:] 43 | } 44 | 45 | private init( 46 | limit: Int? = nil, 47 | skip: Int? = nil, 48 | sorts: [Sort] = [], 49 | propertyQueries: [String: [PropertySubquery]] = [:], 50 | selected: [String] = [], 51 | orQuery: [String: PropertySubquery] = [:]) { 52 | self.limit = limit 53 | self.skip = skip 54 | self.sorts = sorts 55 | self.propertyQueries = propertyQueries 56 | self.selected = selected 57 | self.orQuery = orQuery 58 | } 59 | 60 | public func limit(_ newLimit: Int) -> Query { 61 | return Query(limit: newLimit, skip: skip, sorts: sorts, propertyQueries: propertyQueries, selected: selected, orQuery: orQuery) 62 | } 63 | 64 | public func skip(_ newSkip: Int) -> Query { 65 | return Query(limit: limit, skip: newSkip, sorts: sorts, propertyQueries: propertyQueries, selected: selected, orQuery: orQuery) 66 | } 67 | 68 | public func gt(property: String, value: Any) -> Query { 69 | var queries = propertyQueries 70 | queries[property] = queries[property] == nil ? [] : queries[property] 71 | queries[property]?.append(.gt(value)) 72 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 73 | } 74 | 75 | public func gte(property: String, value: Any) -> Query { 76 | var queries = propertyQueries 77 | queries[property] = queries[property] == nil ? [] : queries[property] 78 | queries[property]?.append(.gte(value)) 79 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 80 | } 81 | 82 | public func lt(property: String, value: Any) -> Query { 83 | var queries = propertyQueries 84 | queries[property] = queries[property] == nil ? [] : queries[property] 85 | queries[property]?.append(.lt(value)) 86 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 87 | } 88 | 89 | public func lte(property: String, value: Any) -> Query { 90 | var queries = propertyQueries 91 | queries[property] = queries[property] == nil ? [] : queries[property] 92 | queries[property]?.append(.lte(value)) 93 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 94 | } 95 | 96 | public func `in`(property: String, values: [Any]) -> Query { 97 | var queries = propertyQueries 98 | queries[property] = queries[property] == nil ? [] : queries[property] 99 | queries[property]?.append(.`in`(values)) 100 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 101 | } 102 | 103 | public func nin(property: String, values: [Any]) -> Query { 104 | var queries = propertyQueries 105 | queries[property] = queries[property] == nil ? [] : queries[property] 106 | queries[property]?.append(.nin(values)) 107 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 108 | } 109 | 110 | public func eq(property: String, value: Any) -> Query { 111 | var queries = propertyQueries 112 | queries[property] = queries[property] == nil ? [] : queries[property] 113 | queries[property]?.append(.eq(value)) 114 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 115 | } 116 | 117 | public func ne(property: String, value: Any) -> Query { 118 | var queries = propertyQueries 119 | queries[property] = queries[property] == nil ? [] : queries[property] 120 | queries[property]?.append(.ne(value)) 121 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: queries, selected: selected, orQuery: orQuery) 122 | } 123 | 124 | public func sort(property: String, ordering: ComparisonResult) -> Query { 125 | var sortList = sorts 126 | sortList.append(Sort(property: property, ordering: ordering)) 127 | return Query(limit: limit, skip: skip, sorts: sortList, propertyQueries: propertyQueries, selected: selected, orQuery: orQuery) 128 | } 129 | 130 | public func select(property: String) -> Query { 131 | var selectedFields = selected 132 | selectedFields.append(property) 133 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: propertyQueries, selected: selectedFields, orQuery: orQuery) 134 | } 135 | 136 | public func select(properties: [String]) -> Query { 137 | var selectedFields = selected 138 | selectedFields += properties 139 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: propertyQueries, selected: selectedFields, orQuery: orQuery) 140 | } 141 | 142 | public func or(subqueries: [String: PropertySubquery]) -> Query { 143 | return Query(limit: limit, skip: skip, sorts: sorts, propertyQueries: propertyQueries, selected: selected, orQuery: subqueries) 144 | } 145 | 146 | public func serialize() -> [String: Any] { 147 | var dictionary: [String: Any] = [:] 148 | // Add any limit 149 | if let limit = limit { 150 | dictionary["$limit"] = limit 151 | } 152 | // Add any skip 153 | if let skip = skip { 154 | dictionary["$skip"] = skip 155 | } 156 | // Seralize sorts 157 | for sort in sorts { 158 | var previousSorts = dictionary["$sort"] as? [String: Int] ?? [:] 159 | switch sort.ordering { 160 | case .orderedAscending: 161 | previousSorts[sort.property] = 1 162 | case .orderedDescending: 163 | previousSorts[sort.property] = -1 164 | case .orderedSame: 165 | previousSorts[sort.property] = 0 166 | } 167 | dictionary["$sort"] = previousSorts 168 | } 169 | for (property, subqueries) in propertyQueries { 170 | var propertyQueries = dictionary[property] as? [String: Any] ?? [:] 171 | var isSubquery = true 172 | for subquery in subqueries { 173 | switch subquery { 174 | case let .gt(value): 175 | propertyQueries["$gt"] = value 176 | case let .gte(value): 177 | propertyQueries["$gte"] = value 178 | case let .lt(value): 179 | propertyQueries["$lt"] = value 180 | case let .lte(value): 181 | propertyQueries["$lte"] = value 182 | case let .`in`(values): 183 | propertyQueries["$in"] = values 184 | case let .nin(values): 185 | propertyQueries["$nin"] = values 186 | case let .ne(value): 187 | propertyQueries["$ne"] = value 188 | case let .eq(value): 189 | dictionary[property] = value 190 | isSubquery = false 191 | } 192 | } 193 | if isSubquery { 194 | dictionary[property] = propertyQueries 195 | } 196 | } 197 | 198 | if !selected.isEmpty { 199 | dictionary["$select"] = selected 200 | } 201 | 202 | var orQueries: [[String: Any]] = [] 203 | for (property, subquery) in orQuery { 204 | var propertyQuery: [String: Any] = [:] 205 | switch subquery { 206 | case let .gt(value): 207 | propertyQuery[property] = ["$gt": value] 208 | case let .gte(value): 209 | propertyQuery[property] = ["$gte": value] 210 | case let .lt(value): 211 | propertyQuery[property] = ["$lt": value] 212 | case let .lte(value): 213 | propertyQuery[property] = ["$lte": value] 214 | case let .`in`(values): 215 | propertyQuery[property] = ["$in": values] 216 | case let .nin(values): 217 | propertyQuery[property] = ["$nin": values] 218 | case let .ne(value): 219 | propertyQuery[property] = ["$ne": value] 220 | case let .eq(value): 221 | propertyQuery[property] = value 222 | } 223 | orQueries.append(propertyQuery) 224 | } 225 | 226 | if !orQueries.isEmpty { 227 | dictionary["$or"] = orQueries 228 | } 229 | 230 | return dictionary 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /FeathersTests/ServiceSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceSpec.swift 3 | // Feathers 4 | // 5 | // Created by Brendan Conron on 5/18/17. 6 | // Copyright © 2017 FeathersJS. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | import Feathers 12 | 13 | class ServiceSpec: QuickSpec { 14 | 15 | override func spec() { 16 | 17 | describe("Service") { 18 | 19 | var app: Feathers! 20 | var service: ServiceType! 21 | 22 | beforeEach { 23 | app = Feathers(provider: StubProvider(data: ["name": "Bob"])) 24 | service = app.service(path: "users") 25 | } 26 | 27 | it("should stub the request") { 28 | print(service) 29 | var error: Error? 30 | var response: Response? 31 | var data: [String: String]? 32 | service.request(.find(query: nil)) 33 | .on(failed: { 34 | error = $0 35 | }, value: { 36 | response = $0 37 | data = $0.data.value as? [String: String] 38 | print($0) 39 | }) 40 | .start() 41 | expect(error).toEventually(beNil()) 42 | expect(response).toEventuallyNot(beNil()) 43 | expect(data).toEventuallyNot(beNil()) 44 | expect(data).to(equal(["name": "Bob"])) 45 | } 46 | 47 | describe("Hooks") { 48 | 49 | context("before") { 50 | 51 | var beforeHooks: Service.Hooks! 52 | 53 | beforeEach { 54 | beforeHooks = Service.Hooks(all: [StubHook(data: .object(["name": "Henry"]))]) 55 | service.before(beforeHooks) 56 | } 57 | 58 | it("should run the before hook and skip the request") { 59 | var error: Error? 60 | var response: Response? 61 | var data: [String: String]? 62 | service.request(.find(query: nil)) 63 | .on(failed: { 64 | error = $0 65 | }, value: { 66 | response = $0 67 | data = $0.data.value as? [String: String] 68 | }) 69 | .start() 70 | expect(error).toEventually(beNil()) 71 | expect(response).toEventuallyNot(beNil()) 72 | expect(data).toEventuallyNot(beNil()) 73 | expect(data).to(equal(["name": "Henry"])) 74 | } 75 | 76 | } 77 | 78 | context("after") { 79 | 80 | var afterHooks: Service.Hooks! 81 | 82 | beforeEach { 83 | afterHooks = Service.Hooks(all: [PopuplateDataAfterHook(data: ["name": "Susie"])]) 84 | service.after(afterHooks) 85 | } 86 | 87 | it("should change the response") { 88 | var error: Error? 89 | var response: Response? 90 | var data: [String: String]? 91 | service.request(.find(query: nil)) 92 | .on(failed: { 93 | error = $0 94 | }, value: { 95 | response = $0 96 | data = $0.data.value as? [String: String] 97 | }) 98 | .start() 99 | expect(error).toEventually(beNil()) 100 | expect(response).toEventuallyNot(beNil()) 101 | expect(data).toEventuallyNot(beNil()) 102 | expect(data).to(equal(["name": "Susie"])) 103 | } 104 | 105 | } 106 | 107 | context("error") { 108 | 109 | var beforeHooks: Service.Hooks! 110 | 111 | beforeEach { 112 | // Force the hook to error with ErrorHook 113 | beforeHooks = Service.Hooks(all: [ErrorHook(error:FeathersNetworkError.unknown)]) 114 | service.before(beforeHooks) 115 | } 116 | 117 | context("when a hook rejects with an error") { 118 | 119 | it("should pass through the original error") { 120 | var error: FeathersNetworkError? 121 | var response: Response? 122 | var data: [String: String]? 123 | service.request(.find(query: nil)) 124 | .on(failed: { 125 | error = $0.error as? FeathersNetworkError 126 | }, value: { 127 | response = $0 128 | data = $0.data.value as? [String: String] 129 | }) 130 | .start() 131 | expect(error).toEventuallyNot(beNil()) 132 | expect(error).toEventually(equal(FeathersNetworkError.unknown)) 133 | expect(response).toEventually(beNil()) 134 | expect(data).toEventually(beNil()) 135 | } 136 | 137 | context("when given an error hook that rejects with an error") { 138 | 139 | var errorHooks: Service.Hooks! 140 | 141 | beforeEach { 142 | errorHooks = Service.Hooks(all: [ErrorHook(error: FeathersNetworkError.unavailable), ErrorHook(error: FeathersNetworkError.unknown)]) 143 | service.error(errorHooks) 144 | } 145 | 146 | it("should be able to modify the final error and skip the rest of the chain") { 147 | var error: FeathersNetworkError? 148 | var response: Response? 149 | var data: [String: String]? 150 | service.request(.find(query: nil)) 151 | .on(failed: { 152 | error = $0.error as? FeathersNetworkError 153 | }, value: { 154 | response = $0 155 | data = $0.data.value as? [String: String] 156 | }) 157 | .start() 158 | expect(error).toEventuallyNot(beNil()) 159 | expect(error).toEventually(equal(FeathersNetworkError.unavailable)) 160 | expect(response).toEventually(beNil()) 161 | expect(data).toEventually(beNil()) 162 | } 163 | 164 | } 165 | 166 | context("when given an error hook that modifies the hook error") { 167 | 168 | var errorHooks: Service.Hooks! 169 | 170 | beforeEach { 171 | errorHooks = Service.Hooks(all: [ModifyErrorHook(error: FeathersNetworkError.unavailable)]) 172 | service.error(errorHooks) 173 | } 174 | 175 | it("should be able to modify the final error") { 176 | var error: FeathersNetworkError? 177 | var response: Response? 178 | var data: [String: String]? 179 | service.request(.find(query: nil)) 180 | .on(failed: { 181 | error = $0.error as? FeathersNetworkError 182 | }, value: { 183 | response = $0 184 | data = $0.data.value as? [String: String] 185 | }) 186 | .start() 187 | expect(error).toEventuallyNot(beNil()) 188 | expect(error).toEventually(equal(FeathersNetworkError.unavailable)) 189 | expect(response).toEventually(beNil()) 190 | expect(data).toEventually(beNil()) 191 | } 192 | 193 | context("with multiple error hooks that modify the error ") { 194 | 195 | beforeEach { 196 | service.error(Service.Hooks(all: [ModifyErrorHook(error: FeathersNetworkError.unknown)])) 197 | } 198 | 199 | it("should pass back the final error") { 200 | var error: FeathersNetworkError? 201 | var response: Response? 202 | var data: [String: String]? 203 | service.request(.find(query: nil)) 204 | .on(failed: { 205 | error = $0.error as? FeathersNetworkError 206 | }, value: { 207 | response = $0 208 | data = $0.data.value as? [String: String] 209 | }) 210 | .start() 211 | expect(error).toEventuallyNot(beNil()) 212 | expect(error).toEventually(equal(FeathersNetworkError.unknown)) 213 | expect(response).toEventually(beNil()) 214 | expect(data).toEventually(beNil()) 215 | } 216 | 217 | } 218 | 219 | } 220 | 221 | } 222 | 223 | } 224 | 225 | } 226 | 227 | } 228 | 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FeathersSwift 2 | 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](#carthage) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/Feathers.svg)](#cocoapods) [![GitHub release](https://img.shields.io/github/release/feathersjs-ecosystem/feathers-swift.svg)](https://github.com/feathersjs-ecosystem/feathers-swift/releases) ![Swift 4.0.x](https://img.shields.io/badge/Swift-4.0.x-orange.svg) ![platforms](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg) ![build](https://travis-ci.org/feathersjs-ecosystem/feathers-swift.svg?branch=master) 4 | 5 | ![feathers](https://media.giphy.com/media/Fn8LZVVgTqXba/giphy.gif) 6 | 7 | ## What is FeathersSwift? 8 | 9 | FeathersSwift is a Cocoa networking library for interacting with a [FeathersJS](https://feathersjs.com/) backend. 10 | Why should you use it? 11 | 12 | * Swift 4 :thumbsup: 13 | * Network abstraction layer 14 | * Integrates seemlessly with any FeathersJS services 15 | * Supports iOS, macOS, tvOS, and watchOS 16 | * Reactive API ([ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift)) 17 | 18 | If you use FeathersJS (which you should), FeathersSwift is the perfect choice for you. No more dealing with HTTP requests or socket clients. One simple interface to rule them all and in the darkness, unify them :ring:. 19 | 20 | ## Installation 21 | 22 | ### Cocoapods 23 | ``` 24 | pod `Feathers` 25 | ``` 26 | 27 | ### Carthage 28 | 29 | Add the following line to your Cartfile: 30 | 31 | ``` 32 | github "feathersjs/feathers-swift" 33 | ``` 34 | ## Getting Started 35 | 36 | FeathersSwift is spread out across multiple repositories to ensure that you're only pulling in exactly what you need and no more. There are two Feathers providers, [feathers-swift-rest](https://github.com/feathersjs-ecosystem/feathers-swift-rest) and [feathers-swift-socketio](https://github.com/feathersjs-ecosystem/feathers-swift-socketio). Install either provider using the instructions on their respective READMEs. 37 | 38 | Once you've install a provider, either rest of socketio, an instance of a `Feathers` application with it: 39 | 40 | ```swift 41 | 42 | let feathersRestApp = Feathers(RestProvider(baseURL: URL(string: "https://myserver.com"))) 43 | 44 | ``` 45 | 46 | Then grab a service: 47 | 48 | ```swift 49 | let userService = feathersRestApp.service("users") 50 | ``` 51 | 52 | Finally, make a request: 53 | 54 | ```swift 55 | service.request(.find(parameters: ["name": "Waldo"])) 56 | .on(value: { response in 57 | print(response) 58 | }) 59 | .start() 60 | ``` 61 | 62 | FeathersSwift's API is built entirely using [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift), an awesome functional-reactive library. Because promises aren't standard in Swift, we had to find an alternative that offers similar usage patterns. By doing this, we can avoid the pyramid of doom and endlessly nesting callbacks, instead offering a simplified reactive API. 63 | 64 | ### Service 65 | 66 | There are six types of requests you can make that correspond with Feathers service methods: 67 | 68 | ```swift 69 | public enum Method { 70 | 71 | case find(query: Query?) 72 | case get(id: String, query: Query?) 73 | case create(data: [String: Any], query: Query?) 74 | case update(id: String?, data: [String: Any], query: Query?) 75 | case patch(id: String?, data: [String: Any], query: Query?) 76 | case remove(id: String?, query: Query?) 77 | 78 | } 79 | ``` 80 | 81 | With `.update`, `.patch`, and `.remove`, you may pass in nil for the id when you want to delete a list of entities. The list of entities is determined by the query you pass in. 82 | 83 | By default, FeathersSwift will return an instance of `ProviderService` which wraps the application's transport provider in a service. However, you can also register your own services: 84 | 85 | ```swift 86 | feathers.use("users-local", CoreDataService()) 87 | ``` 88 | 89 | All custom services must conform to `ServiceType`. Thankfully, that's easy due to the FeathersSwift provided `Service` class which handles things such as hook storage and no-op implementations of the required methods. 90 | 91 | A simple custom service might look like: 92 | 93 | ```swift 94 | class FileService: Service { 95 | 96 | public override func request(_ method: Service.Method) -> SignalProducer { 97 | let fileManager = FileManager.default 98 | switch method { 99 | case let .create(data, _): 100 | guard let id = data["id"] else { break } 101 | let fileData = NSKeyedArchiver.archiveData(withRootObject: data) 102 | fileManager.createFile(atPath: "\(path)/\(id)", contents: fileData, attributes: nil) 103 | default: break 104 | } 105 | } 106 | 107 | } 108 | ``` 109 | 110 | While a tiny example, custom services can be infinitely more complex and used anywhere in the hook process. Just call `hookObject.app.service("my-custom-service").request(.create(data: [:], parameters: nil))`. 111 | 112 | ### Querying 113 | 114 | You may have noticed instead of passing a dictionary of parameters through a request, FeathersSwift uses a `Query` object. The `Query` class has a simple and composable API for represent complex queries without messing around with dictionaries. It supports all the normal queries FeathersJS users have come to know and love such as `ne` or `or`, just in a simplified, type-safe manner. 115 | 116 | To create a query: 117 | 118 | ```swift 119 | let query = Query() 120 | .ne("age", 50) 121 | .limit(25) 122 | .skip(5) 123 | 124 | let service = feathers.service("users") 125 | 126 | service.request(.find(query)).start() 127 | 128 | ``` 129 | 130 | Gone are the days of wondering if you formatted your dictionary correctly, `Query` knows how to serialize itself and takes care of that for you. 131 | 132 | ### Authentication 133 | 134 | To authenticate your application with your Feathers back end: 135 | 136 | ```swift 137 | feathersRestApp.authenticate([ 138 | "strategy": "facebook-token", 139 | "access_token": "ACCESS_TOKEN" 140 | ]) 141 | .start() 142 | ``` 143 | 144 | Authentication returns a JWT payload which is cached by the application and used to make subsequent requests. Currently there is not a re-authentication mechanism but look for that in coming versions. 145 | 146 | To log out, simply call: 147 | 148 | ```swift 149 | feathersRestApp.logout().start() 150 | ``` 151 | 152 | ### Real-Time Events 153 | 154 | When using the socket provider, you can not only use it to call Feathers service methods, you can also listen for real-time events. Simply use [feathers-swift-socketio](https://github.com/feathersjs/feathers-swift-socketio) create a feathers application with an instance of `SocketProvider` and register for events using `.on` on your services. 155 | 156 | There are four different real-time events: 157 | 158 | ```swift 159 | public enum RealTimeEvent: String { 160 | 161 | case created 162 | case updated 163 | case patched 164 | case removed 165 | 166 | } 167 | ``` 168 | 169 | You can use these events to things like dynamically update the UI, save entities to a database, or just log that the event happened. 170 | 171 | ```swift 172 | let feathersSocket = Feathers(provider: SocketProvider(baseURL: URL(string: "https://myserver.com")!, configuration: [])) 173 | 174 | let userService = feathersSocket.service(path: "users") 175 | userService.on(.created) 176 | .observeValues { entity in 177 | print(entity) // Prints the object that was just created 178 | } 179 | ``` 180 | 181 | When you're finished, be sure to call `.off` to unregister from the event. Otherwise your completion block will be retained by the provider. 182 | 183 | ```swift 184 | userService.off(.created) 185 | ``` 186 | 187 | There's also a nifty `.once` function that does exactly what you expect; you listen for one event and one event only. 188 | 189 | ### Hooks 190 | 191 | ![hooks](https://media.giphy.com/media/ujGSCZeZs2yXu/giphy.gif) 192 | 193 | Like in FeathersJS, you can register `before`, `after`, and `error` hooks to run when requests are made. Possible use cases could include stubbing out a unit test with a before hook or simple logging. 194 | 195 | To create a hook, create an object that conforms to `Hook`: 196 | 197 | ```swift 198 | public protocol Hook { 199 | func run(with hookObject: HookObject) -> Promise 200 | } 201 | ``` 202 | 203 | A hook that logs all `create` events might look like this: 204 | 205 | ```swift 206 | struct CreateLogHook: Hook { 207 | 208 | func run(with hookObject: HookObject) -> Promise { 209 | var object = hookObject 210 | if object.method == .create { 211 | print("create happened") 212 | } 213 | return Promise(value: object) 214 | } 215 | 216 | } 217 | ``` 218 | 219 | Or you can do something more complex like a network call: 220 | 221 | ```swift 222 | struct FetchAssociatedUserHook: Hook { 223 | func run(with hookObject: HookObject) -> Promise { 224 | var object = hookObject 225 | guard object.app.service.path == "groups" else { 226 | return Promise(value: hookObject) 227 | } 228 | guard case var .get(id, parameters) = object.method else { 229 | return Promise(value: hookObject) 230 | } 231 | guard let userIdentifier = parameters["user_id"] as? String else { 232 | return Promise(error: .myCustomError("no associated user found when expected to exist")) 233 | } 234 | return object.app.service("users").request(.get(parameters: ["id": userIdentifier])).then { response in 235 | if case let .jsonObject(object) = response.data { 236 | parameters["user_id"] = object["id"] 237 | object.method = .get(id, parameters) 238 | } 239 | return Promise(value: object) 240 | } 241 | } 242 | } 243 | ``` 244 | 245 | Important to note is `var object = hookObject`. Swift function parameters are `let` constants so you first have to copy the object if you want to modify it. 246 | 247 | #### Hook Object 248 | 249 | The hook object gets passed around through hooks in succession. The interface matches the JS one fairly closely: 250 | 251 | ```swift 252 | /// Hook object that gets passed through hook functions 253 | public struct HookObject { 254 | 255 | /// Represents the kind of hook. 256 | /// 257 | /// - before: Hook is run before the request is made. 258 | /// - after: Hook is run after the request is made. 259 | /// - error: Runs when there's an error. 260 | public enum Kind { 261 | case before, after, error 262 | } 263 | 264 | /// The kind of hook. 265 | public let type: Kind 266 | 267 | /// Feathers application, used to retrieve other services. 268 | public let app: Feathers 269 | 270 | /// The service this hook currently runs on. 271 | public let service: Service 272 | 273 | /// The service method. 274 | public var method: Service.Method 275 | 276 | /// Error that can be set which will stop the hook processing chain and run a special chain of error hooks. 277 | public var error: FeathersError? 278 | 279 | /// Result of a successful method call, only in after hooks. 280 | public var result: Response? 281 | 282 | public init( 283 | type: Kind, 284 | app: Feathers, 285 | service: Service, 286 | method: Service.Method) { 287 | self.type = type 288 | self.app = app 289 | self.service = service 290 | self.method = method 291 | } 292 | 293 | } 294 | ``` 295 | 296 | All the `var` declarations are mutable and you can set and mutate them as needed in your hooks, including `.method` if you want to do things like swap out parameters or change the method call entirely (e.g. changing a `.get` to a `.find`). 297 | 298 | Important things to note about the hook object: 299 | - Setting `error` will cause the hook processing chain to stop and immediately run any error hooks. If that happens in a `before` hook, the request will also be skipped. 300 | - Setting `result` to some `Response` value in a `before` hook will skip the request, essentially stubbing it. 301 | 302 | #### Hook Registration 303 | 304 | To register your hooks, you first have to create a `Service.Hooks` object: 305 | 306 | ```swift 307 | let beforeHooks = Service.Hooks(all: [LogResultHook()], create: [AppendUserIdHook()]]) 308 | let afterHooks = Service.Hooks(find: [SaveInRealmHook()]) 309 | let errorHooks = Service.Hooks(all: [LogErrorHook(destination: "log.txt")]) 310 | ``` 311 | 312 | Registering the hooks is just as easy: 313 | 314 | ```swift 315 | let service = app.service("users") 316 | service.before(beforeHooks) 317 | service.after(afterHooks) 318 | service.error(errorHooks) 319 | ``` 320 | 321 | **Important**: The hooks registered for `all` are run first, then the hooks for the particular service method. 322 | 323 | If at any point you need to inspect your hooks, you can do that too using `.hooks`: 324 | 325 | ```swift 326 | 327 | let beforeHooks = service.hooks(for: .before) 328 | 329 | ``` 330 | 331 | ### Contributing 332 | 333 | Have an issue? Open an issue! Have an idea? Open a pull request! 334 | 335 | If you like the library, please :star: it! 336 | 337 | ### Further Help 338 | 339 | FeathersSwift is extensively documented so please take a look at the source code if you have any questions about how something works. 340 | You can also ping me in the [Feathers' slack team](http://slack.feathersjs.com/) @brendan. 341 | 342 | Cheers! :beers: 343 | -------------------------------------------------------------------------------- /Feathers.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EA0505A51ED9B262006B5CCB /* QuerySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0F01ED7E0FC00EA04F4 /* QuerySpec.swift */; }; 11 | EA0505A61ED9B292006B5CCB /* QuerySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0F01ED7E0FC00EA04F4 /* QuerySpec.swift */; }; 12 | EA0F0BB21ED0A6C9000517A4 /* FeathersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0BB11ED0A6C9000517A4 /* FeathersSpec.swift */; }; 13 | EA0F0C7C1ED0B25F000517A4 /* Feathers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C731ED0B25E000517A4 /* Feathers.framework */; }; 14 | EA0F0C8D1ED0B2D0000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C8A1ED0B2D0000517A4 /* KeychainSwift.framework */; }; 15 | EA0F0C8E1ED0B2D0000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C8B1ED0B2D0000517A4 /* ReactiveSwift.framework */; }; 16 | EA0F0C8F1ED0B2D0000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C8C1ED0B2D0000517A4 /* Result.framework */; }; 17 | EA0F0C901ED0B2DD000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C8A1ED0B2D0000517A4 /* KeychainSwift.framework */; }; 18 | EA0F0C911ED0B2DD000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C8B1ED0B2D0000517A4 /* ReactiveSwift.framework */; }; 19 | EA0F0C921ED0B2DD000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C8C1ED0B2D0000517A4 /* Result.framework */; }; 20 | EA0F0C951ED0B2E1000517A4 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C931ED0B2E1000517A4 /* Nimble.framework */; }; 21 | EA0F0C961ED0B2E1000517A4 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0C941ED0B2E1000517A4 /* Quick.framework */; }; 22 | EA0F0C9C1ED0B2EA000517A4 /* KeychainSwift.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = EA0F0C971ED0B2EA000517A4 /* KeychainSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 23 | EA0F0C9D1ED0B2EA000517A4 /* Nimble.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = EA0F0C981ED0B2EA000517A4 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 24 | EA0F0C9E1ED0B2EA000517A4 /* Quick.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = EA0F0C991ED0B2EA000517A4 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 25 | EA0F0C9F1ED0B2EA000517A4 /* ReactiveSwift.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = EA0F0C9A1ED0B2EA000517A4 /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 26 | EA0F0CA01ED0B2EA000517A4 /* Result.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = EA0F0C9B1ED0B2EA000517A4 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 27 | EA0F0CA41ED0B318000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CA11ED0B318000517A4 /* KeychainSwift.framework */; }; 28 | EA0F0CA51ED0B318000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CA21ED0B318000517A4 /* ReactiveSwift.framework */; }; 29 | EA0F0CA61ED0B318000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CA31ED0B318000517A4 /* Result.framework */; }; 30 | EA0F0CAC1ED0B38B000517A4 /* KeychainSwift.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CA91ED0B38B000517A4 /* KeychainSwift.framework.dSYM */; }; 31 | EA0F0CAD1ED0B38B000517A4 /* ReactiveSwift.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CAA1ED0B38B000517A4 /* ReactiveSwift.framework.dSYM */; }; 32 | EA0F0CAE1ED0B38B000517A4 /* Result.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CAB1ED0B38B000517A4 /* Result.framework.dSYM */; }; 33 | EA0F0CAF1ED0B3B3000517A4 /* StubProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA301AB91EC8CD0400114F19 /* StubProvider.swift */; }; 34 | EA0F0CB01ED0B3B3000517A4 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA301AB41EC8BEBD00114F19 /* Hooks.swift */; }; 35 | EA0F0CB11ED0B3B3000517A4 /* ServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA52954B1ECE912C0014F156 /* ServiceSpec.swift */; }; 36 | EA0F0CB21ED0B3B3000517A4 /* FeathersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0BB11ED0A6C9000517A4 /* FeathersSpec.swift */; }; 37 | EA0F0CB31ED0B3E2000517A4 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3017131EC7867E00114F19 /* Hooks.swift */; }; 38 | EA0F0CB41ED0B3E2000517A4 /* LoggerHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA954C771EC9BEEC0094FF43 /* LoggerHooks.swift */; }; 39 | EA0F0CB51ED0B3E2000517A4 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE121EBFA6A400522603 /* Service.swift */; }; 40 | EA0F0CB61ED0B3E2000517A4 /* AuthenticationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0A1EBFA6A400522603 /* AuthenticationConfiguration.swift */; }; 41 | EA0F0CB71ED0B3E2000517A4 /* AuthenticationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0B1EBFA6A400522603 /* AuthenticationStorage.swift */; }; 42 | EA0F0CB81ED0B3E2000517A4 /* Feathers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0C1EBFA6A400522603 /* Feathers.swift */; }; 43 | EA0F0CB91ED0B3E2000517A4 /* FeathersError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0D1EBFA6A400522603 /* FeathersError.swift */; }; 44 | EA0F0CBA1ED0B3E2000517A4 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0F1EBFA6A400522603 /* Provider.swift */; }; 45 | EA0F0CBB1ED0B3E2000517A4 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE101EBFA6A400522603 /* Response.swift */; }; 46 | EA0F0CBC1ED0B3E2000517A4 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE05D691EBFDB0000DA55BF /* Endpoint.swift */; }; 47 | EA0F0CBD1ED0B400000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CA11ED0B318000517A4 /* KeychainSwift.framework */; }; 48 | EA0F0CBE1ED0B400000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CA21ED0B318000517A4 /* ReactiveSwift.framework */; }; 49 | EA0F0CBF1ED0B400000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CA31ED0B318000517A4 /* Result.framework */; }; 50 | EA0F0CC21ED0B400000517A4 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CC01ED0B400000517A4 /* Nimble.framework */; }; 51 | EA0F0CC31ED0B400000517A4 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CC11ED0B400000517A4 /* Quick.framework */; }; 52 | EA0F0CCA1ED0B413000517A4 /* KeychainSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CC51ED0B413000517A4 /* KeychainSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 53 | EA0F0CCB1ED0B413000517A4 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CC61ED0B413000517A4 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 54 | EA0F0CCC1ED0B413000517A4 /* Quick.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CC71ED0B413000517A4 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 55 | EA0F0CCD1ED0B413000517A4 /* ReactiveSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CC81ED0B413000517A4 /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 56 | EA0F0CCE1ED0B413000517A4 /* Result.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0CC91ED0B413000517A4 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 57 | EA0F0CDD1ED0B448000517A4 /* Feathers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CD41ED0B448000517A4 /* Feathers.framework */; }; 58 | EA0F0CEB1ED0B46B000517A4 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3017131EC7867E00114F19 /* Hooks.swift */; }; 59 | EA0F0CEC1ED0B46B000517A4 /* LoggerHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA954C771EC9BEEC0094FF43 /* LoggerHooks.swift */; }; 60 | EA0F0CED1ED0B46B000517A4 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE121EBFA6A400522603 /* Service.swift */; }; 61 | EA0F0CEE1ED0B46B000517A4 /* AuthenticationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0A1EBFA6A400522603 /* AuthenticationConfiguration.swift */; }; 62 | EA0F0CEF1ED0B46B000517A4 /* AuthenticationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0B1EBFA6A400522603 /* AuthenticationStorage.swift */; }; 63 | EA0F0CF01ED0B46B000517A4 /* Feathers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0C1EBFA6A400522603 /* Feathers.swift */; }; 64 | EA0F0CF11ED0B46B000517A4 /* FeathersError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0D1EBFA6A400522603 /* FeathersError.swift */; }; 65 | EA0F0CF21ED0B46B000517A4 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0F1EBFA6A400522603 /* Provider.swift */; }; 66 | EA0F0CF31ED0B46B000517A4 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE101EBFA6A400522603 /* Response.swift */; }; 67 | EA0F0CF41ED0B46B000517A4 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE05D691EBFDB0000DA55BF /* Endpoint.swift */; }; 68 | EA0F0CF51ED0B46F000517A4 /* StubProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA301AB91EC8CD0400114F19 /* StubProvider.swift */; }; 69 | EA0F0CF61ED0B46F000517A4 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA301AB41EC8BEBD00114F19 /* Hooks.swift */; }; 70 | EA0F0CF71ED0B46F000517A4 /* ServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA52954B1ECE912C0014F156 /* ServiceSpec.swift */; }; 71 | EA0F0CF81ED0B46F000517A4 /* FeathersSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0BB11ED0A6C9000517A4 /* FeathersSpec.swift */; }; 72 | EA0F0CFC1ED0B48C000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CF91ED0B48C000517A4 /* KeychainSwift.framework */; }; 73 | EA0F0CFD1ED0B48C000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CFA1ED0B48C000517A4 /* ReactiveSwift.framework */; }; 74 | EA0F0CFE1ED0B48C000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CFB1ED0B48C000517A4 /* Result.framework */; }; 75 | EA0F0D001ED0B4C7000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CF91ED0B48C000517A4 /* KeychainSwift.framework */; }; 76 | EA0F0D011ED0B4C7000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CFA1ED0B48C000517A4 /* ReactiveSwift.framework */; }; 77 | EA0F0D021ED0B4C7000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0CFB1ED0B48C000517A4 /* Result.framework */; }; 78 | EA0F0D051ED0B4C7000517A4 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0D031ED0B4C7000517A4 /* Nimble.framework */; }; 79 | EA0F0D061ED0B4C7000517A4 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0D041ED0B4C7000517A4 /* Quick.framework */; }; 80 | EA0F0D0D1ED0B4D7000517A4 /* KeychainSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0D081ED0B4D7000517A4 /* KeychainSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 81 | EA0F0D0E1ED0B4D7000517A4 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0D091ED0B4D7000517A4 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 82 | EA0F0D0F1ED0B4D7000517A4 /* Quick.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0D0A1ED0B4D7000517A4 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 83 | EA0F0D101ED0B4D7000517A4 /* ReactiveSwift.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0D0B1ED0B4D7000517A4 /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84 | EA0F0D111ED0B4D7000517A4 /* Result.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EA0F0D0C1ED0B4D7000517A4 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 85 | EA0F0D221ED0B535000517A4 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0D1F1ED0B535000517A4 /* KeychainSwift.framework */; }; 86 | EA0F0D231ED0B535000517A4 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0D201ED0B535000517A4 /* ReactiveSwift.framework */; }; 87 | EA0F0D241ED0B535000517A4 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0F0D211ED0B535000517A4 /* Result.framework */; }; 88 | EA0F0D251ED0B539000517A4 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3017131EC7867E00114F19 /* Hooks.swift */; }; 89 | EA0F0D261ED0B539000517A4 /* LoggerHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA954C771EC9BEEC0094FF43 /* LoggerHooks.swift */; }; 90 | EA0F0D271ED0B539000517A4 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE121EBFA6A400522603 /* Service.swift */; }; 91 | EA0F0D281ED0B539000517A4 /* AuthenticationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0A1EBFA6A400522603 /* AuthenticationConfiguration.swift */; }; 92 | EA0F0D291ED0B539000517A4 /* AuthenticationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0B1EBFA6A400522603 /* AuthenticationStorage.swift */; }; 93 | EA0F0D2A1ED0B539000517A4 /* Feathers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0C1EBFA6A400522603 /* Feathers.swift */; }; 94 | EA0F0D2B1ED0B539000517A4 /* FeathersError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0D1EBFA6A400522603 /* FeathersError.swift */; }; 95 | EA0F0D2C1ED0B539000517A4 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0F1EBFA6A400522603 /* Provider.swift */; }; 96 | EA0F0D2D1ED0B539000517A4 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE101EBFA6A400522603 /* Response.swift */; }; 97 | EA0F0D2E1ED0B539000517A4 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE05D691EBFDB0000DA55BF /* Endpoint.swift */; }; 98 | EA0F0D311ED290EF000517A4 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D301ED290EF000517A4 /* ServiceType.swift */; }; 99 | EA0F0D321ED290EF000517A4 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D301ED290EF000517A4 /* ServiceType.swift */; }; 100 | EA0F0D331ED290EF000517A4 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D301ED290EF000517A4 /* ServiceType.swift */; }; 101 | EA0F0D341ED290EF000517A4 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D301ED290EF000517A4 /* ServiceType.swift */; }; 102 | EA0F0D3B1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3A1ED2AA49000517A4 /* ServiceWrapper.swift */; }; 103 | EA0F0D3C1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3A1ED2AA49000517A4 /* ServiceWrapper.swift */; }; 104 | EA0F0D3D1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3A1ED2AA49000517A4 /* ServiceWrapper.swift */; }; 105 | EA0F0D3E1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3A1ED2AA49000517A4 /* ServiceWrapper.swift */; }; 106 | EA0F0D401ED2AB99000517A4 /* ProviderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3F1ED2AB99000517A4 /* ProviderService.swift */; }; 107 | EA0F0D411ED2AB99000517A4 /* ProviderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3F1ED2AB99000517A4 /* ProviderService.swift */; }; 108 | EA0F0D421ED2AB99000517A4 /* ProviderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3F1ED2AB99000517A4 /* ProviderService.swift */; }; 109 | EA0F0D431ED2AB99000517A4 /* ProviderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D3F1ED2AB99000517A4 /* ProviderService.swift */; }; 110 | EA0F0D461ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D451ED3F87F000517A4 /* SignalProducer+Convenience.swift */; }; 111 | EA0F0D471ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D451ED3F87F000517A4 /* SignalProducer+Convenience.swift */; }; 112 | EA0F0D481ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D451ED3F87F000517A4 /* SignalProducer+Convenience.swift */; }; 113 | EA0F0D491ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0F0D451ED3F87F000517A4 /* SignalProducer+Convenience.swift */; }; 114 | EA12E0631ECE950100A0DCF7 /* Feathers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAC0BCD21EC90FDB0049FB13 /* Feathers.framework */; }; 115 | EA12E0691ECE950C00A0DCF7 /* ServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA52954B1ECE912C0014F156 /* ServiceSpec.swift */; }; 116 | EA12E06A1ECE952400A0DCF7 /* StubProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA301AB91EC8CD0400114F19 /* StubProvider.swift */; }; 117 | EA12E06B1ECE952400A0DCF7 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA301AB41EC8BEBD00114F19 /* Hooks.swift */; }; 118 | EA8CE0EC1ED7DC2800EA04F4 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0EB1ED7DC2800EA04F4 /* Query.swift */; }; 119 | EA8CE0ED1ED7DC2800EA04F4 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0EB1ED7DC2800EA04F4 /* Query.swift */; }; 120 | EA8CE0EE1ED7DC2800EA04F4 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0EB1ED7DC2800EA04F4 /* Query.swift */; }; 121 | EA8CE0EF1ED7DC2800EA04F4 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0EB1ED7DC2800EA04F4 /* Query.swift */; }; 122 | EA8CE0F11ED7E0FC00EA04F4 /* QuerySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8CE0F01ED7E0FC00EA04F4 /* QuerySpec.swift */; }; 123 | EA954C781EC9BEEC0094FF43 /* LoggerHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA954C771EC9BEEC0094FF43 /* LoggerHooks.swift */; }; 124 | EAC0BCE91EC90FF30049FB13 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE121EBFA6A400522603 /* Service.swift */; }; 125 | EAC0BCEA1EC90FF30049FB13 /* AuthenticationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0A1EBFA6A400522603 /* AuthenticationConfiguration.swift */; }; 126 | EAC0BCEB1EC90FF30049FB13 /* AuthenticationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0B1EBFA6A400522603 /* AuthenticationStorage.swift */; }; 127 | EAC0BCEC1EC90FF30049FB13 /* Feathers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0C1EBFA6A400522603 /* Feathers.swift */; }; 128 | EAC0BCED1EC90FF30049FB13 /* FeathersError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0D1EBFA6A400522603 /* FeathersError.swift */; }; 129 | EAC0BCEF1EC90FF30049FB13 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE0F1EBFA6A400522603 /* Provider.swift */; }; 130 | EAC0BCF01EC90FF30049FB13 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABBAE101EBFA6A400522603 /* Response.swift */; }; 131 | EAC0BCF31EC90FF30049FB13 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE05D691EBFDB0000DA55BF /* Endpoint.swift */; }; 132 | EAC0BCF41EC90FF30049FB13 /* Hooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3017131EC7867E00114F19 /* Hooks.swift */; }; 133 | /* End PBXBuildFile section */ 134 | 135 | /* Begin PBXContainerItemProxy section */ 136 | EA0F0C7D1ED0B25F000517A4 /* PBXContainerItemProxy */ = { 137 | isa = PBXContainerItemProxy; 138 | containerPortal = EA452A5E1EA2B14300427CEF /* Project object */; 139 | proxyType = 1; 140 | remoteGlobalIDString = EA0F0C721ED0B25E000517A4; 141 | remoteInfo = "Feathers-macOS"; 142 | }; 143 | EA0F0CDE1ED0B448000517A4 /* PBXContainerItemProxy */ = { 144 | isa = PBXContainerItemProxy; 145 | containerPortal = EA452A5E1EA2B14300427CEF /* Project object */; 146 | proxyType = 1; 147 | remoteGlobalIDString = EA0F0CD31ED0B448000517A4; 148 | remoteInfo = "Feathers-tvOS"; 149 | }; 150 | EA12E0641ECE950100A0DCF7 /* PBXContainerItemProxy */ = { 151 | isa = PBXContainerItemProxy; 152 | containerPortal = EA452A5E1EA2B14300427CEF /* Project object */; 153 | proxyType = 1; 154 | remoteGlobalIDString = EAC0BCD11EC90FDB0049FB13; 155 | remoteInfo = "Feathers-iOS"; 156 | }; 157 | /* End PBXContainerItemProxy section */ 158 | 159 | /* Begin PBXCopyFilesBuildPhase section */ 160 | EA0F0BC01ED0ABA4000517A4 /* Copy Files */ = { 161 | isa = PBXCopyFilesBuildPhase; 162 | buildActionMask = 2147483647; 163 | dstPath = ""; 164 | dstSubfolderSpec = 10; 165 | files = ( 166 | EA0F0C9C1ED0B2EA000517A4 /* KeychainSwift.framework in Copy Files */, 167 | EA0F0C9D1ED0B2EA000517A4 /* Nimble.framework in Copy Files */, 168 | EA0F0C9E1ED0B2EA000517A4 /* Quick.framework in Copy Files */, 169 | EA0F0C9F1ED0B2EA000517A4 /* ReactiveSwift.framework in Copy Files */, 170 | EA0F0CA01ED0B2EA000517A4 /* Result.framework in Copy Files */, 171 | ); 172 | name = "Copy Files"; 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | EA0F0CA81ED0B380000517A4 /* CopyFiles */ = { 176 | isa = PBXCopyFilesBuildPhase; 177 | buildActionMask = 2147483647; 178 | dstPath = ""; 179 | dstSubfolderSpec = 16; 180 | files = ( 181 | EA0F0CAC1ED0B38B000517A4 /* KeychainSwift.framework.dSYM in CopyFiles */, 182 | EA0F0CAD1ED0B38B000517A4 /* ReactiveSwift.framework.dSYM in CopyFiles */, 183 | EA0F0CAE1ED0B38B000517A4 /* Result.framework.dSYM in CopyFiles */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | EA0F0CC41ED0B403000517A4 /* CopyFiles */ = { 188 | isa = PBXCopyFilesBuildPhase; 189 | buildActionMask = 2147483647; 190 | dstPath = ""; 191 | dstSubfolderSpec = 10; 192 | files = ( 193 | EA0F0CCA1ED0B413000517A4 /* KeychainSwift.framework in CopyFiles */, 194 | EA0F0CCB1ED0B413000517A4 /* Nimble.framework in CopyFiles */, 195 | EA0F0CCC1ED0B413000517A4 /* Quick.framework in CopyFiles */, 196 | EA0F0CCD1ED0B413000517A4 /* ReactiveSwift.framework in CopyFiles */, 197 | EA0F0CCE1ED0B413000517A4 /* Result.framework in CopyFiles */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | EA0F0D071ED0B4CC000517A4 /* CopyFiles */ = { 202 | isa = PBXCopyFilesBuildPhase; 203 | buildActionMask = 2147483647; 204 | dstPath = ""; 205 | dstSubfolderSpec = 10; 206 | files = ( 207 | EA0F0D0D1ED0B4D7000517A4 /* KeychainSwift.framework in CopyFiles */, 208 | EA0F0D0E1ED0B4D7000517A4 /* Nimble.framework in CopyFiles */, 209 | EA0F0D0F1ED0B4D7000517A4 /* Quick.framework in CopyFiles */, 210 | EA0F0D101ED0B4D7000517A4 /* ReactiveSwift.framework in CopyFiles */, 211 | EA0F0D111ED0B4D7000517A4 /* Result.framework in CopyFiles */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXCopyFilesBuildPhase section */ 216 | 217 | /* Begin PBXFileReference section */ 218 | EA0F0BB11ED0A6C9000517A4 /* FeathersSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeathersSpec.swift; path = FeathersTests/FeathersSpec.swift; sourceTree = SOURCE_ROOT; }; 219 | EA0F0C731ED0B25E000517A4 /* Feathers.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Feathers.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 220 | EA0F0C7B1ED0B25F000517A4 /* Feathers-macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feathers-macOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 221 | EA0F0C8A1ED0B2D0000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/iOS/KeychainSwift.framework; sourceTree = ""; }; 222 | EA0F0C8B1ED0B2D0000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/iOS/ReactiveSwift.framework; sourceTree = ""; }; 223 | EA0F0C8C1ED0B2D0000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = ""; }; 224 | EA0F0C931ED0B2E1000517A4 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = ""; }; 225 | EA0F0C941ED0B2E1000517A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = ""; }; 226 | EA0F0C971ED0B2EA000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/iOS/KeychainSwift.framework; sourceTree = ""; }; 227 | EA0F0C981ED0B2EA000517A4 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = ""; }; 228 | EA0F0C991ED0B2EA000517A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = ""; }; 229 | EA0F0C9A1ED0B2EA000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/iOS/ReactiveSwift.framework; sourceTree = ""; }; 230 | EA0F0C9B1ED0B2EA000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = ""; }; 231 | EA0F0CA11ED0B318000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/Mac/KeychainSwift.framework; sourceTree = ""; }; 232 | EA0F0CA21ED0B318000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/Mac/ReactiveSwift.framework; sourceTree = ""; }; 233 | EA0F0CA31ED0B318000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/Mac/Result.framework; sourceTree = ""; }; 234 | EA0F0CA91ED0B38B000517A4 /* KeychainSwift.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = KeychainSwift.framework.dSYM; path = Carthage/Build/Mac/KeychainSwift.framework.dSYM; sourceTree = ""; }; 235 | EA0F0CAA1ED0B38B000517A4 /* ReactiveSwift.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = ReactiveSwift.framework.dSYM; path = Carthage/Build/Mac/ReactiveSwift.framework.dSYM; sourceTree = ""; }; 236 | EA0F0CAB1ED0B38B000517A4 /* Result.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Result.framework.dSYM; path = Carthage/Build/Mac/Result.framework.dSYM; sourceTree = ""; }; 237 | EA0F0CC01ED0B400000517A4 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/Mac/Nimble.framework; sourceTree = ""; }; 238 | EA0F0CC11ED0B400000517A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/Mac/Quick.framework; sourceTree = ""; }; 239 | EA0F0CC51ED0B413000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/Mac/KeychainSwift.framework; sourceTree = ""; }; 240 | EA0F0CC61ED0B413000517A4 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/Mac/Nimble.framework; sourceTree = ""; }; 241 | EA0F0CC71ED0B413000517A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/Mac/Quick.framework; sourceTree = ""; }; 242 | EA0F0CC81ED0B413000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/Mac/ReactiveSwift.framework; sourceTree = ""; }; 243 | EA0F0CC91ED0B413000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/Mac/Result.framework; sourceTree = ""; }; 244 | EA0F0CD41ED0B448000517A4 /* Feathers.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Feathers.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 245 | EA0F0CDC1ED0B448000517A4 /* Feathers-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feathers-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 246 | EA0F0CF91ED0B48C000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/tvOS/KeychainSwift.framework; sourceTree = ""; }; 247 | EA0F0CFA1ED0B48C000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/tvOS/ReactiveSwift.framework; sourceTree = ""; }; 248 | EA0F0CFB1ED0B48C000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/tvOS/Result.framework; sourceTree = ""; }; 249 | EA0F0D031ED0B4C7000517A4 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/tvOS/Nimble.framework; sourceTree = ""; }; 250 | EA0F0D041ED0B4C7000517A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/tvOS/Quick.framework; sourceTree = ""; }; 251 | EA0F0D081ED0B4D7000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/tvOS/KeychainSwift.framework; sourceTree = ""; }; 252 | EA0F0D091ED0B4D7000517A4 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/tvOS/Nimble.framework; sourceTree = ""; }; 253 | EA0F0D0A1ED0B4D7000517A4 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/tvOS/Quick.framework; sourceTree = ""; }; 254 | EA0F0D0B1ED0B4D7000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/tvOS/ReactiveSwift.framework; sourceTree = ""; }; 255 | EA0F0D0C1ED0B4D7000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/tvOS/Result.framework; sourceTree = ""; }; 256 | EA0F0D171ED0B50D000517A4 /* Feathers.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Feathers.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 257 | EA0F0D1F1ED0B535000517A4 /* KeychainSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeychainSwift.framework; path = Carthage/Build/watchOS/KeychainSwift.framework; sourceTree = ""; }; 258 | EA0F0D201ED0B535000517A4 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/watchOS/ReactiveSwift.framework; sourceTree = ""; }; 259 | EA0F0D211ED0B535000517A4 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/watchOS/Result.framework; sourceTree = ""; }; 260 | EA0F0D301ED290EF000517A4 /* ServiceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ServiceType.swift; path = Core/ServiceType.swift; sourceTree = ""; }; 261 | EA0F0D3A1ED2AA49000517A4 /* ServiceWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ServiceWrapper.swift; path = Core/ServiceWrapper.swift; sourceTree = ""; }; 262 | EA0F0D3F1ED2AB99000517A4 /* ProviderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProviderService.swift; path = Core/ProviderService.swift; sourceTree = ""; }; 263 | EA0F0D451ED3F87F000517A4 /* SignalProducer+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SignalProducer+Convenience.swift"; path = "Core/SignalProducer+Convenience.swift"; sourceTree = ""; }; 264 | EA12E05E1ECE950000A0DCF7 /* Feathers-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Feathers-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 265 | EA3017131EC7867E00114F19 /* Hooks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Hooks.swift; path = Core/Hooks.swift; sourceTree = ""; }; 266 | EA301AB41EC8BEBD00114F19 /* Hooks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Hooks.swift; path = FeathersTests/Hooks.swift; sourceTree = SOURCE_ROOT; }; 267 | EA301AB91EC8CD0400114F19 /* StubProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StubProvider.swift; path = FeathersTests/StubProvider.swift; sourceTree = SOURCE_ROOT; }; 268 | EA452A6A1EA2B14300427CEF /* Feathers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Feathers.h; sourceTree = ""; }; 269 | EA452A6B1EA2B14300427CEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 270 | EA52954B1ECE912C0014F156 /* ServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ServiceSpec.swift; path = FeathersTests/ServiceSpec.swift; sourceTree = SOURCE_ROOT; }; 271 | EA8CE0EB1ED7DC2800EA04F4 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Query.swift; path = Core/Query.swift; sourceTree = ""; }; 272 | EA8CE0F01ED7E0FC00EA04F4 /* QuerySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QuerySpec.swift; path = FeathersTests/QuerySpec.swift; sourceTree = SOURCE_ROOT; }; 273 | EA954C771EC9BEEC0094FF43 /* LoggerHooks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoggerHooks.swift; path = Core/LoggerHooks.swift; sourceTree = ""; }; 274 | EABBAE0A1EBFA6A400522603 /* AuthenticationConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthenticationConfiguration.swift; path = Core/AuthenticationConfiguration.swift; sourceTree = ""; }; 275 | EABBAE0B1EBFA6A400522603 /* AuthenticationStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthenticationStorage.swift; path = Core/AuthenticationStorage.swift; sourceTree = ""; }; 276 | EABBAE0C1EBFA6A400522603 /* Feathers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Feathers.swift; path = Core/Feathers.swift; sourceTree = ""; }; 277 | EABBAE0D1EBFA6A400522603 /* FeathersError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeathersError.swift; path = Core/FeathersError.swift; sourceTree = ""; }; 278 | EABBAE0F1EBFA6A400522603 /* Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Provider.swift; path = Core/Provider.swift; sourceTree = ""; }; 279 | EABBAE101EBFA6A400522603 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Core/Response.swift; sourceTree = ""; }; 280 | EABBAE121EBFA6A400522603 /* Service.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Service.swift; path = Core/Service.swift; sourceTree = ""; }; 281 | EAC0BC821EC90A600049FB13 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = FeathersTests/Info.plist; sourceTree = SOURCE_ROOT; }; 282 | EAC0BCD21EC90FDB0049FB13 /* Feathers.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Feathers.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 283 | EAE05D691EBFDB0000DA55BF /* Endpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Endpoint.swift; path = Core/Endpoint.swift; sourceTree = ""; }; 284 | /* End PBXFileReference section */ 285 | 286 | /* Begin PBXFrameworksBuildPhase section */ 287 | EA0F0C6F1ED0B25E000517A4 /* Frameworks */ = { 288 | isa = PBXFrameworksBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | EA0F0CA41ED0B318000517A4 /* KeychainSwift.framework in Frameworks */, 292 | EA0F0CA51ED0B318000517A4 /* ReactiveSwift.framework in Frameworks */, 293 | EA0F0CA61ED0B318000517A4 /* Result.framework in Frameworks */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | EA0F0C781ED0B25F000517A4 /* Frameworks */ = { 298 | isa = PBXFrameworksBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | EA0F0C7C1ED0B25F000517A4 /* Feathers.framework in Frameworks */, 302 | EA0F0CC21ED0B400000517A4 /* Nimble.framework in Frameworks */, 303 | EA0F0CC31ED0B400000517A4 /* Quick.framework in Frameworks */, 304 | EA0F0CBD1ED0B400000517A4 /* KeychainSwift.framework in Frameworks */, 305 | EA0F0CBE1ED0B400000517A4 /* ReactiveSwift.framework in Frameworks */, 306 | EA0F0CBF1ED0B400000517A4 /* Result.framework in Frameworks */, 307 | ); 308 | runOnlyForDeploymentPostprocessing = 0; 309 | }; 310 | EA0F0CD01ED0B448000517A4 /* Frameworks */ = { 311 | isa = PBXFrameworksBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | EA0F0CFC1ED0B48C000517A4 /* KeychainSwift.framework in Frameworks */, 315 | EA0F0CFD1ED0B48C000517A4 /* ReactiveSwift.framework in Frameworks */, 316 | EA0F0CFE1ED0B48C000517A4 /* Result.framework in Frameworks */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | EA0F0CD91ED0B448000517A4 /* Frameworks */ = { 321 | isa = PBXFrameworksBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | EA0F0CDD1ED0B448000517A4 /* Feathers.framework in Frameworks */, 325 | EA0F0D051ED0B4C7000517A4 /* Nimble.framework in Frameworks */, 326 | EA0F0D061ED0B4C7000517A4 /* Quick.framework in Frameworks */, 327 | EA0F0D001ED0B4C7000517A4 /* KeychainSwift.framework in Frameworks */, 328 | EA0F0D011ED0B4C7000517A4 /* ReactiveSwift.framework in Frameworks */, 329 | EA0F0D021ED0B4C7000517A4 /* Result.framework in Frameworks */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | EA0F0D131ED0B50D000517A4 /* Frameworks */ = { 334 | isa = PBXFrameworksBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | EA0F0D221ED0B535000517A4 /* KeychainSwift.framework in Frameworks */, 338 | EA0F0D231ED0B535000517A4 /* ReactiveSwift.framework in Frameworks */, 339 | EA0F0D241ED0B535000517A4 /* Result.framework in Frameworks */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | EA12E05B1ECE950000A0DCF7 /* Frameworks */ = { 344 | isa = PBXFrameworksBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | EA12E0631ECE950100A0DCF7 /* Feathers.framework in Frameworks */, 348 | EA0F0C901ED0B2DD000517A4 /* KeychainSwift.framework in Frameworks */, 349 | EA0F0C911ED0B2DD000517A4 /* ReactiveSwift.framework in Frameworks */, 350 | EA0F0C921ED0B2DD000517A4 /* Result.framework in Frameworks */, 351 | EA0F0C951ED0B2E1000517A4 /* Nimble.framework in Frameworks */, 352 | EA0F0C961ED0B2E1000517A4 /* Quick.framework in Frameworks */, 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | }; 356 | EAC0BCCE1EC90FDB0049FB13 /* Frameworks */ = { 357 | isa = PBXFrameworksBuildPhase; 358 | buildActionMask = 2147483647; 359 | files = ( 360 | EA0F0C8D1ED0B2D0000517A4 /* KeychainSwift.framework in Frameworks */, 361 | EA0F0C8E1ED0B2D0000517A4 /* ReactiveSwift.framework in Frameworks */, 362 | EA0F0C8F1ED0B2D0000517A4 /* Result.framework in Frameworks */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | /* End PBXFrameworksBuildPhase section */ 367 | 368 | /* Begin PBXGroup section */ 369 | E6F86BE4240DD808800C3F09 /* Frameworks */ = { 370 | isa = PBXGroup; 371 | children = ( 372 | EA0F0D1F1ED0B535000517A4 /* KeychainSwift.framework */, 373 | EA0F0D201ED0B535000517A4 /* ReactiveSwift.framework */, 374 | EA0F0D211ED0B535000517A4 /* Result.framework */, 375 | EA0F0D081ED0B4D7000517A4 /* KeychainSwift.framework */, 376 | EA0F0D091ED0B4D7000517A4 /* Nimble.framework */, 377 | EA0F0D0A1ED0B4D7000517A4 /* Quick.framework */, 378 | EA0F0D0B1ED0B4D7000517A4 /* ReactiveSwift.framework */, 379 | EA0F0D0C1ED0B4D7000517A4 /* Result.framework */, 380 | EA0F0D031ED0B4C7000517A4 /* Nimble.framework */, 381 | EA0F0D041ED0B4C7000517A4 /* Quick.framework */, 382 | EA0F0CF91ED0B48C000517A4 /* KeychainSwift.framework */, 383 | EA0F0CFA1ED0B48C000517A4 /* ReactiveSwift.framework */, 384 | EA0F0CFB1ED0B48C000517A4 /* Result.framework */, 385 | EA0F0CC51ED0B413000517A4 /* KeychainSwift.framework */, 386 | EA0F0CC61ED0B413000517A4 /* Nimble.framework */, 387 | EA0F0CC71ED0B413000517A4 /* Quick.framework */, 388 | EA0F0CC81ED0B413000517A4 /* ReactiveSwift.framework */, 389 | EA0F0CC91ED0B413000517A4 /* Result.framework */, 390 | EA0F0CC01ED0B400000517A4 /* Nimble.framework */, 391 | EA0F0CC11ED0B400000517A4 /* Quick.framework */, 392 | EA0F0CA91ED0B38B000517A4 /* KeychainSwift.framework.dSYM */, 393 | EA0F0CAA1ED0B38B000517A4 /* ReactiveSwift.framework.dSYM */, 394 | EA0F0CAB1ED0B38B000517A4 /* Result.framework.dSYM */, 395 | EA0F0C971ED0B2EA000517A4 /* KeychainSwift.framework */, 396 | EA0F0C981ED0B2EA000517A4 /* Nimble.framework */, 397 | EA0F0C991ED0B2EA000517A4 /* Quick.framework */, 398 | EA0F0C9A1ED0B2EA000517A4 /* ReactiveSwift.framework */, 399 | EA0F0C9B1ED0B2EA000517A4 /* Result.framework */, 400 | EA0F0CA11ED0B318000517A4 /* KeychainSwift.framework */, 401 | EA0F0CA21ED0B318000517A4 /* ReactiveSwift.framework */, 402 | EA0F0CA31ED0B318000517A4 /* Result.framework */, 403 | EA0F0C931ED0B2E1000517A4 /* Nimble.framework */, 404 | EA0F0C941ED0B2E1000517A4 /* Quick.framework */, 405 | EA0F0C8A1ED0B2D0000517A4 /* KeychainSwift.framework */, 406 | EA0F0C8B1ED0B2D0000517A4 /* ReactiveSwift.framework */, 407 | EA0F0C8C1ED0B2D0000517A4 /* Result.framework */, 408 | ); 409 | name = Frameworks; 410 | sourceTree = ""; 411 | }; 412 | EA0F0D441ED3F85A000517A4 /* Extensions */ = { 413 | isa = PBXGroup; 414 | children = ( 415 | EA0F0D451ED3F87F000517A4 /* SignalProducer+Convenience.swift */, 416 | ); 417 | name = Extensions; 418 | sourceTree = ""; 419 | }; 420 | EA301AB31EC8BEAF00114F19 /* Fakes */ = { 421 | isa = PBXGroup; 422 | children = ( 423 | EA301AB41EC8BEBD00114F19 /* Hooks.swift */, 424 | ); 425 | name = Fakes; 426 | sourceTree = ""; 427 | }; 428 | EA301ABD1EC8CD0B00114F19 /* Stubs */ = { 429 | isa = PBXGroup; 430 | children = ( 431 | EA301AB91EC8CD0400114F19 /* StubProvider.swift */, 432 | ); 433 | name = Stubs; 434 | sourceTree = ""; 435 | }; 436 | EA452A5D1EA2B14300427CEF = { 437 | isa = PBXGroup; 438 | children = ( 439 | EA452A691EA2B14300427CEF /* Feathers */, 440 | EABBADB01EBE77C900522603 /* FeathersTests */, 441 | EA452A681EA2B14300427CEF /* Products */, 442 | E6F86BE4240DD808800C3F09 /* Frameworks */, 443 | ); 444 | sourceTree = ""; 445 | }; 446 | EA452A681EA2B14300427CEF /* Products */ = { 447 | isa = PBXGroup; 448 | children = ( 449 | EAC0BCD21EC90FDB0049FB13 /* Feathers.framework */, 450 | EA12E05E1ECE950000A0DCF7 /* Feathers-iOSTests.xctest */, 451 | EA0F0C731ED0B25E000517A4 /* Feathers.framework */, 452 | EA0F0C7B1ED0B25F000517A4 /* Feathers-macOSTests.xctest */, 453 | EA0F0CD41ED0B448000517A4 /* Feathers.framework */, 454 | EA0F0CDC1ED0B448000517A4 /* Feathers-tvOSTests.xctest */, 455 | EA0F0D171ED0B50D000517A4 /* Feathers.framework */, 456 | ); 457 | name = Products; 458 | sourceTree = ""; 459 | }; 460 | EA452A691EA2B14300427CEF /* Feathers */ = { 461 | isa = PBXGroup; 462 | children = ( 463 | EABBAE091EBFA69C00522603 /* Core */, 464 | EA452A6A1EA2B14300427CEF /* Feathers.h */, 465 | EA452A6B1EA2B14300427CEF /* Info.plist */, 466 | ); 467 | path = Feathers; 468 | sourceTree = ""; 469 | }; 470 | EABBADB01EBE77C900522603 /* FeathersTests */ = { 471 | isa = PBXGroup; 472 | children = ( 473 | EA301ABD1EC8CD0B00114F19 /* Stubs */, 474 | EA301AB31EC8BEAF00114F19 /* Fakes */, 475 | EAC0BC821EC90A600049FB13 /* Info.plist */, 476 | EA52954B1ECE912C0014F156 /* ServiceSpec.swift */, 477 | EA8CE0F01ED7E0FC00EA04F4 /* QuerySpec.swift */, 478 | EA0F0BB11ED0A6C9000517A4 /* FeathersSpec.swift */, 479 | ); 480 | path = FeathersTests; 481 | sourceTree = ""; 482 | }; 483 | EABBAE091EBFA69C00522603 /* Core */ = { 484 | isa = PBXGroup; 485 | children = ( 486 | EA0F0D441ED3F85A000517A4 /* Extensions */, 487 | EA3017131EC7867E00114F19 /* Hooks.swift */, 488 | EA954C771EC9BEEC0094FF43 /* LoggerHooks.swift */, 489 | EABBAE121EBFA6A400522603 /* Service.swift */, 490 | EABBAE0A1EBFA6A400522603 /* AuthenticationConfiguration.swift */, 491 | EABBAE0B1EBFA6A400522603 /* AuthenticationStorage.swift */, 492 | EABBAE0C1EBFA6A400522603 /* Feathers.swift */, 493 | EA0F0D3F1ED2AB99000517A4 /* ProviderService.swift */, 494 | EABBAE0D1EBFA6A400522603 /* FeathersError.swift */, 495 | EABBAE0F1EBFA6A400522603 /* Provider.swift */, 496 | EABBAE101EBFA6A400522603 /* Response.swift */, 497 | EAE05D691EBFDB0000DA55BF /* Endpoint.swift */, 498 | EA0F0D301ED290EF000517A4 /* ServiceType.swift */, 499 | EA0F0D3A1ED2AA49000517A4 /* ServiceWrapper.swift */, 500 | EA8CE0EB1ED7DC2800EA04F4 /* Query.swift */, 501 | ); 502 | name = Core; 503 | sourceTree = ""; 504 | }; 505 | /* End PBXGroup section */ 506 | 507 | /* Begin PBXHeadersBuildPhase section */ 508 | EA0F0C701ED0B25E000517A4 /* Headers */ = { 509 | isa = PBXHeadersBuildPhase; 510 | buildActionMask = 2147483647; 511 | files = ( 512 | ); 513 | runOnlyForDeploymentPostprocessing = 0; 514 | }; 515 | EA0F0CD11ED0B448000517A4 /* Headers */ = { 516 | isa = PBXHeadersBuildPhase; 517 | buildActionMask = 2147483647; 518 | files = ( 519 | ); 520 | runOnlyForDeploymentPostprocessing = 0; 521 | }; 522 | EA0F0D141ED0B50D000517A4 /* Headers */ = { 523 | isa = PBXHeadersBuildPhase; 524 | buildActionMask = 2147483647; 525 | files = ( 526 | ); 527 | runOnlyForDeploymentPostprocessing = 0; 528 | }; 529 | EAC0BCCF1EC90FDB0049FB13 /* Headers */ = { 530 | isa = PBXHeadersBuildPhase; 531 | buildActionMask = 2147483647; 532 | files = ( 533 | ); 534 | runOnlyForDeploymentPostprocessing = 0; 535 | }; 536 | /* End PBXHeadersBuildPhase section */ 537 | 538 | /* Begin PBXNativeTarget section */ 539 | EA0F0C721ED0B25E000517A4 /* Feathers-macOS */ = { 540 | isa = PBXNativeTarget; 541 | buildConfigurationList = EA0F0C841ED0B25F000517A4 /* Build configuration list for PBXNativeTarget "Feathers-macOS" */; 542 | buildPhases = ( 543 | EA0F0C6E1ED0B25E000517A4 /* Sources */, 544 | EA0F0C6F1ED0B25E000517A4 /* Frameworks */, 545 | EA0F0C701ED0B25E000517A4 /* Headers */, 546 | EA0F0C711ED0B25E000517A4 /* Resources */, 547 | EA0F0CA81ED0B380000517A4 /* CopyFiles */, 548 | ); 549 | buildRules = ( 550 | ); 551 | dependencies = ( 552 | ); 553 | name = "Feathers-macOS"; 554 | productName = "Feathers-macOS"; 555 | productReference = EA0F0C731ED0B25E000517A4 /* Feathers.framework */; 556 | productType = "com.apple.product-type.framework"; 557 | }; 558 | EA0F0C7A1ED0B25F000517A4 /* Feathers-macOSTests */ = { 559 | isa = PBXNativeTarget; 560 | buildConfigurationList = EA0F0C871ED0B25F000517A4 /* Build configuration list for PBXNativeTarget "Feathers-macOSTests" */; 561 | buildPhases = ( 562 | EA0F0C771ED0B25F000517A4 /* Sources */, 563 | EA0F0C781ED0B25F000517A4 /* Frameworks */, 564 | EA0F0C791ED0B25F000517A4 /* Resources */, 565 | EA0F0CC41ED0B403000517A4 /* CopyFiles */, 566 | ); 567 | buildRules = ( 568 | ); 569 | dependencies = ( 570 | EA0F0C7E1ED0B25F000517A4 /* PBXTargetDependency */, 571 | ); 572 | name = "Feathers-macOSTests"; 573 | productName = "Feathers-macOSTests"; 574 | productReference = EA0F0C7B1ED0B25F000517A4 /* Feathers-macOSTests.xctest */; 575 | productType = "com.apple.product-type.bundle.unit-test"; 576 | }; 577 | EA0F0CD31ED0B448000517A4 /* Feathers-tvOS */ = { 578 | isa = PBXNativeTarget; 579 | buildConfigurationList = EA0F0CE51ED0B448000517A4 /* Build configuration list for PBXNativeTarget "Feathers-tvOS" */; 580 | buildPhases = ( 581 | EA0F0CCF1ED0B448000517A4 /* Sources */, 582 | EA0F0CD01ED0B448000517A4 /* Frameworks */, 583 | EA0F0CD11ED0B448000517A4 /* Headers */, 584 | EA0F0CD21ED0B448000517A4 /* Resources */, 585 | ); 586 | buildRules = ( 587 | ); 588 | dependencies = ( 589 | ); 590 | name = "Feathers-tvOS"; 591 | productName = "Feathers-tvOS"; 592 | productReference = EA0F0CD41ED0B448000517A4 /* Feathers.framework */; 593 | productType = "com.apple.product-type.framework"; 594 | }; 595 | EA0F0CDB1ED0B448000517A4 /* Feathers-tvOSTests */ = { 596 | isa = PBXNativeTarget; 597 | buildConfigurationList = EA0F0CE81ED0B448000517A4 /* Build configuration list for PBXNativeTarget "Feathers-tvOSTests" */; 598 | buildPhases = ( 599 | EA0F0CD81ED0B448000517A4 /* Sources */, 600 | EA0F0CD91ED0B448000517A4 /* Frameworks */, 601 | EA0F0CDA1ED0B448000517A4 /* Resources */, 602 | EA0F0D071ED0B4CC000517A4 /* CopyFiles */, 603 | ); 604 | buildRules = ( 605 | ); 606 | dependencies = ( 607 | EA0F0CDF1ED0B448000517A4 /* PBXTargetDependency */, 608 | ); 609 | name = "Feathers-tvOSTests"; 610 | productName = "Feathers-tvOSTests"; 611 | productReference = EA0F0CDC1ED0B448000517A4 /* Feathers-tvOSTests.xctest */; 612 | productType = "com.apple.product-type.bundle.unit-test"; 613 | }; 614 | EA0F0D161ED0B50D000517A4 /* Feathers-watchOS */ = { 615 | isa = PBXNativeTarget; 616 | buildConfigurationList = EA0F0D1C1ED0B50D000517A4 /* Build configuration list for PBXNativeTarget "Feathers-watchOS" */; 617 | buildPhases = ( 618 | EA0F0D121ED0B50D000517A4 /* Sources */, 619 | EA0F0D131ED0B50D000517A4 /* Frameworks */, 620 | EA0F0D141ED0B50D000517A4 /* Headers */, 621 | EA0F0D151ED0B50D000517A4 /* Resources */, 622 | ); 623 | buildRules = ( 624 | ); 625 | dependencies = ( 626 | ); 627 | name = "Feathers-watchOS"; 628 | productName = "Feathers-watchOS"; 629 | productReference = EA0F0D171ED0B50D000517A4 /* Feathers.framework */; 630 | productType = "com.apple.product-type.framework"; 631 | }; 632 | EA12E05D1ECE950000A0DCF7 /* Feathers-iOSTests */ = { 633 | isa = PBXNativeTarget; 634 | buildConfigurationList = EA12E0661ECE950100A0DCF7 /* Build configuration list for PBXNativeTarget "Feathers-iOSTests" */; 635 | buildPhases = ( 636 | EA12E05A1ECE950000A0DCF7 /* Sources */, 637 | EA12E05B1ECE950000A0DCF7 /* Frameworks */, 638 | EA12E05C1ECE950000A0DCF7 /* Resources */, 639 | EA0F0BC01ED0ABA4000517A4 /* Copy Files */, 640 | ); 641 | buildRules = ( 642 | ); 643 | dependencies = ( 644 | EA12E0651ECE950100A0DCF7 /* PBXTargetDependency */, 645 | ); 646 | name = "Feathers-iOSTests"; 647 | productName = "FeathersTests-iOS"; 648 | productReference = EA12E05E1ECE950000A0DCF7 /* Feathers-iOSTests.xctest */; 649 | productType = "com.apple.product-type.bundle.unit-test"; 650 | }; 651 | EAC0BCD11EC90FDB0049FB13 /* Feathers-iOS */ = { 652 | isa = PBXNativeTarget; 653 | buildConfigurationList = EAC0BCE31EC90FDB0049FB13 /* Build configuration list for PBXNativeTarget "Feathers-iOS" */; 654 | buildPhases = ( 655 | EAC0BCCD1EC90FDB0049FB13 /* Sources */, 656 | EAC0BCCE1EC90FDB0049FB13 /* Frameworks */, 657 | EAC0BCCF1EC90FDB0049FB13 /* Headers */, 658 | EAC0BCD01EC90FDB0049FB13 /* Resources */, 659 | ); 660 | buildRules = ( 661 | ); 662 | dependencies = ( 663 | ); 664 | name = "Feathers-iOS"; 665 | productName = "Feathers-iOS"; 666 | productReference = EAC0BCD21EC90FDB0049FB13 /* Feathers.framework */; 667 | productType = "com.apple.product-type.framework"; 668 | }; 669 | /* End PBXNativeTarget section */ 670 | 671 | /* Begin PBXProject section */ 672 | EA452A5E1EA2B14300427CEF /* Project object */ = { 673 | isa = PBXProject; 674 | attributes = { 675 | LastSwiftUpdateCheck = 0830; 676 | LastUpgradeCheck = 1020; 677 | ORGANIZATIONNAME = "Swoopy Studios"; 678 | TargetAttributes = { 679 | EA0F0C721ED0B25E000517A4 = { 680 | CreatedOnToolsVersion = 8.3.2; 681 | LastSwiftMigration = 0920; 682 | ProvisioningStyle = Automatic; 683 | }; 684 | EA0F0C7A1ED0B25F000517A4 = { 685 | CreatedOnToolsVersion = 8.3.2; 686 | LastSwiftMigration = 0920; 687 | ProvisioningStyle = Automatic; 688 | }; 689 | EA0F0CD31ED0B448000517A4 = { 690 | CreatedOnToolsVersion = 8.3.2; 691 | LastSwiftMigration = 0920; 692 | ProvisioningStyle = Automatic; 693 | }; 694 | EA0F0CDB1ED0B448000517A4 = { 695 | CreatedOnToolsVersion = 8.3.2; 696 | LastSwiftMigration = 0920; 697 | ProvisioningStyle = Automatic; 698 | }; 699 | EA0F0D161ED0B50D000517A4 = { 700 | CreatedOnToolsVersion = 8.3.2; 701 | LastSwiftMigration = 0920; 702 | ProvisioningStyle = Automatic; 703 | }; 704 | EA12E05D1ECE950000A0DCF7 = { 705 | CreatedOnToolsVersion = 8.3.2; 706 | LastSwiftMigration = 0920; 707 | ProvisioningStyle = Automatic; 708 | }; 709 | EAC0BCD11EC90FDB0049FB13 = { 710 | CreatedOnToolsVersion = 8.3.2; 711 | LastSwiftMigration = 0920; 712 | ProvisioningStyle = Automatic; 713 | }; 714 | }; 715 | }; 716 | buildConfigurationList = EA452A611EA2B14300427CEF /* Build configuration list for PBXProject "Feathers" */; 717 | compatibilityVersion = "Xcode 3.2"; 718 | developmentRegion = en; 719 | hasScannedForEncodings = 0; 720 | knownRegions = ( 721 | en, 722 | Base, 723 | ); 724 | mainGroup = EA452A5D1EA2B14300427CEF; 725 | productRefGroup = EA452A681EA2B14300427CEF /* Products */; 726 | projectDirPath = ""; 727 | projectRoot = ""; 728 | targets = ( 729 | EAC0BCD11EC90FDB0049FB13 /* Feathers-iOS */, 730 | EA12E05D1ECE950000A0DCF7 /* Feathers-iOSTests */, 731 | EA0F0C721ED0B25E000517A4 /* Feathers-macOS */, 732 | EA0F0C7A1ED0B25F000517A4 /* Feathers-macOSTests */, 733 | EA0F0CD31ED0B448000517A4 /* Feathers-tvOS */, 734 | EA0F0CDB1ED0B448000517A4 /* Feathers-tvOSTests */, 735 | EA0F0D161ED0B50D000517A4 /* Feathers-watchOS */, 736 | ); 737 | }; 738 | /* End PBXProject section */ 739 | 740 | /* Begin PBXResourcesBuildPhase section */ 741 | EA0F0C711ED0B25E000517A4 /* Resources */ = { 742 | isa = PBXResourcesBuildPhase; 743 | buildActionMask = 2147483647; 744 | files = ( 745 | ); 746 | runOnlyForDeploymentPostprocessing = 0; 747 | }; 748 | EA0F0C791ED0B25F000517A4 /* Resources */ = { 749 | isa = PBXResourcesBuildPhase; 750 | buildActionMask = 2147483647; 751 | files = ( 752 | ); 753 | runOnlyForDeploymentPostprocessing = 0; 754 | }; 755 | EA0F0CD21ED0B448000517A4 /* Resources */ = { 756 | isa = PBXResourcesBuildPhase; 757 | buildActionMask = 2147483647; 758 | files = ( 759 | ); 760 | runOnlyForDeploymentPostprocessing = 0; 761 | }; 762 | EA0F0CDA1ED0B448000517A4 /* Resources */ = { 763 | isa = PBXResourcesBuildPhase; 764 | buildActionMask = 2147483647; 765 | files = ( 766 | ); 767 | runOnlyForDeploymentPostprocessing = 0; 768 | }; 769 | EA0F0D151ED0B50D000517A4 /* Resources */ = { 770 | isa = PBXResourcesBuildPhase; 771 | buildActionMask = 2147483647; 772 | files = ( 773 | ); 774 | runOnlyForDeploymentPostprocessing = 0; 775 | }; 776 | EA12E05C1ECE950000A0DCF7 /* Resources */ = { 777 | isa = PBXResourcesBuildPhase; 778 | buildActionMask = 2147483647; 779 | files = ( 780 | ); 781 | runOnlyForDeploymentPostprocessing = 0; 782 | }; 783 | EAC0BCD01EC90FDB0049FB13 /* Resources */ = { 784 | isa = PBXResourcesBuildPhase; 785 | buildActionMask = 2147483647; 786 | files = ( 787 | ); 788 | runOnlyForDeploymentPostprocessing = 0; 789 | }; 790 | /* End PBXResourcesBuildPhase section */ 791 | 792 | /* Begin PBXSourcesBuildPhase section */ 793 | EA0F0C6E1ED0B25E000517A4 /* Sources */ = { 794 | isa = PBXSourcesBuildPhase; 795 | buildActionMask = 2147483647; 796 | files = ( 797 | EA0F0CB61ED0B3E2000517A4 /* AuthenticationConfiguration.swift in Sources */, 798 | EA0F0CB91ED0B3E2000517A4 /* FeathersError.swift in Sources */, 799 | EA0F0D411ED2AB99000517A4 /* ProviderService.swift in Sources */, 800 | EA0F0CB81ED0B3E2000517A4 /* Feathers.swift in Sources */, 801 | EA0F0CB71ED0B3E2000517A4 /* AuthenticationStorage.swift in Sources */, 802 | EA0F0CBC1ED0B3E2000517A4 /* Endpoint.swift in Sources */, 803 | EA0F0CBB1ED0B3E2000517A4 /* Response.swift in Sources */, 804 | EA0F0CBA1ED0B3E2000517A4 /* Provider.swift in Sources */, 805 | EA0F0CB41ED0B3E2000517A4 /* LoggerHooks.swift in Sources */, 806 | EA0F0CB31ED0B3E2000517A4 /* Hooks.swift in Sources */, 807 | EA8CE0ED1ED7DC2800EA04F4 /* Query.swift in Sources */, 808 | EA0F0D471ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */, 809 | EA0F0D3C1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */, 810 | EA0F0D321ED290EF000517A4 /* ServiceType.swift in Sources */, 811 | EA0F0CB51ED0B3E2000517A4 /* Service.swift in Sources */, 812 | ); 813 | runOnlyForDeploymentPostprocessing = 0; 814 | }; 815 | EA0F0C771ED0B25F000517A4 /* Sources */ = { 816 | isa = PBXSourcesBuildPhase; 817 | buildActionMask = 2147483647; 818 | files = ( 819 | EA0F0CB01ED0B3B3000517A4 /* Hooks.swift in Sources */, 820 | EA0F0CB11ED0B3B3000517A4 /* ServiceSpec.swift in Sources */, 821 | EA0F0CB21ED0B3B3000517A4 /* FeathersSpec.swift in Sources */, 822 | EA0505A51ED9B262006B5CCB /* QuerySpec.swift in Sources */, 823 | EA0F0CAF1ED0B3B3000517A4 /* StubProvider.swift in Sources */, 824 | ); 825 | runOnlyForDeploymentPostprocessing = 0; 826 | }; 827 | EA0F0CCF1ED0B448000517A4 /* Sources */ = { 828 | isa = PBXSourcesBuildPhase; 829 | buildActionMask = 2147483647; 830 | files = ( 831 | EA0F0CEE1ED0B46B000517A4 /* AuthenticationConfiguration.swift in Sources */, 832 | EA0F0CF11ED0B46B000517A4 /* FeathersError.swift in Sources */, 833 | EA0F0D421ED2AB99000517A4 /* ProviderService.swift in Sources */, 834 | EA0F0CF01ED0B46B000517A4 /* Feathers.swift in Sources */, 835 | EA0F0CEF1ED0B46B000517A4 /* AuthenticationStorage.swift in Sources */, 836 | EA0F0CF41ED0B46B000517A4 /* Endpoint.swift in Sources */, 837 | EA0F0CF31ED0B46B000517A4 /* Response.swift in Sources */, 838 | EA0F0CF21ED0B46B000517A4 /* Provider.swift in Sources */, 839 | EA0F0CEC1ED0B46B000517A4 /* LoggerHooks.swift in Sources */, 840 | EA0F0CEB1ED0B46B000517A4 /* Hooks.swift in Sources */, 841 | EA8CE0EE1ED7DC2800EA04F4 /* Query.swift in Sources */, 842 | EA0F0D481ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */, 843 | EA0F0D3D1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */, 844 | EA0F0D331ED290EF000517A4 /* ServiceType.swift in Sources */, 845 | EA0F0CED1ED0B46B000517A4 /* Service.swift in Sources */, 846 | ); 847 | runOnlyForDeploymentPostprocessing = 0; 848 | }; 849 | EA0F0CD81ED0B448000517A4 /* Sources */ = { 850 | isa = PBXSourcesBuildPhase; 851 | buildActionMask = 2147483647; 852 | files = ( 853 | EA0F0CF61ED0B46F000517A4 /* Hooks.swift in Sources */, 854 | EA0F0CF71ED0B46F000517A4 /* ServiceSpec.swift in Sources */, 855 | EA0F0CF81ED0B46F000517A4 /* FeathersSpec.swift in Sources */, 856 | EA0505A61ED9B292006B5CCB /* QuerySpec.swift in Sources */, 857 | EA0F0CF51ED0B46F000517A4 /* StubProvider.swift in Sources */, 858 | ); 859 | runOnlyForDeploymentPostprocessing = 0; 860 | }; 861 | EA0F0D121ED0B50D000517A4 /* Sources */ = { 862 | isa = PBXSourcesBuildPhase; 863 | buildActionMask = 2147483647; 864 | files = ( 865 | EA0F0D281ED0B539000517A4 /* AuthenticationConfiguration.swift in Sources */, 866 | EA0F0D2B1ED0B539000517A4 /* FeathersError.swift in Sources */, 867 | EA0F0D431ED2AB99000517A4 /* ProviderService.swift in Sources */, 868 | EA0F0D2A1ED0B539000517A4 /* Feathers.swift in Sources */, 869 | EA0F0D291ED0B539000517A4 /* AuthenticationStorage.swift in Sources */, 870 | EA0F0D2E1ED0B539000517A4 /* Endpoint.swift in Sources */, 871 | EA0F0D2D1ED0B539000517A4 /* Response.swift in Sources */, 872 | EA0F0D2C1ED0B539000517A4 /* Provider.swift in Sources */, 873 | EA0F0D261ED0B539000517A4 /* LoggerHooks.swift in Sources */, 874 | EA0F0D251ED0B539000517A4 /* Hooks.swift in Sources */, 875 | EA8CE0EF1ED7DC2800EA04F4 /* Query.swift in Sources */, 876 | EA0F0D491ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */, 877 | EA0F0D3E1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */, 878 | EA0F0D341ED290EF000517A4 /* ServiceType.swift in Sources */, 879 | EA0F0D271ED0B539000517A4 /* Service.swift in Sources */, 880 | ); 881 | runOnlyForDeploymentPostprocessing = 0; 882 | }; 883 | EA12E05A1ECE950000A0DCF7 /* Sources */ = { 884 | isa = PBXSourcesBuildPhase; 885 | buildActionMask = 2147483647; 886 | files = ( 887 | EA12E06A1ECE952400A0DCF7 /* StubProvider.swift in Sources */, 888 | EA12E0691ECE950C00A0DCF7 /* ServiceSpec.swift in Sources */, 889 | EA0F0BB21ED0A6C9000517A4 /* FeathersSpec.swift in Sources */, 890 | EA8CE0F11ED7E0FC00EA04F4 /* QuerySpec.swift in Sources */, 891 | EA12E06B1ECE952400A0DCF7 /* Hooks.swift in Sources */, 892 | ); 893 | runOnlyForDeploymentPostprocessing = 0; 894 | }; 895 | EAC0BCCD1EC90FDB0049FB13 /* Sources */ = { 896 | isa = PBXSourcesBuildPhase; 897 | buildActionMask = 2147483647; 898 | files = ( 899 | EA954C781EC9BEEC0094FF43 /* LoggerHooks.swift in Sources */, 900 | EAC0BCF01EC90FF30049FB13 /* Response.swift in Sources */, 901 | EA0F0D401ED2AB99000517A4 /* ProviderService.swift in Sources */, 902 | EAC0BCEB1EC90FF30049FB13 /* AuthenticationStorage.swift in Sources */, 903 | EAC0BCEF1EC90FF30049FB13 /* Provider.swift in Sources */, 904 | EAC0BCEA1EC90FF30049FB13 /* AuthenticationConfiguration.swift in Sources */, 905 | EAC0BCE91EC90FF30049FB13 /* Service.swift in Sources */, 906 | EAC0BCF41EC90FF30049FB13 /* Hooks.swift in Sources */, 907 | EAC0BCED1EC90FF30049FB13 /* FeathersError.swift in Sources */, 908 | EAC0BCEC1EC90FF30049FB13 /* Feathers.swift in Sources */, 909 | EA8CE0EC1ED7DC2800EA04F4 /* Query.swift in Sources */, 910 | EA0F0D461ED3F87F000517A4 /* SignalProducer+Convenience.swift in Sources */, 911 | EA0F0D3B1ED2AA49000517A4 /* ServiceWrapper.swift in Sources */, 912 | EA0F0D311ED290EF000517A4 /* ServiceType.swift in Sources */, 913 | EAC0BCF31EC90FF30049FB13 /* Endpoint.swift in Sources */, 914 | ); 915 | runOnlyForDeploymentPostprocessing = 0; 916 | }; 917 | /* End PBXSourcesBuildPhase section */ 918 | 919 | /* Begin PBXTargetDependency section */ 920 | EA0F0C7E1ED0B25F000517A4 /* PBXTargetDependency */ = { 921 | isa = PBXTargetDependency; 922 | target = EA0F0C721ED0B25E000517A4 /* Feathers-macOS */; 923 | targetProxy = EA0F0C7D1ED0B25F000517A4 /* PBXContainerItemProxy */; 924 | }; 925 | EA0F0CDF1ED0B448000517A4 /* PBXTargetDependency */ = { 926 | isa = PBXTargetDependency; 927 | target = EA0F0CD31ED0B448000517A4 /* Feathers-tvOS */; 928 | targetProxy = EA0F0CDE1ED0B448000517A4 /* PBXContainerItemProxy */; 929 | }; 930 | EA12E0651ECE950100A0DCF7 /* PBXTargetDependency */ = { 931 | isa = PBXTargetDependency; 932 | target = EAC0BCD11EC90FDB0049FB13 /* Feathers-iOS */; 933 | targetProxy = EA12E0641ECE950100A0DCF7 /* PBXContainerItemProxy */; 934 | }; 935 | /* End PBXTargetDependency section */ 936 | 937 | /* Begin XCBuildConfiguration section */ 938 | EA0F0C851ED0B25F000517A4 /* Debug */ = { 939 | isa = XCBuildConfiguration; 940 | buildSettings = { 941 | CODE_SIGN_IDENTITY = "-"; 942 | COMBINE_HIDPI_IMAGES = YES; 943 | DEFINES_MODULE = YES; 944 | DYLIB_COMPATIBILITY_VERSION = 1; 945 | DYLIB_CURRENT_VERSION = 1; 946 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 947 | FRAMEWORK_SEARCH_PATHS = ( 948 | "$(inherited)", 949 | "$(PROJECT_DIR)/Carthage/Build/Mac", 950 | ); 951 | FRAMEWORK_VERSION = A; 952 | INFOPLIST_FILE = Feathers/Info.plist; 953 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 954 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 955 | MACOSX_DEPLOYMENT_TARGET = 10.10; 956 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-macOS"; 957 | PRODUCT_NAME = Feathers; 958 | SDKROOT = macosx; 959 | SKIP_INSTALL = YES; 960 | SWIFT_VERSION = 5.0; 961 | TVOS_DEPLOYMENT_TARGET = 9.0; 962 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 963 | }; 964 | name = Debug; 965 | }; 966 | EA0F0C861ED0B25F000517A4 /* Release */ = { 967 | isa = XCBuildConfiguration; 968 | buildSettings = { 969 | CODE_SIGN_IDENTITY = "-"; 970 | COMBINE_HIDPI_IMAGES = YES; 971 | DEFINES_MODULE = YES; 972 | DYLIB_COMPATIBILITY_VERSION = 1; 973 | DYLIB_CURRENT_VERSION = 1; 974 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 975 | FRAMEWORK_SEARCH_PATHS = ( 976 | "$(inherited)", 977 | "$(PROJECT_DIR)/Carthage/Build/Mac", 978 | ); 979 | FRAMEWORK_VERSION = A; 980 | INFOPLIST_FILE = Feathers/Info.plist; 981 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 982 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 983 | MACOSX_DEPLOYMENT_TARGET = 10.10; 984 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-macOS"; 985 | PRODUCT_NAME = Feathers; 986 | SDKROOT = macosx; 987 | SKIP_INSTALL = YES; 988 | SWIFT_VERSION = 5.0; 989 | TVOS_DEPLOYMENT_TARGET = 9.0; 990 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 991 | }; 992 | name = Release; 993 | }; 994 | EA0F0C881ED0B25F000517A4 /* Debug */ = { 995 | isa = XCBuildConfiguration; 996 | buildSettings = { 997 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 998 | CODE_SIGN_IDENTITY = "-"; 999 | COMBINE_HIDPI_IMAGES = YES; 1000 | FRAMEWORK_SEARCH_PATHS = ( 1001 | "$(inherited)", 1002 | "$(PROJECT_DIR)/Carthage/Build/Mac", 1003 | ); 1004 | INFOPLIST_FILE = FeathersTests/Info.plist; 1005 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1006 | MACOSX_DEPLOYMENT_TARGET = 10.12; 1007 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-macOSTests"; 1008 | PRODUCT_NAME = "$(TARGET_NAME)"; 1009 | SDKROOT = macosx; 1010 | SWIFT_VERSION = 5.0; 1011 | }; 1012 | name = Debug; 1013 | }; 1014 | EA0F0C891ED0B25F000517A4 /* Release */ = { 1015 | isa = XCBuildConfiguration; 1016 | buildSettings = { 1017 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1018 | CODE_SIGN_IDENTITY = "-"; 1019 | COMBINE_HIDPI_IMAGES = YES; 1020 | FRAMEWORK_SEARCH_PATHS = ( 1021 | "$(inherited)", 1022 | "$(PROJECT_DIR)/Carthage/Build/Mac", 1023 | ); 1024 | INFOPLIST_FILE = FeathersTests/Info.plist; 1025 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 1026 | MACOSX_DEPLOYMENT_TARGET = 10.12; 1027 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-macOSTests"; 1028 | PRODUCT_NAME = "$(TARGET_NAME)"; 1029 | SDKROOT = macosx; 1030 | SWIFT_VERSION = 5.0; 1031 | }; 1032 | name = Release; 1033 | }; 1034 | EA0F0CE61ED0B448000517A4 /* Debug */ = { 1035 | isa = XCBuildConfiguration; 1036 | buildSettings = { 1037 | CODE_SIGN_IDENTITY = ""; 1038 | DEFINES_MODULE = YES; 1039 | DYLIB_COMPATIBILITY_VERSION = 1; 1040 | DYLIB_CURRENT_VERSION = 1; 1041 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1042 | FRAMEWORK_SEARCH_PATHS = ( 1043 | "$(inherited)", 1044 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 1045 | ); 1046 | INFOPLIST_FILE = Feathers/Info.plist; 1047 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1048 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1049 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-tvOS"; 1050 | PRODUCT_NAME = Feathers; 1051 | SDKROOT = appletvos; 1052 | SKIP_INSTALL = YES; 1053 | SWIFT_VERSION = 5.0; 1054 | TARGETED_DEVICE_FAMILY = 3; 1055 | TVOS_DEPLOYMENT_TARGET = 9.0; 1056 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 1057 | }; 1058 | name = Debug; 1059 | }; 1060 | EA0F0CE71ED0B448000517A4 /* Release */ = { 1061 | isa = XCBuildConfiguration; 1062 | buildSettings = { 1063 | CODE_SIGN_IDENTITY = ""; 1064 | DEFINES_MODULE = YES; 1065 | DYLIB_COMPATIBILITY_VERSION = 1; 1066 | DYLIB_CURRENT_VERSION = 1; 1067 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1068 | FRAMEWORK_SEARCH_PATHS = ( 1069 | "$(inherited)", 1070 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 1071 | ); 1072 | INFOPLIST_FILE = Feathers/Info.plist; 1073 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1074 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1075 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-tvOS"; 1076 | PRODUCT_NAME = Feathers; 1077 | SDKROOT = appletvos; 1078 | SKIP_INSTALL = YES; 1079 | SWIFT_VERSION = 5.0; 1080 | TARGETED_DEVICE_FAMILY = 3; 1081 | TVOS_DEPLOYMENT_TARGET = 9.0; 1082 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 1083 | }; 1084 | name = Release; 1085 | }; 1086 | EA0F0CE91ED0B448000517A4 /* Debug */ = { 1087 | isa = XCBuildConfiguration; 1088 | buildSettings = { 1089 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1090 | FRAMEWORK_SEARCH_PATHS = ( 1091 | "$(inherited)", 1092 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 1093 | ); 1094 | INFOPLIST_FILE = FeathersTests/Info.plist; 1095 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1096 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-tvOSTests"; 1097 | PRODUCT_NAME = "$(TARGET_NAME)"; 1098 | SDKROOT = appletvos; 1099 | SWIFT_VERSION = 5.0; 1100 | TVOS_DEPLOYMENT_TARGET = 10.2; 1101 | }; 1102 | name = Debug; 1103 | }; 1104 | EA0F0CEA1ED0B448000517A4 /* Release */ = { 1105 | isa = XCBuildConfiguration; 1106 | buildSettings = { 1107 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1108 | FRAMEWORK_SEARCH_PATHS = ( 1109 | "$(inherited)", 1110 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 1111 | ); 1112 | INFOPLIST_FILE = FeathersTests/Info.plist; 1113 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1114 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-tvOSTests"; 1115 | PRODUCT_NAME = "$(TARGET_NAME)"; 1116 | SDKROOT = appletvos; 1117 | SWIFT_VERSION = 5.0; 1118 | TVOS_DEPLOYMENT_TARGET = 10.2; 1119 | }; 1120 | name = Release; 1121 | }; 1122 | EA0F0D1D1ED0B50D000517A4 /* Debug */ = { 1123 | isa = XCBuildConfiguration; 1124 | buildSettings = { 1125 | APPLICATION_EXTENSION_API_ONLY = YES; 1126 | CODE_SIGN_IDENTITY = ""; 1127 | DEFINES_MODULE = YES; 1128 | DYLIB_COMPATIBILITY_VERSION = 1; 1129 | DYLIB_CURRENT_VERSION = 1; 1130 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1131 | FRAMEWORK_SEARCH_PATHS = ( 1132 | "$(inherited)", 1133 | "$(PROJECT_DIR)/Carthage/Build/watchOS", 1134 | ); 1135 | INFOPLIST_FILE = Feathers/Info.plist; 1136 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1137 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1138 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-watchOS"; 1139 | PRODUCT_NAME = Feathers; 1140 | SDKROOT = watchos; 1141 | SKIP_INSTALL = YES; 1142 | SWIFT_VERSION = 5.0; 1143 | TARGETED_DEVICE_FAMILY = 4; 1144 | TVOS_DEPLOYMENT_TARGET = 9.0; 1145 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 1146 | }; 1147 | name = Debug; 1148 | }; 1149 | EA0F0D1E1ED0B50D000517A4 /* Release */ = { 1150 | isa = XCBuildConfiguration; 1151 | buildSettings = { 1152 | APPLICATION_EXTENSION_API_ONLY = YES; 1153 | CODE_SIGN_IDENTITY = ""; 1154 | DEFINES_MODULE = YES; 1155 | DYLIB_COMPATIBILITY_VERSION = 1; 1156 | DYLIB_CURRENT_VERSION = 1; 1157 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1158 | FRAMEWORK_SEARCH_PATHS = ( 1159 | "$(inherited)", 1160 | "$(PROJECT_DIR)/Carthage/Build/watchOS", 1161 | ); 1162 | INFOPLIST_FILE = Feathers/Info.plist; 1163 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1164 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1165 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.Feathers-watchOS"; 1166 | PRODUCT_NAME = Feathers; 1167 | SDKROOT = watchos; 1168 | SKIP_INSTALL = YES; 1169 | SWIFT_VERSION = 5.0; 1170 | TARGETED_DEVICE_FAMILY = 4; 1171 | TVOS_DEPLOYMENT_TARGET = 9.0; 1172 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 1173 | }; 1174 | name = Release; 1175 | }; 1176 | EA12E0671ECE950100A0DCF7 /* Debug */ = { 1177 | isa = XCBuildConfiguration; 1178 | buildSettings = { 1179 | FRAMEWORK_SEARCH_PATHS = ( 1180 | "$(inherited)", 1181 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1182 | ); 1183 | INFOPLIST_FILE = FeathersTests/Info.plist; 1184 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1185 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.FeathersTests-iOS"; 1186 | PRODUCT_NAME = "$(TARGET_NAME)"; 1187 | SWIFT_VERSION = 5.0; 1188 | }; 1189 | name = Debug; 1190 | }; 1191 | EA12E0681ECE950100A0DCF7 /* Release */ = { 1192 | isa = XCBuildConfiguration; 1193 | buildSettings = { 1194 | FRAMEWORK_SEARCH_PATHS = ( 1195 | "$(inherited)", 1196 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1197 | ); 1198 | INFOPLIST_FILE = FeathersTests/Info.plist; 1199 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1200 | PRODUCT_BUNDLE_IDENTIFIER = "com.feathersjs.FeathersTests-iOS"; 1201 | PRODUCT_NAME = "$(TARGET_NAME)"; 1202 | SWIFT_VERSION = 5.0; 1203 | }; 1204 | name = Release; 1205 | }; 1206 | EA452A791EA2B14300427CEF /* Debug */ = { 1207 | isa = XCBuildConfiguration; 1208 | buildSettings = { 1209 | ALWAYS_SEARCH_USER_PATHS = NO; 1210 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 1211 | CLANG_ANALYZER_NONNULL = YES; 1212 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 1213 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1214 | CLANG_CXX_LIBRARY = "libc++"; 1215 | CLANG_ENABLE_MODULES = YES; 1216 | CLANG_ENABLE_OBJC_ARC = YES; 1217 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 1218 | CLANG_WARN_BOOL_CONVERSION = YES; 1219 | CLANG_WARN_COMMA = YES; 1220 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1221 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 1222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1223 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1224 | CLANG_WARN_EMPTY_BODY = YES; 1225 | CLANG_WARN_ENUM_CONVERSION = YES; 1226 | CLANG_WARN_INFINITE_RECURSION = YES; 1227 | CLANG_WARN_INT_CONVERSION = YES; 1228 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 1229 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 1230 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 1231 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1232 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1233 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1234 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 1235 | CLANG_WARN_UNREACHABLE_CODE = YES; 1236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1237 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1238 | COPY_PHASE_STRIP = NO; 1239 | CURRENT_PROJECT_VERSION = 1; 1240 | DEBUG_INFORMATION_FORMAT = dwarf; 1241 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1242 | ENABLE_TESTABILITY = YES; 1243 | GCC_C_LANGUAGE_STANDARD = gnu99; 1244 | GCC_DYNAMIC_NO_PIC = NO; 1245 | GCC_NO_COMMON_BLOCKS = YES; 1246 | GCC_OPTIMIZATION_LEVEL = 0; 1247 | GCC_PREPROCESSOR_DEFINITIONS = ( 1248 | "DEBUG=1", 1249 | "$(inherited)", 1250 | ); 1251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1253 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1255 | GCC_WARN_UNUSED_FUNCTION = YES; 1256 | GCC_WARN_UNUSED_VARIABLE = YES; 1257 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 1258 | MACOSX_DEPLOYMENT_TARGET = 10.10; 1259 | MTL_ENABLE_DEBUG_INFO = YES; 1260 | ONLY_ACTIVE_ARCH = YES; 1261 | PRODUCT_NAME = Feathers; 1262 | SDKROOT = iphoneos; 1263 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 1264 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1265 | TARGETED_DEVICE_FAMILY = "1,2"; 1266 | VERSIONING_SYSTEM = "apple-generic"; 1267 | VERSION_INFO_PREFIX = ""; 1268 | }; 1269 | name = Debug; 1270 | }; 1271 | EA452A7A1EA2B14300427CEF /* Release */ = { 1272 | isa = XCBuildConfiguration; 1273 | buildSettings = { 1274 | ALWAYS_SEARCH_USER_PATHS = NO; 1275 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 1276 | CLANG_ANALYZER_NONNULL = YES; 1277 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 1278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1279 | CLANG_CXX_LIBRARY = "libc++"; 1280 | CLANG_ENABLE_MODULES = YES; 1281 | CLANG_ENABLE_OBJC_ARC = YES; 1282 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 1283 | CLANG_WARN_BOOL_CONVERSION = YES; 1284 | CLANG_WARN_COMMA = YES; 1285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1286 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 1287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1289 | CLANG_WARN_EMPTY_BODY = YES; 1290 | CLANG_WARN_ENUM_CONVERSION = YES; 1291 | CLANG_WARN_INFINITE_RECURSION = YES; 1292 | CLANG_WARN_INT_CONVERSION = YES; 1293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 1294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 1295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 1296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1297 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1298 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 1300 | CLANG_WARN_UNREACHABLE_CODE = YES; 1301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1302 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1303 | COPY_PHASE_STRIP = NO; 1304 | CURRENT_PROJECT_VERSION = 1; 1305 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1306 | ENABLE_NS_ASSERTIONS = NO; 1307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1308 | GCC_C_LANGUAGE_STANDARD = gnu99; 1309 | GCC_NO_COMMON_BLOCKS = YES; 1310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1314 | GCC_WARN_UNUSED_FUNCTION = YES; 1315 | GCC_WARN_UNUSED_VARIABLE = YES; 1316 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 1317 | MACOSX_DEPLOYMENT_TARGET = 10.10; 1318 | MTL_ENABLE_DEBUG_INFO = NO; 1319 | PRODUCT_NAME = Feathers; 1320 | SDKROOT = iphoneos; 1321 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 1322 | TARGETED_DEVICE_FAMILY = "1,2"; 1323 | VALIDATE_PRODUCT = YES; 1324 | VERSIONING_SYSTEM = "apple-generic"; 1325 | VERSION_INFO_PREFIX = ""; 1326 | }; 1327 | name = Release; 1328 | }; 1329 | EAC0BCE41EC90FDB0049FB13 /* Debug */ = { 1330 | isa = XCBuildConfiguration; 1331 | buildSettings = { 1332 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 1333 | CODE_SIGN_IDENTITY = ""; 1334 | DEFINES_MODULE = YES; 1335 | DYLIB_COMPATIBILITY_VERSION = 1; 1336 | DYLIB_CURRENT_VERSION = 1; 1337 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1338 | FRAMEWORK_SEARCH_PATHS = ( 1339 | "$(inherited)", 1340 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1341 | ); 1342 | INFOPLIST_FILE = Feathers/Info.plist; 1343 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1344 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 1345 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1346 | OTHER_LDFLAGS = "$(inherited)"; 1347 | PRODUCT_BUNDLE_IDENTIFIER = "com.swoopystudios.Feathers-iOS"; 1348 | PRODUCT_NAME = Feathers; 1349 | SKIP_INSTALL = YES; 1350 | SWIFT_VERSION = 5.0; 1351 | TVOS_DEPLOYMENT_TARGET = 9.0; 1352 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 1353 | }; 1354 | name = Debug; 1355 | }; 1356 | EAC0BCE51EC90FDB0049FB13 /* Release */ = { 1357 | isa = XCBuildConfiguration; 1358 | buildSettings = { 1359 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 1360 | CODE_SIGN_IDENTITY = ""; 1361 | DEFINES_MODULE = YES; 1362 | DYLIB_COMPATIBILITY_VERSION = 1; 1363 | DYLIB_CURRENT_VERSION = 1; 1364 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1365 | FRAMEWORK_SEARCH_PATHS = ( 1366 | "$(inherited)", 1367 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1368 | ); 1369 | INFOPLIST_FILE = Feathers/Info.plist; 1370 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1371 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 1372 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1373 | OTHER_LDFLAGS = "$(inherited)"; 1374 | PRODUCT_BUNDLE_IDENTIFIER = "com.swoopystudios.Feathers-iOS"; 1375 | PRODUCT_NAME = Feathers; 1376 | SKIP_INSTALL = YES; 1377 | SWIFT_VERSION = 5.0; 1378 | TVOS_DEPLOYMENT_TARGET = 9.0; 1379 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 1380 | }; 1381 | name = Release; 1382 | }; 1383 | /* End XCBuildConfiguration section */ 1384 | 1385 | /* Begin XCConfigurationList section */ 1386 | EA0F0C841ED0B25F000517A4 /* Build configuration list for PBXNativeTarget "Feathers-macOS" */ = { 1387 | isa = XCConfigurationList; 1388 | buildConfigurations = ( 1389 | EA0F0C851ED0B25F000517A4 /* Debug */, 1390 | EA0F0C861ED0B25F000517A4 /* Release */, 1391 | ); 1392 | defaultConfigurationIsVisible = 0; 1393 | defaultConfigurationName = Release; 1394 | }; 1395 | EA0F0C871ED0B25F000517A4 /* Build configuration list for PBXNativeTarget "Feathers-macOSTests" */ = { 1396 | isa = XCConfigurationList; 1397 | buildConfigurations = ( 1398 | EA0F0C881ED0B25F000517A4 /* Debug */, 1399 | EA0F0C891ED0B25F000517A4 /* Release */, 1400 | ); 1401 | defaultConfigurationIsVisible = 0; 1402 | defaultConfigurationName = Release; 1403 | }; 1404 | EA0F0CE51ED0B448000517A4 /* Build configuration list for PBXNativeTarget "Feathers-tvOS" */ = { 1405 | isa = XCConfigurationList; 1406 | buildConfigurations = ( 1407 | EA0F0CE61ED0B448000517A4 /* Debug */, 1408 | EA0F0CE71ED0B448000517A4 /* Release */, 1409 | ); 1410 | defaultConfigurationIsVisible = 0; 1411 | defaultConfigurationName = Release; 1412 | }; 1413 | EA0F0CE81ED0B448000517A4 /* Build configuration list for PBXNativeTarget "Feathers-tvOSTests" */ = { 1414 | isa = XCConfigurationList; 1415 | buildConfigurations = ( 1416 | EA0F0CE91ED0B448000517A4 /* Debug */, 1417 | EA0F0CEA1ED0B448000517A4 /* Release */, 1418 | ); 1419 | defaultConfigurationIsVisible = 0; 1420 | defaultConfigurationName = Release; 1421 | }; 1422 | EA0F0D1C1ED0B50D000517A4 /* Build configuration list for PBXNativeTarget "Feathers-watchOS" */ = { 1423 | isa = XCConfigurationList; 1424 | buildConfigurations = ( 1425 | EA0F0D1D1ED0B50D000517A4 /* Debug */, 1426 | EA0F0D1E1ED0B50D000517A4 /* Release */, 1427 | ); 1428 | defaultConfigurationIsVisible = 0; 1429 | defaultConfigurationName = Release; 1430 | }; 1431 | EA12E0661ECE950100A0DCF7 /* Build configuration list for PBXNativeTarget "Feathers-iOSTests" */ = { 1432 | isa = XCConfigurationList; 1433 | buildConfigurations = ( 1434 | EA12E0671ECE950100A0DCF7 /* Debug */, 1435 | EA12E0681ECE950100A0DCF7 /* Release */, 1436 | ); 1437 | defaultConfigurationIsVisible = 0; 1438 | defaultConfigurationName = Release; 1439 | }; 1440 | EA452A611EA2B14300427CEF /* Build configuration list for PBXProject "Feathers" */ = { 1441 | isa = XCConfigurationList; 1442 | buildConfigurations = ( 1443 | EA452A791EA2B14300427CEF /* Debug */, 1444 | EA452A7A1EA2B14300427CEF /* Release */, 1445 | ); 1446 | defaultConfigurationIsVisible = 0; 1447 | defaultConfigurationName = Release; 1448 | }; 1449 | EAC0BCE31EC90FDB0049FB13 /* Build configuration list for PBXNativeTarget "Feathers-iOS" */ = { 1450 | isa = XCConfigurationList; 1451 | buildConfigurations = ( 1452 | EAC0BCE41EC90FDB0049FB13 /* Debug */, 1453 | EAC0BCE51EC90FDB0049FB13 /* Release */, 1454 | ); 1455 | defaultConfigurationIsVisible = 0; 1456 | defaultConfigurationName = Release; 1457 | }; 1458 | /* End XCConfigurationList section */ 1459 | }; 1460 | rootObject = EA452A5E1EA2B14300427CEF /* Project object */; 1461 | } 1462 | --------------------------------------------------------------------------------