├── .clang-format ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── DCO1.1.txt ├── Jenkinsfile ├── LICENSE ├── Package.swift ├── README.md ├── Source └── SwiftCloudant │ ├── CouchClient.swift │ ├── HTTP │ ├── RequestBuilder.swift │ ├── RequestExecutor.swift │ └── URLSession.swift │ ├── Info.plist │ ├── Operations │ ├── Database │ │ └── GetChangesOperation.swift │ ├── Documents │ │ ├── BulkDocs.swift │ │ ├── DeleteAttachmentOperation.swift │ │ ├── DeleteDocumentOperation.swift │ │ ├── GetDocumentOperation.swift │ │ ├── PutAttachmentOperation.swift │ │ ├── PutDocumentOperation.swift │ │ └── ReadAttachmentOperation.swift │ ├── Operation.swift │ ├── OperationProtocols.swift │ ├── Query │ │ ├── CreateQueryIndexOperation.swift │ │ ├── DeleteQueryIndexOperation.swift │ │ └── FindDocumentsOperation.swift │ ├── Server │ │ ├── CouchDatabaseOperation.swift │ │ ├── CreateDatabaseOperation.swift │ │ ├── DeleteDatabaseOperation.swift │ │ └── GetAllDatabasesOperation.swift │ └── Views │ │ ├── GetAllDocsOperation.swift │ │ ├── QueryViewOperation.swift │ │ ├── ViewLikeOperation.swift │ │ └── ViewPaging.swift │ └── SwiftCloudant.h ├── SwiftCloudant.podspec ├── Tests ├── Info.plist ├── LinuxMain.swift ├── SwiftCloudantTests │ ├── BulkDocsTests.swift │ ├── CouchClientTest.swift │ ├── CreateDatabaseTests.swift │ ├── CreateQueryIndexTests.swift │ ├── DeleteAttachmentTests.swift │ ├── DeleteDocumentTests.swift │ ├── DeleteQueryIndexTests.swift │ ├── FindDocumentOperationTests.swift │ ├── GetAllDatabasesTests.swift │ ├── GetAllDocsTest.swift │ ├── GetChangesTests.swift │ ├── GetDocumentTests.swift │ ├── InterceptableSessionTests.swift │ ├── PutAttachmentTests.swift │ ├── PutDocumentTests.swift │ ├── QueryViewTests.swift │ ├── ReadAttachmentTests.swift │ ├── TestHelpers.swift │ └── ViewPagingTests.swift └── TestSettings.plist ├── cloudant-service.yml ├── couchdb.yml ├── doc └── Architecture.md └── swift.yml /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | ColumnLimit: 100 3 | IndentWidth: 4 4 | UseTab: Never 5 | BreakBeforeBraces: Linux 6 | BreakBeforeBinaryOperators: false 7 | KeepEmptyLinesAtTheStartOfBlocks: false 8 | ObjCSpaceAfterProperty: true 9 | ObjCSpaceBeforeProtocolList: true 10 | PointerBindsToType: false 11 | SpacesBeforeTrailingComments: 2 12 | SpaceBeforeParens: ControlStatements 13 | TabWidth: 4 14 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 15 | AllowShortBlocksOnASingleLine: false 16 | MaxEmptyLinesToKeep: 1 17 | ObjCSpaceAfterProperty: true 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please [read these guidelines](http://ibm.biz/cdt-issue-guide) before opening an issue. 2 | 3 | 4 | 5 | ## Bug Description 6 | 7 | ### 1. Steps to reproduce and the simplest code sample possible to demonstrate the issue 8 | 12 | 13 | ### 2. What you expected to happen 14 | 15 | ### 3. What actually happened 16 | 17 | ## Environment details 18 | 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | ## Checklist 5 | 6 | - [ ] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](../blob/master/DCO1.1.txt) 7 | - [ ] Added tests for code changes _or_ test/build only changes 8 | - [ ] Updated the change log file (`CHANGES.md`|`CHANGELOG.md`) _or_ test/build only changes 9 | - [ ] Completed the PR template below: 10 | 11 | ## Description 12 | 29 | 30 | ## Approach 31 | 32 | 38 | 39 | ## Schema & API Changes 40 | 41 | 52 | 53 | ## Security and Privacy 54 | 55 | 66 | 67 | ## Testing 68 | 69 | 92 | 93 | ## Monitoring and Logging 94 | 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | profile 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | *.hmap 19 | build 20 | *.xcodeproj 21 | .build 22 | 23 | #CocoaPods 24 | Pods 25 | Podfile.lock 26 | 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | 4 | matrix: 5 | allow_failures: 6 | - os: linux 7 | include: 8 | - os: linux 9 | dist: xenial 10 | sudo: required 11 | script: 12 | - docker-compose -f couchdb.yml -f swift.yml up --abort-on-container-exit 13 | after_script: 14 | - docker-compose -f couchdb.yml -f swift.yml down -v --rmi local 15 | - os: osx 16 | language: objective-c 17 | osx_image: xcode10.2 18 | before_script: 19 | - brew install couchdb 20 | - brew services start couchdb 21 | script: 22 | - swift build && swift test 23 | 24 | services: 25 | - couchdb 26 | - docker 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | - [DEPRECATED] This library is now deprecated and will be EOL on Dec 31 2021. 3 | 4 | # 0.9.0 (2019-04-15) 5 | - [FIXED] Fixed scope of `Operation.Error` to be exposed as public. 6 | - [BREAKING CHANGE] Projects using older Swift tool and language versions will not work with versions newer than 0.8.0. 7 | - [UPGRADED] Minimum Swift language version to 4.2. 8 | - [UPGRADED] Swift tools version and package manifest to version 5.0. 9 | 10 | # 0.8.0 (2018-03-20) 11 | 12 | - [NEW] Runs on Swift 4.x on Linux with some limitations (see note). 13 | - [FIXED] Set the default Swift language version in the SwiftCloudant.podspec to 3.1. 14 | - [FIXED] Fixed warning for non-exhaustive switch statement. 15 | - [NOTE] The `GetChangesOperation` is not currently supported by the Linux platform, 16 | see [issue #176](https://github.com/cloudant/swift-cloudant/issues/176). 17 | 18 | # 0.7.0 (2017-04-10) 19 | 20 | - [NEW] Implement convenience init for creating client from VCAP services metadata. 21 | - [NEW] Allow server to generate document IDs if they are set as `nil` in the documents being saved. 22 | - [FIXED] Access level warning in the ViewPager class. 23 | - [FIXED] Crash on deallocate for unused client. 24 | - [FIXED] Resolved issue where an infinite retry loop would be triggered if 25 | a `401: Forbidden` response was always received from an endpoint. 26 | 27 | # 0.6.0 (2016-09-16) 28 | 29 | - [NEW] Support for Swift 3.0 Release for Darwin Platforms. 30 | - [NEW] New `PutBulkDocsOperation` API. 31 | - [NEW] Support for backing off when a 429 status code is encountered, optionally enabled by using 32 | `ClientConfiguration` struct. 33 | - [NEW] New `GetChangesOperation` API. 34 | - [NEW] New `ViewPager` API. 35 | - [FIXED] Correctly handle differences between CouchDb and Cloudant for cookie expiration. 36 | 37 | # 0.5.0 38 | 39 | - [NEW] Support Swift SNAPSHOT 2016-07-26 40 | 41 | # 0.4.0 (2016-07-28) 42 | 43 | - [NEW] New `GetAllDocs` API. 44 | - [BREAKING] `skip` and `limit` properties on `QueryViewOperation` are now `UInt` 45 | - [BREAKING] All APIs have changed to match the API guidelines laid down by the 46 | swift community. To see the full list of APIs which have changed, see 47 | [issue #96](https://github.com/cloudant/swift-cloudant/issues/96) 48 | 49 | # 0.3.2 (2016-07-08) 50 | 51 | - [FIXED] Created explicit `Sort` and `TextIndexField` initalizers so they are exposed as public. 52 | 53 | # 0.3.1 (2016-07-08) 54 | 55 | - [FIXED] Fixed scope of members of `Sort` struct, they are now exposed 56 | correctly as public. 57 | 58 | # 0.3.0 (2016-06-30) 59 | 60 | - [NEW] New FindDocumentsOperation API. 61 | - [NEW] New DeleteAttachmentOperation API. 62 | - [BREAKING] `Database` class has been removed 63 | - [BREAKING] `CouchOperation` is now a protocol, in order to use `NSOperation` APIs 64 | you need to use the `Operation` class in conjunction with objects that conform 65 | to `CouchOperation` 66 | - [BREAKING] `CouchDatabaseOperation` is now a protocol. 67 | - [NEW] `JsonOperation` and `DataOperation` protocols. 68 | - [BREAKING] `completionHandler` property is no longer defined on `CouchOperation` 69 | its replacement is `completionHander` defined on `JsonOperation` and `DataOperation` 70 | - [BREAKING] The following properties on `CouchOperation` have been renamed: 71 | 72 | | Previous Name | New Name | 73 | |---------------|---------| 74 | | `httpPath` | `endpoint`| 75 | | `httpRequestData` | `data` | 76 | | `httpContentType` | `contentType`| 77 | |`httpMethod` | `method`| 78 | 79 | - [BREAKING] `QueryItems` property has been removed. 80 | - [NEW] parameters property API, used to define HTTP query parameters for a CouchOperation request. 81 | 82 | 83 | # 0.2.1 (2016-05-13) 84 | 85 | - [FIX] Fixed compatibility with Swift Development Snapshot from 2016-05-09 86 | 87 | # 0.2.0 (2016-05-11) 88 | 89 | - [BREAKING CHANGE] Renamed and changed completion handler signatures for **all** 90 | operations. They are now called `completionHandler` instead of `xxxxCompletionHandler`. 91 | Their signature is now `(response:[String:AnyObject]?, httpInfo:HttpInfo?, error:ErrorProtocol?) -> Void`. 92 | - [IMPROVED] Error objects generated by the library now contain more information 93 | such as HTTP status code and the response received. 94 | - [FIX] Fixed issue where the user agent wasn't being generated correctly. 95 | - [FIX] Fixed issue where the `Content-Type` header was not being set correctly. 96 | - [FIX] Fixed issue where a `DatabaseOperation` subclass would pass validation 97 | when the database name was not set. 98 | - [FIX] Fixed issue where the session cookie was not created/set for requests to 99 | a protected CouchDB instance. 100 | - [IMPROVED] Simplify operation subclasses processing responses in `CouchOperation` 101 | - [NEW] New Hook for operations subclasses to perform additional actions such 102 | as a handler for each row in a view response. 103 | - [NEW] New `QueryViewOperation`, to query CouchDB views, see the class documentation 104 | for usage. 105 | - [NEW] New hook for operation subclasses, `serialise`, for usage see method documentation. 106 | 107 | # 0.1.0 (2016-04-21) 108 | 109 | Initial Release 110 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Issues 4 | 5 | Please [read these guidelines](http://ibm.biz/cdt-issue-guide) before opening an issue. 6 | If you still need to open an issue then we ask that you complete the template as 7 | fully as possible. 8 | 9 | ## Pull requests 10 | 11 | We welcome pull requests, but ask contributors to keep in mind the following: 12 | 13 | * Only PRs with the template completed will be accepted 14 | * We will not accept PRs for user specific functionality 15 | 16 | ### Developer Certificate of Origin 17 | 18 | In order for us to accept pull-requests, the contributor must sign-off a 19 | [Developer Certificate of Origin (DCO)](DCO1.1.txt). This clarifies the 20 | intellectual property license granted with any contribution. It is for your 21 | protection as a Contributor as well as the protection of IBM and its customers; 22 | it does not change your rights to use your own Contributions for any other purpose. 23 | 24 | Please read the agreement and acknowledge it by ticking the appropriate box in the PR 25 | text, for example: 26 | 27 | - [x] Tick to sign-off your agreement to the Developer Certificate of Origin (DCO) 1.1 28 | 29 | ## General information 30 | 31 | ### Adding files 32 | 33 | Production source files are stored in the `Source` tree with tests stored in the 34 | `Tests` tree. 35 | 36 | `Tests/SwiftCloudantTests` contains all the tests for the `SwiftCloudant` module. 37 | 38 | ## Requirements 39 | 40 | Starting from scratch you'll need: 41 | 42 | * Xcode 43 | * Xcode command line tools 44 | * [Swift 3 or 4](https://swift.org/getting-started/#installing-swift) 45 | 46 | First, download Xcode from the app store or [ADC][adc]. 47 | 48 | When this is installed, install the command line tools. The simplest way is: 49 | 50 | ```sh 51 | xcode-select --install 52 | ``` 53 | 54 | ### Getting started with the project 55 | 56 | The Swift Package Manager is the default build tool for SwiftCloudant. In order 57 | to use Xcode as a development envrionment run the command: 58 | 59 | ```sh 60 | $ swift package generate-xcodeproj 61 | ``` 62 | 63 | ## Building 64 | 65 | ```sh 66 | swift build 67 | ``` 68 | 69 | ## Testing 70 | 71 | ### Test Configuration 72 | 73 | By default, the tests will attempt to use CouchDB located at `http://localhost:5984`, 74 | these can be configured using the Test Bundle's `TestSettings.plist` file. The properties 75 | are as follows: 76 | 77 | | Property Name | Purpose | Default | 78 | |---------------|---------|---------| 79 | | SERVER_URL | The URL to connect to | http://localhost:5984 | 80 | | SERVER_USER | The username to use when accessing the server | `nil` | 81 | | SERVER_PASSWORD | The password to use when accessing the server | `nil`| 82 | 83 | 84 | Note: Since the move to using the SwiftPackageManager test configuration options 85 | currently do not work. For now tests will only connect to `http://localhost:5984` 86 | 87 | ### Running the tests 88 | 89 | Run: 90 | ```bash 91 | export TOOLCHAINS=swift 92 | swift build 93 | swift test 94 | ``` 95 | 96 | or if you have a generated xcode project you can use: 97 | ```bash 98 | export TOOLCHAINS=swift 99 | xcodebuild -project SwiftCloudant.xcproj -scheme SwiftCloudant test 100 | ``` 101 | 102 | Currently only OS X / macOS is supported for testing. 103 | 104 | __NOTE__: You should also check that any changes made compile using the Swift Package Manager, 105 | use the command `swift build` in the root of the checkout to compile using the Swift Package Manager. 106 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Rhys Short, IBM Corp. (rhyshort@uk.ibm.com) 2 | Michael Rhodes, IBM Corp. (mike.rhodes@uk.ibm.com) 3 | Stefan Kruger, IBM Corp. (stefan.kruger@uk.ibm.com) 4 | Richard Ellis, IBM Corp. (ricellis@uk.ibm.com) 5 | Taylor Franklin, IBM Corp. (tfranklin@us.ibm.com) 6 | -------------------------------------------------------------------------------- /DCO1.1.txt: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | 3 | /* 4 | * Copyright © 2017, 2019 IBM Corp. All rights reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 7 | * except in compliance with the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the 12 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | * either express or implied. See the License for the specific language governing permissions 14 | * and limitations under the License. 15 | */ 16 | def getEnvForServer(server) { 17 | // Define the matrix environments 18 | def testEnvVars = ['DOCKER_HOST='] 19 | if (server == 'cloudant-service') { 20 | testEnvVars.add("SERVER_URL=https://${SERVER_USER}.cloudant.com") 21 | } 22 | return testEnvVars 23 | } 24 | 25 | def buildAndTest(nodeLabel, server) { 26 | node(nodeLabel == 'linux' ? null : nodeLabel) { 27 | checkout scm 28 | withCredentials([usernamePassword(credentialsId: 'clientlibs-test', usernameVariable: 'SERVER_USER', passwordVariable: 'SERVER_PASSWORD')]) { 29 | withEnv(getEnvForServer(server)) { 30 | if (nodeLabel == 'linux') { 31 | try { 32 | sh "docker-compose -f ${server}.yml -f swift.yml up --abort-on-container-exit" 33 | } finally { 34 | sh "docker-compose -f ${server}.yml -f swift.yml down -v --rmi local" 35 | } 36 | } else { 37 | sh "swift build" 38 | sh "swift test" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | 46 | stage('QA') { 47 | axes = [:] 48 | services = ['couchdb', 'cloudant-service'] 49 | oses = ['linux'] 50 | services.each { server -> 51 | oses.each { os -> 52 | axes.put("${os}-${server}", {buildAndTest(os, server)}) 53 | } 54 | } 55 | axes.put("macos-cloudant-service", {buildAndTest('macos', 'cloudant-service')}) 56 | parallel(axes) 57 | } 58 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 6 | // except in compliance with the License. You may obtain a copy of the License at 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // Unless required by applicable law or agreed to in writing, software distributed under the 9 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 10 | // either express or implied. See the License for the specific language governing permissions 11 | // and limitations under the License. 12 | // 13 | 14 | 15 | import PackageDescription 16 | 17 | let package = Package( 18 | name: "SwiftCloudant", 19 | products: [ 20 | .library( 21 | name: "SwiftCloudant", 22 | targets: ["SwiftCloudant"]), 23 | ], 24 | targets: [ 25 | .target( 26 | name: "SwiftCloudant"), 27 | .testTarget( 28 | name: "SwiftCloudantTests", 29 | dependencies: ["SwiftCloudant"]) 30 | ], 31 | swiftLanguageVersions: [.v4_2, .v5] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :warning: NO LONGER MAINTAINED :warning: 2 | 3 | **This library is end-of-life and no longer supported.** 4 | 5 | This repository will not be updated. The repository will be kept available in read-only mode. 6 | 7 | Please see the list of [forks](https://github.com/cloudant/swift-cloudant/network/members) for continuing development efforts [#194](https://github.com/cloudant/swift-cloudant/issues/194). 8 | 9 | For FAQs and additional information please refer to the 10 | [Cloudant blog](https://blog.cloudant.com/2021/06/30/Cloudant-SDK-Transition.html). 11 | 12 | # swift-cloudant 13 | [![Version](http://cocoapod-badges.herokuapp.com/v/SwiftCloudant/badge.png)](http://cocoadocs.org/docsets/SwiftCloudant) 14 | [![Platform](http://cocoapod-badges.herokuapp.com/p/CDTDatastore/SwiftCloudant.png)](http://cocoadocs.org/docsets/SwiftCloudant) 15 | [![Build Status](https://travis-ci.org/cloudant/swift-cloudant.svg?branch=master)](https://travis-ci.org/cloudant/swift-cloudant) 16 | 17 | **Applications use swift-cloudant to store, index and query remote 18 | JSON data on Cloudant or CouchDB.** 19 | 20 | Swift-Cloudant is an [Apache CouchDB™][acdb] client written in Swift. It 21 | is built by [Cloudant](https://cloudant.com) and is available under the 22 | [Apache 2.0 license][ap2]. 23 | 24 | [ap2]: https://github.com/cloudant/sync-android/blob/master/LICENSE 25 | [acdb]: http://couchdb.apache.org/ 26 | 27 | ## Early-Release 28 | 29 | This is an early-release version of the library, with support for the following operations: 30 | 31 | - Getting documents by doc ID. 32 | - Updating and deleting documents. 33 | - Creating and deleting databases. 34 | - Changes Feed operations 35 | - Creating, updating and deleting of attachments. 36 | - Querying views. 37 | - Creating, deleting and querying indexes. 38 | 39 | We will be rounding out the feature set in upcoming releases. 40 | 41 | **Currently it does not support being called from Objective-C.** 42 | 43 | ## Support 44 | 45 | `SwiftCloudant` is supported, however since it is an early release it is 46 | on a "best effort" basis. 47 | 48 | ### Platforms 49 | 50 | Currently Swift Cloudant supports: 51 | 52 | Swift versions 53 | - Minimum Swift language version 4.2 54 | - Minimum Swift tools version 5.0 55 | 56 | Platforms 57 | - macOS 58 | - Linux 59 | 60 | Swift Cloudant is unsupported on: 61 | 62 | - iOS (should work, but hasn't been tested) 63 | - tvOS 64 | - watchOS 65 | 66 | ## Using in your project 67 | 68 | SwiftCloudant is available using the Swift Package Manager and [CocoaPods](http://cocoapods.org). 69 | 70 | To use with CocoaPods add the following line to your Podfile: 71 | 72 | ```ruby 73 | pod 'SwiftCloudant', :git => 'https://github.com/cloudant/swift-cloudant.git' 74 | ``` 75 | 76 | To use with the swift package manager add the following line to your dependencies 77 | in your Package.swift: 78 | ```swift 79 | .Package(url: "https://github.com/cloudant/swift-cloudant.git") 80 | ``` 81 | ## Overview of the library 82 | ```swift 83 | import SwiftCloudant 84 | 85 | // Create a CouchDBClient 86 | let cloudantURL = URL(string:"https://username.cloudant.com")! 87 | let client = CouchDBClient(url:cloudantURL, username:"username", password:"password") 88 | let dbName = "database" 89 | 90 | // Create a document 91 | let create = PutDocumentOperation(id: "doc1", body: ["hello":"world"], databaseName: dbName) {(response, httpInfo, error) in 92 | if let error = error as? SwiftCloudant.Operation.Error { 93 | switch error { 94 | case .http(let httpError): 95 | print("http error status code: \(httpError.statusCode) response: \(httpError.response)") 96 | default: 97 | print("Encountered an error while creating a document. Error:\(error)") 98 | } 99 | } else { 100 | print("Created document \(response?["id"]) with revision id \(response?["rev"])") 101 | } 102 | } 103 | client.add(operation:create) 104 | 105 | // create an attachment 106 | let attachment = "This is my awesome essay attachment for my document" 107 | let putAttachment = PutAttachmentOperation(name: "myAwesomeAttachment", 108 | contentType: "text/plain", 109 | data: attachment.data(using: String.Encoding.utf8, allowLossyConversion: false)!, 110 | documentID: "doc1", 111 | revision: "1-revisionidhere", 112 | databaseName: dbName) { (response, info, error) in 113 | if let error = error { 114 | print("Encountered an error while creating an attachment. Error:\(error)") 115 | } else { 116 | print("Created attachment \(response?["id"]) with revision id \(response?["rev"])") 117 | } 118 | } 119 | client.add(operation: putAttachment) 120 | 121 | // Read a document 122 | let read = GetDocumentOperation(id: "doc1", databaseName: dbName) { (response, httpInfo, error) in 123 | if let error = error { 124 | print("Encountered an error while reading a document. Error:\(error)") 125 | } else { 126 | print("Read document: \(response)") 127 | } 128 | } 129 | client.add(operation:read) 130 | 131 | // Delete a document 132 | let delete = DeleteDocumentOperation(id: "doc1", 133 | revision: "1-revisionidhere", 134 | databaseName: dbName) { (response, httpInfo, error) in 135 | if let error = error { 136 | print("Encountered an error while deleting a document. Error: \(error)") 137 | } else { 138 | print("Document deleted") 139 | } 140 | } 141 | client.add(operation:delete) 142 | ``` 143 | ## Requirements 144 | 145 | Currently they are no third party dependencies. 146 | 147 | ## Contributors 148 | 149 | See [CONTRIBUTORS](CONTRIBUTORS). 150 | 151 | ## Contributing to the project 152 | 153 | See [CONTRIBUTING](CONTRIBUTING.md). 154 | 155 | ## License 156 | 157 | See [LICENSE](LICENSE) 158 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/CouchClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CouchClient.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import Dispatch 19 | 20 | /** 21 | Configures an instance of CouchDBClient. 22 | */ 23 | public struct ClientConfiguration { 24 | /** 25 | Should the client back off when a 429 response is encountered. Backing off will result 26 | in the client retrying the request at a later time. 27 | */ 28 | public var shouldBackOff: Bool 29 | /** 30 | The number of attempts the client should make to back off and get a successful response 31 | from server. 32 | 33 | - Note: The maximum is hard limited by the client to 10 retries. 34 | */ 35 | public var backOffAttempts: UInt 36 | 37 | /** 38 | The initial value to use when backing off. 39 | 40 | - Remark: The client uses a doubling back off when a 429 reponse is encountered, so care is required when selecting 41 | the initial back off value and the number of attempts to back off and successfully retreive a response from the server. 42 | */ 43 | public var initialBackOff:DispatchTimeInterval 44 | 45 | /** 46 | Creates an ClientConfiguration 47 | - parameter shouldBackOff: Should the client automatically back off. 48 | - parameter backOffAttempts: The number of attempts the client should make to back off and 49 | get a successful response. Default 3. 50 | - parameter initialBackOff: The time to wait before retrying when the first 429 response is received, 51 | this value will be doubled for each subsequent back off 52 | 53 | */ 54 | public init(shouldBackOff: Bool, backOffAttempts: UInt = 3, initialBackOff: DispatchTimeInterval = .milliseconds(250)){ 55 | self.shouldBackOff = shouldBackOff 56 | self.backOffAttempts = backOffAttempts 57 | self.initialBackOff = initialBackOff 58 | } 59 | 60 | } 61 | 62 | 63 | /** 64 | Class for running operations against a CouchDB instance. 65 | */ 66 | public class CouchDBClient { 67 | 68 | private let session: InterceptableSession 69 | private let queue: OperationQueue 70 | 71 | internal let username: String? 72 | internal let password: String? 73 | internal let rootURL: URL 74 | 75 | // The version number of swift-cloudant, as a string 76 | static let version = "0.9.1-SNAPSHOT" 77 | 78 | /** 79 | Creates a CouchDBClient instance. 80 | 81 | - parameter url: url of the server to connect to. 82 | - parameter username: the username to use when authenticating. 83 | - parameter password: the password to use when authenticating. 84 | - parameter configuration: configuration options for the client. 85 | */ 86 | public init(url: URL, 87 | username: String?, 88 | password: String?, 89 | configuration: ClientConfiguration = ClientConfiguration(shouldBackOff: false)) { 90 | self.rootURL = url 91 | self.username = username 92 | self.password = password 93 | queue = OperationQueue() 94 | 95 | let sessionConfiguration = InterceptableSessionConfiguration(shouldBackOff: configuration.shouldBackOff, 96 | backOffRetries: configuration.backOffAttempts, 97 | initialBackOff: configuration.initialBackOff, 98 | username: username, 99 | password: password) 100 | 101 | self.session = InterceptableSession(delegate: nil, configuration: sessionConfiguration) 102 | 103 | } 104 | 105 | /** 106 | Adds an operation to the queue to be executed. 107 | - parameter operation: the operation to add to the queue. 108 | - returns: An `Operation` instance which represents the executing 109 | `CouchOperation` 110 | */ 111 | @discardableResult 112 | public func add(operation: CouchOperation) -> Operation { 113 | let cOp = Operation(couchOperation: operation) 114 | self.add(operation: cOp) 115 | return cOp 116 | } 117 | 118 | /** 119 | Adds an operation to the queue to be executed. 120 | - parameter operation: the operation to add to the queue. 121 | */ 122 | func add(operation: Operation) { 123 | operation.mSession = self.session 124 | operation.rootURL = self.rootURL 125 | queue.addOperation(operation) 126 | } 127 | 128 | } 129 | 130 | /** 131 | Extension for loading Cloud Foundry service configuration. 132 | */ 133 | public extension CouchDBClient { 134 | 135 | /** 136 | A enum of errors which could be returned. 137 | */ 138 | enum Error: Swift.Error { 139 | 140 | /** 141 | Failed to decode VCAP_SERVICES environment variable as JSON. 142 | */ 143 | case invalidVCAP 144 | 145 | /** 146 | Missing VCAP_SERVICES environment variable. 147 | */ 148 | case missingVCAP 149 | 150 | /** 151 | Missing Cloudant service from VCAP_SERVICES environment variable. 152 | */ 153 | case missingCloudantService 154 | 155 | /** 156 | Instance name is required. 157 | */ 158 | case instanceNameRquired 159 | } 160 | 161 | /** 162 | Creates a CouchDBClient instance using credentials from the Cloud Foundry environment variable. 163 | 164 | - parameter vcapServices: contents of VCAP_SERVICES environment variable. 165 | - parameter instanceName: Bluemix service instance name. 166 | - parameter configuration: configuration options for the client. 167 | */ 168 | convenience init(vcapServices: String, instanceName: String? = nil, configuration: ClientConfiguration = ClientConfiguration(shouldBackOff: false)) throws { 169 | var cloudantService: [String:Any]? = nil 170 | 171 | guard let data = vcapServices.data(using: .utf8), 172 | let app = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else { 173 | throw Error.invalidVCAP 174 | } 175 | 176 | guard let services = app["cloudantNoSQLDB"] as? [[String: Any]] else { 177 | throw Error.missingCloudantService 178 | } 179 | 180 | if instanceName == nil { 181 | if services.count == 1 { 182 | cloudantService = services.first! 183 | } else { 184 | throw Error.instanceNameRquired 185 | } 186 | } else { 187 | for service in services { 188 | if instanceName == service["name"] as? String { 189 | cloudantService = service 190 | break 191 | } 192 | } 193 | } 194 | 195 | guard let cloudant = cloudantService else { 196 | throw Error.missingCloudantService 197 | } 198 | 199 | if let credentials = cloudant["credentials"] as? [String: Any], 200 | let urlStr = credentials["url"] as? String, 201 | let url = URL(string: urlStr) { 202 | self.init(url: url, username: url.user, password: url.password, configuration: configuration) 203 | } else { 204 | throw Error.invalidVCAP 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/HTTP/RequestBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestBuilder.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | // TODO rename to something that makes a little more swift sense. 20 | /** 21 | Designates an operation which provides data to perform a HTTP Request. 22 | */ 23 | internal protocol HTTPRequestOperation { 24 | 25 | /** 26 | The root of url, e.g. `example.cloudant.com` 27 | */ 28 | var rootURL: URL { get } 29 | 30 | /** 31 | The path of the url e.g. `/exampledb/document1/` 32 | */ 33 | var httpPath: String { get } 34 | /** 35 | The method to use for the HTTP request e.g. `GET` 36 | */ 37 | var httpMethod: String { get } 38 | /** 39 | The query items to use for the request 40 | */ 41 | var queryItems: [URLQueryItem] { get } 42 | 43 | /** 44 | The body of the HTTP request or `nil` if there is no data for the request. 45 | */ 46 | var httpRequestBody: Data? { get } 47 | 48 | /** 49 | The content type of the HTTP request payload. This is guranteed to be called 50 | if and only if `httpRequestBody` is not `nil` 51 | */ 52 | var httpContentType: String { get } 53 | 54 | /** 55 | Provides the `InterceptableSession` to use when making HTTP requests. 56 | */ 57 | var session: InterceptableSession { get } 58 | 59 | /** 60 | A function that is called when the operation is completed. 61 | */ 62 | func completeOperation() 63 | /** 64 | A function to process the response from a HTTP request. 65 | 66 | - parameter data: The data returned from the HTTP request or nil if there was an error. 67 | - parameter httpInfo: Information about the HTTP response. 68 | - parameter error: A type representing an error if one occurred or `nil` 69 | */ 70 | func processResponse(data: Data?, httpInfo: HTTPInfo?, error: Error?); 71 | 72 | var isCancelled: Bool { get } 73 | 74 | } 75 | 76 | /** 77 | A class which builds `NSURLRequest` objects from `HTTPRequestOperation` objects. 78 | */ 79 | class OperationRequestBuilder { 80 | 81 | enum Error: Swift.Error { 82 | case URLGenerationFailed 83 | } 84 | 85 | /** 86 | The operation this builder will turn into a HTTP object. 87 | */ 88 | let operation: HTTPRequestOperation 89 | 90 | /** 91 | Creates an OperationRequestBuilder instance. 92 | 93 | - parameter operation: the operation that the request will be built from. 94 | */ 95 | init(operation: HTTPRequestOperation) { 96 | self.operation = operation 97 | } 98 | 99 | /** 100 | Builds the NSURLRequest from the operation in the property `operation` 101 | */ 102 | func makeRequest() throws -> URLRequest { 103 | 104 | guard let components = NSURLComponents(url: operation.rootURL, resolvingAgainstBaseURL: false) 105 | else { 106 | throw Error.URLGenerationFailed 107 | } 108 | components.path = operation.httpPath 109 | var queryItems: [URLQueryItem] = [] 110 | 111 | if let _ = components.queryItems { 112 | queryItems.append(contentsOf: components.queryItems!) 113 | } 114 | 115 | queryItems.append(contentsOf: operation.queryItems) 116 | components.queryItems = queryItems 117 | 118 | guard let url = components.url 119 | else { 120 | throw Error.URLGenerationFailed 121 | } 122 | 123 | var request = URLRequest(url: url) 124 | request.cachePolicy = .useProtocolCachePolicy 125 | request.timeoutInterval = 10.0 126 | request.httpMethod = operation.httpMethod 127 | 128 | if let body = operation.httpRequestBody { 129 | request.httpBody = body 130 | request.setValue(operation.httpContentType, forHTTPHeaderField: "Content-Type") 131 | } 132 | 133 | return request 134 | 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/HTTP/RequestExecutor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestExecutor.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | Contains HTTP response information. 21 | */ 22 | public struct HTTPInfo { 23 | /** 24 | The status code of the HTTP request. 25 | */ 26 | public let statusCode: Int 27 | /** 28 | The headers that were returned by the server. 29 | */ 30 | public let headers: [String: String] 31 | } 32 | 33 | /** 34 | Executes a `HTTPRequestOperation`'s HTTP request. 35 | */ 36 | class OperationRequestExecutor: InterceptableSessionDelegate { 37 | 38 | /** 39 | The HTTP task currently processing 40 | */ 41 | var task: URLSessionTask? 42 | /** 43 | The operation which this OperationRequestExecutor is Executing. 44 | */ 45 | let operation: HTTPRequestOperation 46 | 47 | var buffer: Data 48 | var response: HTTPURLResponse? 49 | /** 50 | Creates an OperationRequestExecutor. 51 | - parameter operation: The operation that this OperationRequestExecutor will execute 52 | */ 53 | init(operation: HTTPRequestOperation) { 54 | self.operation = operation 55 | task = nil 56 | buffer = Data() 57 | } 58 | 59 | func received(data: Data) { 60 | // This class doesn't support streaming of data 61 | // so we buffer until the request completes 62 | // and then we will deliver it to the 63 | // operation in chunk. 64 | buffer.append(data) 65 | } 66 | 67 | func received(response: HTTPURLResponse) { 68 | // Store the response to deliver with the data when the task completes. 69 | self.response = response 70 | } 71 | 72 | func completed(error: Error?) { 73 | self.task = nil // allow task to be deallocated. 74 | 75 | // task has completed, handle the operation canceling etc. 76 | if self.operation.isCancelled { 77 | self.operation.completeOperation() 78 | return 79 | } 80 | 81 | let httpInfo: HTTPInfo? 82 | 83 | if let response = response { 84 | var headers:[String: String] = [:] 85 | for (key, value) in response.allHeaderFields { 86 | headers["\(key)"] = "\(value)" 87 | } 88 | httpInfo = HTTPInfo(statusCode: response.statusCode, headers: headers) 89 | } else { 90 | httpInfo = nil 91 | } 92 | 93 | self.operation.processResponse(data: buffer, httpInfo: httpInfo, error: error) 94 | self.operation.completeOperation() 95 | 96 | } 97 | 98 | /** 99 | Executes the HTTP request for the operation held in the `operation` property 100 | */ 101 | func executeRequest () { 102 | 103 | do { 104 | let builder = OperationRequestBuilder(operation: self.operation) 105 | let request = try builder.makeRequest() 106 | 107 | self.task = self.operation.session.dataTask(request: request, delegate: self) 108 | self.task?.resume() 109 | } catch { 110 | self.operation.processResponse(data: nil, httpInfo: nil, error: error) 111 | self.operation.completeOperation() 112 | } 113 | 114 | } 115 | 116 | /** 117 | Cancels the currently processing HTTP task. 118 | */ 119 | func cancel() { 120 | if let task = task { 121 | task.cancel() 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Database/GetChangesOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetChangesOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 22/08/2016. 6 | // 7 | // Copyright (C) 2016 IBM Corp. 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 10 | // except in compliance with the License. You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software distributed under the 13 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 14 | // either express or implied. See the License for the specific language governing permissions 15 | // and limitations under the License. 16 | // 17 | 18 | 19 | import Foundation 20 | 21 | /** 22 | Operation to get the changes feed for a database. 23 | 24 | Example usage: 25 | ``` 26 | let changes = GetChangesOperation(dbName: "exampleDB", changeHandler: {(change) in 27 | // do something for each change. 28 | }) { (response, info, error) in 29 | if let error = error { 30 | // handle the error 31 | } else { 32 | // process the changes feed result. 33 | } 34 | } 35 | ``` 36 | */ 37 | public class GetChangesOperation : CouchDatabaseOperation, JSONOperation { 38 | 39 | public typealias Json = [String: Any] 40 | 41 | /** 42 | Abstract representation of a CouchDB Sequence. No assumptions should be 43 | made about the concrete type that is returned from the server, it should be used 44 | transparently. 45 | */ 46 | public typealias Sequence = Any 47 | 48 | /** 49 | The style of changes feed that should be requested. 50 | */ 51 | public enum Style : String { 52 | /** 53 | Only the "winning" revision. 54 | */ 55 | case main = "main_only" 56 | /** 57 | All "leaf" revisions (include previously deleted conflicts). 58 | */ 59 | case allLeaves = "all_docs" 60 | } 61 | 62 | public let databaseName: String 63 | 64 | public let completionHandler: (([String: Any]?, HTTPInfo?, Error?) -> Void)? 65 | 66 | /** 67 | List of document IDs to limit the changes to. 68 | */ 69 | public let docIDs: [String]? 70 | 71 | /** 72 | Include conflict information in the response. 73 | */ 74 | public let conflicts: Bool? 75 | 76 | /** 77 | Sort the changes feed in descending sequence order. 78 | */ 79 | public let descending: Bool? 80 | 81 | /** 82 | The filter function to use to filter the feed. 83 | */ 84 | public let filter: String? 85 | 86 | /** 87 | Include the associated document with each result in the changes feed. 88 | */ 89 | public let includeDocs: Bool? 90 | 91 | /** 92 | Include attachments as Base-64 encoded data in the document. 93 | */ 94 | public let includeAttachments: Bool? 95 | 96 | /** 97 | Include information encoding in attachment stubs. 98 | */ 99 | public let includeAttachmentEncodingInformation: Bool? 100 | 101 | /** 102 | The number of results that the feed should be limited to. 103 | */ 104 | public let limit: Int? 105 | 106 | /** 107 | Return results starting from the sequence after. 108 | */ 109 | public let since: Sequence? 110 | 111 | /** 112 | Specifies how many revisions are returned in the changes array. 113 | */ 114 | public let style: Style? 115 | 116 | /** 117 | The view to use for filtering the changes feed, to be used with the `_view` filter. 118 | */ 119 | public let view: String? 120 | 121 | /** 122 | A handler to run for each entry in the `results` array in the response. 123 | */ 124 | public let changeHandler: (([String: Any]) -> Void)? // this will be each result in the `results` section of the response 125 | 126 | /** 127 | Creates the operation. 128 | 129 | - param databaseName: The name of the database from which to get the changes 130 | - param docIDs: Document IDs to limit the changes result to. 131 | - param conflicts: include information about conflicts in the response 132 | - param descending: sort the changes feed in descending sequence order. 133 | - param filter: the name of a filter function to use to filter the changes feed. 134 | - param includeDocs: include the document contents with the changes feed result. 135 | - param includeAttachments: include the attachments inline with the document results 136 | - param includeAttachmentEncodingInformation: Include attachment encoding information for attachment stubs 137 | - param limit: the number of results the response should be limited to. 138 | - param since: Return results starting from the sequence after this one. 139 | - param style: the style of changes feed that should be returned 140 | - param view: The view to use for filtering the changes feed, should be used with `_view` filter. 141 | - param changeHandler: A handler to call for each change returned from the server. 142 | - param completionHander: A handler to call when the operation has compeleted. 143 | */ 144 | public init(databaseName:String, 145 | docIDs:[String]? = nil, 146 | conflicts: Bool? = nil, 147 | descending: Bool? = nil, 148 | filter: String? = nil, 149 | includeDocs: Bool? = nil, 150 | includeAttachments: Bool? = nil, 151 | includeAttachmentEncodingInformation: Bool? = nil, 152 | limit: Int? = nil, 153 | since: Int? = nil, 154 | style: Style? = nil, 155 | view: String? = nil, 156 | changeHandler: (([String: Any]) -> Void)? = nil, 157 | completionHandler: (([String: Any]?, HTTPInfo?, Error?) -> Void)? = nil){ 158 | self.databaseName = databaseName 159 | self.docIDs = docIDs 160 | self.conflicts = conflicts 161 | self.descending = descending 162 | self.filter = filter 163 | self.includeDocs = includeDocs 164 | self.includeAttachments = includeAttachments 165 | self.includeAttachmentEncodingInformation = includeAttachmentEncodingInformation 166 | self.limit = limit 167 | self.since = since 168 | self.style = style 169 | self.view = view 170 | self.changeHandler = changeHandler 171 | self.completionHandler = completionHandler 172 | } 173 | 174 | public var endpoint: String { 175 | return "/\(self.databaseName)/_changes" 176 | } 177 | 178 | public var parameters: [String : String] { 179 | get { 180 | var params: [String: String] = [:] 181 | 182 | if let conflicts = conflicts { 183 | params["conflicts"] = conflicts.description 184 | } 185 | 186 | if let descending = descending { 187 | params["descending"] = descending.description 188 | } 189 | 190 | 191 | if let filter = filter { 192 | params["filter"] = filter 193 | } 194 | 195 | if let includeDocs = includeDocs { 196 | params["include_docs"] = includeDocs.description 197 | } 198 | 199 | if let includeAttachments = includeAttachments { 200 | params["attachments"] = includeAttachments.description 201 | } 202 | 203 | if let includeAttachmentEncodingInformation = includeAttachmentEncodingInformation { 204 | params["att_encoding_info"] = includeAttachmentEncodingInformation.description 205 | } 206 | 207 | if let limit = limit { 208 | params["limit"] = "\(limit)" 209 | } 210 | 211 | if let since = since { 212 | params["since"] = "\(since)" 213 | } 214 | 215 | if let style = style { 216 | params["style"] = style.rawValue 217 | } 218 | 219 | if let view = view { 220 | params["view"] = view 221 | } 222 | 223 | return params 224 | } 225 | } 226 | 227 | private var jsonData: Data? = nil 228 | 229 | public func serialise() throws { 230 | if let docIDs = docIDs { 231 | jsonData = try JSONSerialization.data(withJSONObject: ["doc_ids": docIDs]) 232 | } 233 | } 234 | 235 | public var data: Data? { 236 | return jsonData 237 | } 238 | 239 | public func processResponse(json: Any) { 240 | if let json = json as? [String: Any], 241 | let results = json["results"] as? [[String: Any]] { 242 | for result in results { 243 | self.changeHandler?(result) 244 | } 245 | } 246 | } 247 | 248 | public var method: String { 249 | get { 250 | if let _ = jsonData { 251 | return "POST" 252 | } else { 253 | return "GET" 254 | } 255 | } 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/BulkDocs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BulkDocs.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 27/07/2016. 6 | // 7 | // Copyright (C) 2016 IBM Corp. 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 10 | // except in compliance with the License. You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software distributed under the 13 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 14 | // either express or implied. See the License for the specific language governing permissions 15 | // and limitations under the License. 16 | // 17 | 18 | import Foundation 19 | 20 | /** 21 | An operation to create and update documents in bulk. 22 | 23 | Example usage: 24 | 25 | ``` 26 | let documents = [["hello":"world"], ["foo":"bar", "_id": "foobar", "_rev": "1-revision"]] 27 | let bulkDocs = PutBulkDocsOperation(databaseName: "exampleDB", documents: documents) { response, httpInfo, error in 28 | guard let response = response, httpInfo = httpInfo, error == nil 29 | else { 30 | //handle the error 31 | return 32 | } 33 | 34 | //handle success. 35 | } 36 | 37 | ``` 38 | */ 39 | public class PutBulkDocsOperation : CouchDatabaseOperation, JSONOperation { 40 | 41 | public typealias Json = [[String: Any]] 42 | 43 | public let databaseName: String 44 | 45 | public let completionHandler: (([[String:Any]]?, HTTPInfo?, Error?) -> Void)? 46 | 47 | /** 48 | The documents that make up this request. 49 | */ 50 | public let documents: [[String:Any]] 51 | 52 | /** 53 | If false, CouchDB will not assign documents new revision IDs. This option is normally 54 | used for replication with CouchDB. 55 | */ 56 | public let newEdits: Bool? 57 | 58 | /** 59 | If true the commit mode for the request will be "All or Nothing" meaning that if one document 60 | fails to be created or updated, no documents will be commited to the database. 61 | */ 62 | public let allOrNothing: Bool? 63 | 64 | /** 65 | Creates the operation 66 | 67 | - parameter databaseName: The name of the database where the documents should be created or updated. 68 | - parameter documents: The documents that should be saved to the server. 69 | - parameter newEdits: Should the server treat the request as new edits or save as is. 70 | - parameter allOrNothing: The commit mode for the database, if set to true, if one document fails 71 | to be inserted into the database, all other documents in the request will also not be inserted. 72 | - parameter completionHandler: Optional handler to call when the operation completes. 73 | */ 74 | public init(databaseName: String, 75 | documents:[[String:Any]], 76 | newEdits: Bool? = nil, 77 | allOrNothing: Bool? = nil, 78 | completionHandler: (([[String:Any]]?, HTTPInfo?, Error?) -> Void)? = nil){ 79 | self.databaseName = databaseName 80 | self.documents = documents 81 | self.newEdits = newEdits 82 | self.allOrNothing = allOrNothing 83 | self.completionHandler = completionHandler 84 | } 85 | 86 | public var endpoint: String { 87 | return "/\(databaseName)/_bulk_docs" 88 | } 89 | 90 | public func validate() -> Bool { 91 | return JSONSerialization.isValidJSONObject(documents) 92 | } 93 | 94 | 95 | private var jsonData: Data? 96 | 97 | public func serialise() throws { 98 | var request:[String: Any] = ["docs":documents] 99 | 100 | if let newEdits = newEdits { 101 | request["new_edits"] = newEdits 102 | } 103 | 104 | if let allOrNothing = allOrNothing { 105 | request["all_or_nothing"] = allOrNothing 106 | } 107 | 108 | jsonData = try JSONSerialization.data(withJSONObject: request); 109 | } 110 | 111 | public var data: Data? { 112 | return jsonData 113 | } 114 | 115 | public var method:String { 116 | return "POST" 117 | } 118 | 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/DeleteAttachmentOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteAttachmentOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 17/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | 21 | An Operation to delete an attachment from a document. 22 | 23 | Example usage: 24 | ``` 25 | let deleteAttachment = DeleteAttachmentOperation(name: "myAwesomeAttachment", 26 | documentID: "exampleDocId", 27 | revision: "1-arevision", 28 | databaseName: "exampledb"){(response, info, error) in 29 | if let error = error { 30 | // handle the error 31 | } else { 32 | // process successful response 33 | } 34 | } 35 | client.add(operation: deleteAttachment) 36 | ``` 37 | 38 | */ 39 | public class DeleteAttachmentOperation: CouchDatabaseOperation, JSONOperation { 40 | 41 | /** 42 | 43 | Creates the operation 44 | 45 | - parameter name : The name of the attachment to delete 46 | - parameter documentID : the ID of the document that the attachment is attached to. 47 | - parameter revision : the revision of the document that the attachment is attached to. 48 | - parameter databaseName : the name of the database that the contains the attachment. 49 | - parameter completionHandler: optional handler to run when the operation completes. 50 | */ 51 | public init(name: String, documentID: String, revision: String, databaseName: String, completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 52 | self.name = name 53 | self.documentID = documentID 54 | self.revision = revision 55 | self.databaseName = databaseName 56 | self.completionHandler = completionHandler 57 | } 58 | 59 | public let databaseName: String 60 | 61 | public let completionHandler: (( [String : Any]?, HTTPInfo?, Error?) -> Void)? 62 | 63 | /** 64 | The ID of the document that the attachment is attached to. 65 | 66 | */ 67 | public let documentID: String 68 | 69 | /** 70 | The revision of the document that the attachment is attached to. 71 | */ 72 | public let revision: String 73 | 74 | /** 75 | The name of the attachment. 76 | */ 77 | public let name: String 78 | 79 | public var endpoint: String { 80 | return "/\(self.databaseName)/\(documentID)/\(name)" 81 | } 82 | 83 | public var parameters: [String: String] { 84 | return ["rev": revision] 85 | } 86 | 87 | public var method: String { 88 | return "DELETE" 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/DeleteDocumentOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteDocumentOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 12/04/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | 21 | An operation to delete a document from a database. 22 | 23 | Example usage: 24 | ```` 25 | let delete = DeleteDocumentOperation(id: "exampleDocId", revision: "1-examplerevid", databaseName: "exampledb") { (response, httpInfo, error) in 26 | if let error = error { 27 | NSLog("An error occured attemtping to delete a document") 28 | } else { 29 | NSLog("Document deleted with statusCode \(statusCode!)" 30 | } 31 | } 32 | client.add(delete) 33 | ```` 34 | */ 35 | public class DeleteDocumentOperation: CouchDatabaseOperation, JSONOperation { 36 | 37 | /** 38 | 39 | Creates the operation 40 | - parameter id: the ID of the document to delete 41 | - parameter revision: the revision of the document to delete. 42 | - parameter databaseName: the name of the database which contains the document. 43 | - parameter completionHandler: optional handler to run when the operation completes. 44 | */ 45 | public init(id: String, revision: String, databaseName: String, completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 46 | self.id = id 47 | self.revision = revision 48 | self.databaseName = databaseName 49 | self.completionHandler = completionHandler 50 | } 51 | 52 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 53 | public let databaseName: String 54 | 55 | /** 56 | The revision of the document to delete 57 | */ 58 | public let revision: String 59 | 60 | /** 61 | The id of the document to delete. 62 | */ 63 | public let id: String 64 | 65 | public var method: String { 66 | return "DELETE" 67 | } 68 | 69 | public var endpoint: String { 70 | return "/\(self.databaseName)/\(id)" 71 | } 72 | 73 | public var parameters: [String: String] { 74 | return ["rev": revision] 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/GetDocumentOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetDocumentOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Stefan Kruger on 04/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | 21 | Gets a document from a database. 22 | 23 | Example useage: 24 | 25 | ``` 26 | let getDoc = GetDocumentOperation(id:"example", databaseName:"exampledb"){ (response, httpInfo, error) in 27 | if let error = error { 28 | // handle the error 29 | } else { 30 | // check the response code to determine if the request was successful. 31 | } 32 | } 33 | ``` 34 | 35 | */ 36 | public class GetDocumentOperation: CouchDatabaseOperation, JSONOperation { 37 | public typealias Json = [String: Any] 38 | 39 | 40 | /** 41 | Creates the operation. 42 | 43 | - parameter id: the id of the document to get from the database. 44 | - parameter databaseName : the name of the database which contains the document 45 | - parameter includeRevisions : include information about previous revisions of the document 46 | - parameter revision: the revision of the document to get 47 | - parameter completionHandler: optional handler to run when the operation completes. 48 | */ 49 | public init(id: String, databaseName:String, includeRevisions:Bool? = nil, revision: String? = nil, completionHandler:(([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 50 | self.id = id 51 | self.databaseName = databaseName 52 | self.includeRevisions = includeRevisions 53 | self.revision = revision 54 | self.completionHandler = completionHandler 55 | } 56 | 57 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 58 | 59 | public let databaseName: String 60 | 61 | /** 62 | Include all revisions of the document. 63 | */ 64 | public let includeRevisions: Bool? 65 | 66 | /** 67 | The revision of the document to get. 68 | */ 69 | public let revision: String? 70 | 71 | /** 72 | The id of the document that this operation will retrieve. 73 | */ 74 | public let id: String 75 | 76 | public var endpoint: String { 77 | return "/\(self.databaseName)/\(id)" 78 | } 79 | 80 | public var parameters: [String: String] { 81 | get { 82 | var items: [String: String] = [:] 83 | 84 | if let revision = revision { 85 | items["rev"] = revision 86 | } 87 | 88 | if let includeRevisions = includeRevisions { 89 | items["revs"] = "\(includeRevisions)" 90 | } 91 | 92 | return items 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/PutAttachmentOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PutAttachmentOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 11/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | 21 | An Operation to add an attachment to a document. 22 | 23 | Example usage: 24 | ``` 25 | let attachment = "This is my awesome essay attachment for my document" 26 | let putAttachment = PutAttachmentOperation(name: "myAwsomeAttachment", 27 | contentType: "text/plain", 28 | data: attachment.data(using: .utf8, allowLossyConversion: false) 29 | documentID: docId 30 | revID: revID 31 | databaseName: "exampledb") {(response, info, error) in 32 | if let error = error { 33 | // handle the error 34 | } else { 35 | // process successful response 36 | } 37 | } 38 | client.add(operation: putAttachment) 39 | ``` 40 | 41 | */ 42 | public class PutAttachmentOperation: CouchDatabaseOperation, JSONOperation { 43 | 44 | /** 45 | Creates the operation. 46 | 47 | - parameter name: The name of the attachment to upload. 48 | - parameter contentType: The content type of the attachment e.g. text/plain. 49 | - parameter data: The attachment's data. 50 | - parameter documentID: the ID of the document to attach the attachment to. 51 | - parameter revision: the revision of the document to attach the attachment to. 52 | - parameter databaseName: The name of the database that the document is stored in. 53 | - parameter completionHandler: optional handler to run when the operation completes. 54 | */ 55 | public init(name: String, contentType: String, data: Data, documentID: String, revision: String, databaseName: String, completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 56 | 57 | self.name = name 58 | self.documentID = documentID 59 | self.revision = revision 60 | self.databaseName = databaseName 61 | self.contentType = contentType 62 | self.data = data 63 | self.completionHandler = completionHandler 64 | 65 | } 66 | 67 | public let databaseName: String 68 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 69 | 70 | /** 71 | The id of the document that the attachment should be attached to. 72 | 73 | */ 74 | public let documentID: String 75 | 76 | /** 77 | The revision of the document that the attachment should be attached to. 78 | */ 79 | public let revision: String 80 | 81 | /** 82 | The name of the attachment. 83 | */ 84 | public let name: String 85 | 86 | /** 87 | The attachment's data. 88 | */ 89 | public let data: Data? 90 | 91 | /** 92 | The Content type for the attachment such as text/plain, image/jpeg 93 | */ 94 | public let contentType: String 95 | 96 | public var method: String { 97 | return "PUT" 98 | } 99 | 100 | public var endpoint: String { 101 | return "/\(self.databaseName)/\(documentID)/\(name)" 102 | } 103 | 104 | public var parameters: [String: String] { 105 | return ["rev": revision] 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/PutDocumentOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PutDocumentOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by stefan kruger on 05/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | Creates or updates a document. 21 | 22 | Usage example: 23 | 24 | ``` 25 | let create = PutDocumentOperation(id: "example", body: ["hello":"world"], databaseName: "exampleDB") { (response, httpInfo, error) in 26 | if let error = error { 27 | // handle the error 28 | } else { 29 | // successfull request. 30 | } 31 | } 32 | ``` 33 | 34 | */ 35 | public class PutDocumentOperation: CouchDatabaseOperation, JSONOperation { 36 | 37 | /** 38 | Creates the operation. 39 | 40 | - parameter id: the id of the document to create or update, or nil if the server should generate an ID. 41 | - parameter revision: the revision of the document to update, or `nil` if it is a create. 42 | - parameter body: the body of the document 43 | - parameter databaseName: the name of the database where the document will be created / updated. 44 | - parameter completionHandler: optional handler to run when the operation completes. 45 | */ 46 | public init(id: String? = nil, revision: String? = nil, body: [String: Any], databaseName:String, completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 47 | self.id = id; 48 | self.revision = revision 49 | self.body = body 50 | self.databaseName = databaseName 51 | self.completionHandler = completionHandler 52 | 53 | } 54 | 55 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 56 | 57 | 58 | public let databaseName: String 59 | /** 60 | The document that this operation will modify. 61 | */ 62 | public let id: String? 63 | 64 | /** 65 | The revision of the document being updated or `nil` if this operation is creating a document. 66 | */ 67 | public let revision: String? 68 | 69 | /** Body of document. Must be serialisable with NSJSONSerialization */ 70 | public let body: [String: Any] 71 | 72 | public func validate() -> Bool { 73 | return JSONSerialization.isValidJSONObject(body) 74 | } 75 | 76 | public var method: String { 77 | get { 78 | if let _ = id { 79 | return "PUT" 80 | } else { 81 | return "POST" 82 | } 83 | } 84 | } 85 | 86 | public private(set) var data: Data? 87 | 88 | public var endpoint: String { 89 | get { 90 | if let id = id { 91 | return "/\(self.databaseName)/\(id)" 92 | } else { 93 | return "/\(self.databaseName)" 94 | } 95 | } 96 | 97 | } 98 | 99 | public var parameters: [String: String] { 100 | get { 101 | var items:[String:String] = [:] 102 | 103 | if let revision = revision { 104 | items["rev"] = revision 105 | } 106 | 107 | return items 108 | } 109 | } 110 | 111 | public func serialise() throws { 112 | data = try JSONSerialization.data(withJSONObject: body) 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Documents/ReadAttachmentOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadAttachmentOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 02/06/2016. 6 | // Copyright © 2016 IBM. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | An Operation to read an attachment from the database 21 | 22 | - Requires: All properties defined on this operation to be set. 23 | 24 | Example usage: 25 | 26 | ``` 27 | let read = ReadAttachmentOperation(name: attachmentName, documentID: docID, databaseName: dbName){ (data, info, error) in 28 | if let error = error { 29 | // handle the error 30 | } else { 31 | // process the successful response 32 | } 33 | } 34 | client.add(operation:read) 35 | ``` 36 | */ 37 | 38 | public class ReadAttachmentOperation: CouchDatabaseOperation, DataOperation { 39 | 40 | /** 41 | Creates the operation. 42 | 43 | - parameter name: the name of the attachment to read from the server 44 | - parameter documentID: the ID of the document that the attachment is attached to 45 | - parameter revision: the revision of the document that the attachment is attached to, or `nil` for latest. 46 | - parameter databaseName: the name of the database that the attachment is stored in. 47 | - parameter completionHAndler: optional handler to run when the operation completes. 48 | */ 49 | public init(name: String, documentID: String, revision: String? = nil, databaseName: String, completionHandler: ((Data?, HTTPInfo?, Error?) -> Void)? = nil) { 50 | 51 | self.name = name 52 | self.documentID = documentID 53 | self.revision = revision 54 | self.databaseName = databaseName 55 | self.completionHandler = completionHandler 56 | } 57 | 58 | /** 59 | Sets a completion handler to run when the operation completes. 60 | 61 | - parameter data: - The attachment data. 62 | - parameter httpInfo: - Information about the HTTP response. 63 | - parameter error: - ErrorProtocol instance with information about an error executing the operation. 64 | */ 65 | public let completionHandler: ((Data?, HTTPInfo?, Error?) -> Void)? 66 | 67 | public let databaseName: String 68 | 69 | /** 70 | The id of the document that the attachment should be attached to. 71 | */ 72 | public let documentID: String 73 | 74 | /** 75 | The revision of the document that the attachment should be attached to. 76 | */ 77 | public let revision: String? 78 | 79 | /** 80 | The name of the attachment. 81 | */ 82 | public let name: String 83 | 84 | public var method: String { 85 | return "GET" 86 | } 87 | 88 | public var endpoint: String { 89 | return "/\(self.databaseName)/\(documentID)/\(name)" 90 | } 91 | 92 | public var parameters: [String: String] { 93 | 94 | if let revision = self.revision { 95 | return ["rev": revision] 96 | } else { 97 | return [:] 98 | } 99 | } 100 | 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Operation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Operation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | An NSOperation subclass for executing `CouchOperations` 21 | */ 22 | public class Operation: Foundation.Operation, HTTPRequestOperation 23 | { 24 | 25 | /** 26 | A enum of errors which could be returned. 27 | */ 28 | public enum Error: Swift.Error { 29 | /** 30 | Validation of operation settings failed. 31 | */ 32 | case validationFailed 33 | 34 | /** 35 | The JSON format wasn't what we expected. 36 | */ 37 | case unexpectedJSONFormat(statusCode: Int, response: String?) 38 | 39 | /** 40 | An unexpected HTTP status code (e.g. 4xx or 5xx) was received. 41 | */ 42 | case http(statusCode: Int, response: String?) 43 | }; 44 | 45 | private let couchOperation: CouchOperation 46 | 47 | /** 48 | Initalises an Operation for executing. 49 | 50 | - parameter couchOperation: The `CouchOperation` to execute. 51 | */ 52 | public init(couchOperation: CouchOperation) { 53 | self.couchOperation = couchOperation 54 | } 55 | 56 | 57 | // NS operation property overrides 58 | private var mExecuting: Bool = false 59 | override public var isExecuting: Bool { 60 | get { 61 | return mExecuting 62 | } 63 | set { 64 | if mExecuting != newValue { 65 | willChangeValue(forKey: "isExecuting") 66 | mExecuting = newValue 67 | didChangeValue(forKey: "isExecuting") 68 | } 69 | } 70 | } 71 | 72 | private var mFinished: Bool = false 73 | override public var isFinished: Bool { 74 | get { 75 | return mFinished 76 | } 77 | set { 78 | if mFinished != newValue { 79 | willChangeValue(forKey: "isFinished") 80 | mFinished = newValue 81 | didChangeValue(forKey: "isFinished") 82 | } 83 | } 84 | } 85 | 86 | override public var isAsynchronous: Bool { 87 | get { 88 | return true 89 | } 90 | } 91 | 92 | var mSession: InterceptableSession? = nil 93 | internal var session: InterceptableSession { 94 | get { 95 | if let session = mSession { 96 | return session 97 | } else { 98 | mSession = InterceptableSession() 99 | return mSession! 100 | } 101 | } 102 | } 103 | 104 | internal var rootURL: URL = URL(string: "http://cloudant.invalid")! 105 | 106 | internal var httpPath: String { 107 | return couchOperation.endpoint 108 | } 109 | internal var httpMethod: String { 110 | return couchOperation.method 111 | } 112 | 113 | internal var queryItems: [URLQueryItem] { 114 | get { 115 | var items:[URLQueryItem] = [] 116 | 117 | for (key, value) in couchOperation.parameters { 118 | items.append(URLQueryItem(name: key, value: value)) 119 | } 120 | return items 121 | } 122 | } 123 | 124 | internal var httpContentType: String { 125 | return couchOperation.contentType 126 | } 127 | 128 | // return nil if there is no body 129 | internal var httpRequestBody: Data? { 130 | return couchOperation.data 131 | } 132 | 133 | internal var executor: OperationRequestExecutor? = nil 134 | 135 | internal func processResponse(data: Data?, httpInfo: HTTPInfo?, error: Swift.Error?) { 136 | couchOperation.processResponse(data: data, httpInfo: httpInfo, error: error) 137 | } 138 | 139 | final override public func start() { 140 | do { 141 | // Always check for cancellation before launching the task 142 | if isCancelled { 143 | isFinished = true 144 | return 145 | } 146 | 147 | if !couchOperation.validate() { 148 | couchOperation.callCompletionHandler(error: Error.validationFailed) 149 | isFinished = true 150 | return 151 | } 152 | 153 | try couchOperation.serialise() 154 | 155 | // start the operation 156 | isExecuting = true 157 | executor = OperationRequestExecutor(operation: self) 158 | executor?.executeRequest() 159 | } catch { 160 | couchOperation.callCompletionHandler(error: error) 161 | isFinished = true 162 | } 163 | } 164 | 165 | final public func completeOperation() { 166 | self.executor = nil // break the cycle. 167 | self.isExecuting = false 168 | self.isFinished = true 169 | } 170 | 171 | final override public func cancel() { 172 | super.cancel() 173 | self.executor?.cancel() 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/OperationProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperationProtocols.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 06/06/2016. 6 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | A protocol that denotes an operation which can be run agasint a CouchDB instance. 21 | */ 22 | public protocol CouchOperation { 23 | 24 | /** 25 | The CouchDB API endpoint to call, for example `/exampleDB/document1/` or `/_all_dbs` 26 | */ 27 | var endpoint: String { get } 28 | 29 | /** 30 | The HTTP method to use when making the API call. 31 | */ 32 | var method: String { get } 33 | 34 | /** 35 | Dictionary of query parameters, `Key` is the parameter name and `Value` is the 36 | value to use for that parameter. E.g. ["rev": "1-revisionhash"]. 37 | */ 38 | var parameters: [String: String] { get } 39 | 40 | /** 41 | The data to send to the CouchDB endpoint. 42 | */ 43 | var data: Data? { get } 44 | 45 | 46 | /** 47 | The content type of the data to send to the CouchDB endpoint. This is guaranteed to be called 48 | if and only if `data` is not `nil` 49 | */ 50 | var contentType: String { get } 51 | 52 | /** 53 | Calls the completionHandler for this operation. 54 | 55 | - parameter response: The response received from the server, this should be one of the following types, 56 | an `Array`/`NSArray`, `Dictionary`/`NSDictionary` or Data. 57 | - parameter httpInfo: Information about the HTTP response. 58 | - parameter error: The error that occurred, or nil if the request was made successfully. 59 | */ 60 | func callCompletionHandler(response: Any?, httpInfo: HTTPInfo?, error: Swift.Error?) 61 | 62 | /** 63 | This method is called from the 64 | `processResponse(data: Data?, httpInfo: HttpInfo?, error: ErrorProtocol?)` method, 65 | it will contain the deserialized json response in the event the request returned with a 66 | 2xx status code. 67 | 68 | Provides a hook to process the json response 69 | 70 | - parameter json: Desearalised json, this will either be an Array or a Dictionary. 71 | 72 | - Note: This should be overridden to trigger other handlers such as a handler for each row of 73 | a returned view. 74 | */ 75 | func processResponse(json: Any) 76 | 77 | 78 | /** 79 | Processes the response from CouchDB. This is required to call the following 80 | methods in the lifecycle of the function when the given circumstances arise: 81 | 82 | * callCompletionHandler(error:) when the processing of the response has completed with an error. 83 | * callCompletionHandler(response:httpInfo:error:) when the processing of the response has completed. 84 | * processResponse(json:) when a 2xx response code was received and the response is a JSON response. 85 | This allows an operation to provide additional processing of the JSON. 86 | */ 87 | func processResponse(data: Data?, httpInfo: HTTPInfo?, error: Swift.Error?) 88 | 89 | /** 90 | Validates the operation has been set up correctly, subclasses should override but call and 91 | use the result of the super class implementation. 92 | */ 93 | func validate() -> Bool 94 | 95 | /** 96 | This should be used to serialise any data into the format expected by the CouchDB/Cloudant 97 | endpoint. 98 | 99 | - throws: An error in the event of a failure to serialise. 100 | - note: This is guaranteed to be called after `validate() -> Bool` and before the 101 | `HTTPRequestOperation` properties are computed. 102 | */ 103 | func serialise() throws 104 | } 105 | 106 | public extension CouchOperation { 107 | /** 108 | Calls the completion handler for the operation with the specified error. 109 | Subclasses need to override this to call the completion handler they have defined. 110 | */ 111 | func callCompletionHandler(error: Swift.Error) { 112 | self.callCompletionHandler(response: nil, httpInfo: nil, error: error) 113 | } 114 | 115 | // default implementation of serialise, does nothing. 116 | func serialise() throws { return } 117 | 118 | // default implementation, does nothing. 119 | func processResponse(json: Any) { return } 120 | 121 | var contentType: String { return "application/json" } 122 | 123 | var data: Data? { return nil } 124 | 125 | var parameters: [String: String] { return [:] } 126 | 127 | var method: String { return "GET" } 128 | 129 | func validate() -> Bool { return true } 130 | 131 | } 132 | 133 | /** 134 | Marks an operation as a JSON Operation. It will provide response 135 | processing for operations. 136 | */ 137 | public protocol JSONOperation: CouchOperation { 138 | 139 | /** 140 | The Json type that is expected. This should only be a type that can be returned from `NSJSONSeralization` 141 | */ 142 | associatedtype Json 143 | 144 | /** 145 | Sets a completion handler to run when the operation completes. 146 | 147 | - parameter response: - The full deseralised JSON response. 148 | - parameter httpInfo: - Information about the HTTP response. 149 | - parameter error: - ErrorProtocol instance with information about an error executing the operation. 150 | */ 151 | var completionHandler: ((Json?, HTTPInfo?, Swift.Error?) -> Void)? { get } 152 | } 153 | 154 | public extension JSONOperation { 155 | 156 | func callCompletionHandler(response: Any?, httpInfo: HTTPInfo?, error: Swift.Error?) { 157 | self.completionHandler?(response as? Json, httpInfo, error) 158 | } 159 | 160 | func processResponse(data: Data?, httpInfo: HTTPInfo?, error: Swift.Error?) { 161 | guard error == nil, let httpInfo = httpInfo 162 | else { 163 | self.callCompletionHandler(error: error!) 164 | return 165 | } 166 | 167 | do { 168 | if let data = data { 169 | let json = try JSONSerialization.jsonObject(with: data) 170 | if httpInfo.statusCode / 100 == 2 { 171 | self.processResponse(json: json) 172 | self.callCompletionHandler(response: json, httpInfo: httpInfo, error: error) 173 | } else { 174 | let error = Operation.Error.http(statusCode: httpInfo.statusCode, 175 | response: String(data: data, encoding: .utf8)) 176 | self.callCompletionHandler(response: json, httpInfo: httpInfo, error: error as Error) 177 | } 178 | } else { 179 | let error = Operation.Error.http(statusCode: httpInfo.statusCode, response: nil) 180 | self.callCompletionHandler(response: nil, httpInfo: httpInfo, error: error as Error) 181 | } 182 | } catch { 183 | let response: String? 184 | if let data = data { 185 | response = String(data: data, encoding: .utf8) 186 | } else { 187 | response = nil 188 | } 189 | let error = Operation.Error.unexpectedJSONFormat(statusCode: httpInfo.statusCode, response: response) 190 | self.callCompletionHandler(response: nil, httpInfo: httpInfo, error: error as Error) 191 | } 192 | } 193 | } 194 | 195 | /** 196 | Marks an operation as a Data Only operation. It will provide the data directly from the database. 197 | */ 198 | public protocol DataOperation: CouchOperation { 199 | 200 | /** 201 | Sets a completion handler to run when the operation completes. 202 | 203 | - parameter response: - The full data received from the server. 204 | - parameter httpInfo: - Information about the HTTP response. 205 | - parameter error: - ErrorProtocol instance with information about an error executing the operation. 206 | */ 207 | var completionHandler: ((Data?, HTTPInfo?, Swift.Error?) -> Void)? { get } 208 | } 209 | 210 | public extension DataOperation { 211 | func callCompletionHandler(response: Any?, httpInfo: HTTPInfo?, error: Swift.Error?) { 212 | self.completionHandler?(response as? Data, httpInfo, error) 213 | } 214 | 215 | func processResponse(data: Data?, httpInfo: HTTPInfo?, error: Swift.Error?) { 216 | guard error == nil, let httpInfo = httpInfo 217 | else { 218 | self.callCompletionHandler(error: error!) 219 | return 220 | } 221 | if httpInfo.statusCode / 100 == 2 { 222 | self.completionHandler?(data, httpInfo, error) 223 | } else { 224 | self.completionHandler?(data, httpInfo, Operation.Error.http(statusCode: httpInfo.statusCode, response: String(data: data!, encoding: .utf8))) 225 | } 226 | } 227 | } 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Query/CreateQueryIndexOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateQueryIndexOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 18/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | 18 | import Foundation 19 | 20 | /** 21 | An operation to create a JSON Query (Mango) Index. 22 | 23 | Usage example: 24 | ``` 25 | let index = CreateJSONQueryIndexOperation(databaseName: "exampledb", 26 | designDocumentID: "examples", 27 | name:"exampleIndex", 28 | fields: [Sort(field:"food", sort: .desc)]) { (response, httpInfo, error) in 29 | if let error = error { 30 | // handle the error 31 | } else { 32 | // Check the status code for success. 33 | } 34 | 35 | } 36 | 37 | client.add(operation: index) 38 | ``` 39 | */ 40 | public class CreateJSONQueryIndexOperation: CouchDatabaseOperation, MangoOperation, JSONOperation { 41 | 42 | /** 43 | Creates the operation 44 | - parameter databaseName : The name of the database where the index should be created. 45 | - parameter designDocumentID : The ID of the design document where the index should be saved, 46 | if set to `nil` the server will create a new design document with a generated ID. 47 | - parameter fields : the fields to be indexed. 48 | - parameter completionHandler: block to run when the operation completes. 49 | */ 50 | public init(databaseName: String, 51 | designDocumentID: String? = nil, 52 | name: String? = nil, 53 | fields: [Sort], 54 | completionHandler: (( [String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 55 | self.databaseName = databaseName 56 | self.fields = fields 57 | self.designDocumentID = designDocumentID 58 | self.name = name 59 | self.completionHandler = completionHandler 60 | } 61 | 62 | public let databaseName: String 63 | 64 | public let completionHandler: (( [String : Any]?, HTTPInfo?, Error?) -> Void)? 65 | 66 | /** 67 | The name of the index. 68 | */ 69 | public let name: String? 70 | 71 | /** 72 | The fields to which the index will be applied. 73 | */ 74 | public let fields: [Sort] 75 | 76 | /** 77 | The ID of the design document that this index should be saved to. If `nil` the server will 78 | create a new design document with a generated ID. 79 | */ 80 | public let designDocumentID: String? 81 | 82 | private var jsonData: Data? 83 | 84 | 85 | public var method: String { 86 | return "POST" 87 | } 88 | 89 | public var data: Data? { 90 | return self.jsonData 91 | } 92 | 93 | public var endpoint: String { 94 | return "/\(self.databaseName)/_index" 95 | } 96 | 97 | public func serialise() throws { 98 | 99 | var jsonDict: [String: Any] = ["type": "json"] 100 | 101 | var index: [String: Any] = [:] 102 | index["fields"] = transform(sortArray: fields) 103 | jsonDict["index"] = index 104 | 105 | 106 | 107 | if let name = name { 108 | jsonDict["name"] = name 109 | } 110 | 111 | if let designDocumentID = designDocumentID { 112 | jsonDict["ddoc"] = designDocumentID 113 | } 114 | 115 | jsonData = try JSONSerialization.data(withJSONObject: jsonDict) 116 | 117 | } 118 | 119 | } 120 | 121 | /** 122 | A struct to represent a field in a Text index. 123 | */ 124 | public struct TextIndexField { 125 | 126 | /** 127 | The data types for a field in a Text index. 128 | */ 129 | public enum `Type` : String { 130 | /** 131 | A Boolean data type. 132 | */ 133 | case boolean = "boolean" 134 | /** 135 | A String data type. 136 | */ 137 | case string = "string" 138 | /** 139 | A Number data type. 140 | */ 141 | case number = "number" 142 | } 143 | 144 | /** 145 | The name of the field 146 | */ 147 | public let name: String 148 | /** 149 | The type of field. 150 | */ 151 | public let type: Type 152 | 153 | public init(name: String, type: Type) { 154 | self.name = name 155 | self.type = type 156 | } 157 | } 158 | 159 | 160 | 161 | /** 162 | An Operation to create a Text Query (Mango) Index. 163 | 164 | Usage Example: 165 | ``` 166 | let index = CreateTextQueryIndexOperation(databaseName: "exampledb", 167 | name: "example", 168 | fields: [TextIndexField(name:"food", type: .string), 169 | defaultFieldAnalyzer: "english", 170 | defaultFieldEnabled: true, 171 | selector:["type": "food"], 172 | designDocumentID: "examples"){ (response, httpInfo, error) in 173 | if let error = error { 174 | // handle the error 175 | } else { 176 | // Check the status code for success. 177 | } 178 | } 179 | client.add(operation: index) 180 | */ 181 | public class CreateTextQueryIndexOperation: CouchDatabaseOperation, MangoOperation, JSONOperation { 182 | 183 | /** 184 | Creates the operation. 185 | 186 | - parameter databaseName : The name of the database where the index should be created. 187 | - parameter name : The name of the index, if `nil` the server will generate a name for the index. 188 | - parameter fields : the fields which should be indexed, if `nil` all the fields in a document 189 | will be indexed. 190 | - parameter defaultFieldAnalyzer: The analyzer to use for the default field. The default field is 191 | used when using the `$text` operator in queries. 192 | - parameter defaultFieldAnalyzerEnabled: Determines if the default field should be enabled. 193 | - parameter selector: A selector which documents should match before being indexed. 194 | - parameter designDocumentID : the ID of the design document where the index should be saved, 195 | if `nil` the server will create a new design document with a generated ID. 196 | - parameter completionHandler: optional handler to run when the operation completes. 197 | */ 198 | public init(databaseName: String, 199 | name: String? = nil, 200 | fields: [TextIndexField]? = nil, 201 | defaultFieldAnalyzer: String? = nil, 202 | defaultFieldEnabled: Bool? = nil, 203 | selector: [String:Any]? = nil, 204 | designDocumentID: String? = nil, 205 | completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 206 | self.databaseName = databaseName 207 | self.completionHandler = completionHandler 208 | self.name = name 209 | self.fields = fields 210 | self.defaultFieldEnabled = defaultFieldEnabled 211 | self.defaultFieldAnalyzer = defaultFieldAnalyzer 212 | self.selector = selector 213 | self.designDocumentID = designDocumentID 214 | } 215 | 216 | public let databaseName: String 217 | public let completionHandler: ((_ response: [String : Any]?, _ httpInfo: HTTPInfo?, _ error: Error?) -> Void)? 218 | 219 | /** 220 | The name of the index 221 | */ 222 | public let name: String? 223 | 224 | /** 225 | The fields to be included in the index. 226 | */ 227 | public let fields: [TextIndexField]? 228 | 229 | /** 230 | The name of the analyzer to use for $text operator with this index. 231 | */ 232 | public let defaultFieldAnalyzer: String? 233 | 234 | /** 235 | If the default field should be enabled for this index. 236 | 237 | - Note: If this is not enabled the `$text` operator will **always** return 0 results. 238 | 239 | */ 240 | public let defaultFieldEnabled: Bool? 241 | 242 | /** 243 | The selector that limits the documents in the index. 244 | */ 245 | public let selector: [String: Any]? 246 | 247 | /** 248 | The name of the design doc this index should be included with 249 | */ 250 | public let designDocumentID: String? 251 | 252 | private var jsonData : Data? 253 | public var data: Data? { 254 | return jsonData 255 | } 256 | 257 | public var method: String { 258 | return "POST" 259 | } 260 | 261 | public var endpoint: String { 262 | return "/\(self.databaseName)/_index" 263 | } 264 | 265 | public func validate() -> Bool { 266 | 267 | if let selector = selector { 268 | return JSONSerialization.isValidJSONObject(selector) 269 | } 270 | 271 | return true 272 | } 273 | 274 | public func serialise() throws { 275 | 276 | do { 277 | var jsonDict: [String: Any] = [:] 278 | var index: [String: Any] = [:] 279 | var defaultField: [String: Any] = [:] 280 | 281 | jsonDict["type"] = "text" 282 | 283 | if let name = name { 284 | jsonDict["name"] = name 285 | } 286 | 287 | if let fields = fields { 288 | index["fields"] = transform(fields: fields) 289 | } 290 | 291 | if let defaultFieldEnabled = defaultFieldEnabled { 292 | defaultField["enabled"] = defaultFieldEnabled 293 | } 294 | 295 | if let defaultFieldAnalyzer = defaultFieldAnalyzer { 296 | defaultField["analyzer"] = defaultFieldAnalyzer 297 | } 298 | 299 | if let designDocumentID = designDocumentID { 300 | jsonDict["ddoc"] = designDocumentID 301 | } 302 | 303 | if let selector = selector { 304 | index["selector"] = selector 305 | } 306 | 307 | if defaultField.count > 0 { 308 | index["default_field"] = defaultField 309 | } 310 | 311 | if index.count > 0 { 312 | jsonDict["index"] = index 313 | } 314 | 315 | self.jsonData = try JSONSerialization.data(withJSONObject:jsonDict) 316 | 317 | } 318 | 319 | } 320 | 321 | } 322 | 323 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Query/DeleteQueryIndexOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteQueryIndexOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 17/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | An operation to delete a Query index. 21 | 22 | Example usage: 23 | ``` 24 | let deleteIndex = DeleteQueryIndexOperation(name: "exampleIndexName", 25 | type: .JSON, 26 | designDocumentID: "exampleDesignDoc", 27 | databaseName: "exampledb"){(response, httpInfo, error) in 28 | if error != nil { 29 | // Example: handle an error by printing a message 30 | print("Error") 31 | } 32 | } 33 | 34 | client.add(operation: deleteIndex) 35 | ``` 36 | */ 37 | public class DeleteQueryIndexOperation: CouchDatabaseOperation, JSONOperation { 38 | 39 | /** 40 | An enum representing the possible index types for Query. 41 | */ 42 | public enum `Type` : String { 43 | /** 44 | Represents the json index type. 45 | */ 46 | case json = "json" 47 | /** 48 | Represents the text Index type. 49 | */ 50 | case text = "text" 51 | } 52 | 53 | /** 54 | 55 | Creates the operation. 56 | 57 | - parameter name: the name of the index to delete 58 | - parameter type: the type of the index that is being deleted. 59 | - parameter designDocumentID: the ID of the design document that contains the index. 60 | - parameter databaseName: the name of the database that contains the design document. 61 | - parameter completionHandler: optional handler to run when the operation completes. 62 | */ 63 | public init(name: String, type: Type, designDocumentID: String, databaseName: String, completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 64 | self.name = name 65 | self.type = type 66 | self.designDocumentID = designDocumentID 67 | self.databaseName = databaseName 68 | self.completionHandler = completionHandler 69 | } 70 | 71 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 72 | public let databaseName: String 73 | 74 | /** 75 | The name of the design document which contains the index. 76 | */ 77 | public let designDocumentID: String 78 | 79 | /** 80 | The name of the index to delete. 81 | */ 82 | public let name: String 83 | 84 | /** 85 | The type of index e.g. JSON 86 | */ 87 | public let type: Type 88 | 89 | public var endpoint: String { 90 | return "/\(self.databaseName)/_index/\(self.designDocumentID)/\(self.type.rawValue)/\(self.name)" 91 | } 92 | 93 | public var method: String { 94 | return "DELETE" 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Query/FindDocumentsOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindDocumentsOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 20/04/2016. 6 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | 20 | 21 | /** 22 | Specfies how a field should be sorted 23 | */ 24 | public struct Sort { 25 | 26 | /** 27 | The direction of Sorting 28 | */ 29 | public enum Direction: String { 30 | /** 31 | Sort ascending 32 | */ 33 | case asc = "asc" 34 | /** 35 | Sort descending 36 | */ 37 | case desc = "desc" 38 | } 39 | 40 | /** 41 | The field on which to sort 42 | */ 43 | public let field: String 44 | 45 | /** 46 | The direction in which to sort. 47 | */ 48 | public let sort: Direction? 49 | 50 | public init(field: String, sort: Direction?) { 51 | self.field = field 52 | self.sort = sort 53 | } 54 | } 55 | 56 | /** 57 | Protocol for operations which deal with the Mango API set for CouchDB / Cloudant. 58 | */ 59 | internal protocol MangoOperation { 60 | 61 | } 62 | 63 | internal extension MangoOperation { 64 | /** 65 | Transform an Array of Sort into a Array in the form of Sort Syntax. 66 | */ 67 | func transform(sortArray: [Sort]) -> [Any] { 68 | 69 | var transformed: [Any] = [] 70 | for s in sortArray { 71 | if let sort = s.sort { 72 | let dict = [s.field: sort.rawValue] 73 | transformed.append(dict) 74 | } else { 75 | transformed.append(s.field) 76 | } 77 | } 78 | 79 | return transformed 80 | } 81 | 82 | /** 83 | Transform an array of TextIndexField into an Array in the form of Lucene field definitions. 84 | */ 85 | func transform(fields: [TextIndexField]) -> [[String:String]] { 86 | var array: [[String:String]] = [] 87 | 88 | for field in fields { 89 | array.append([field.name: field.type.rawValue]) 90 | } 91 | 92 | return array 93 | } 94 | } 95 | 96 | /** 97 | Usage example: 98 | 99 | ``` 100 | let find = FindDocumentsOperation(selector:["foo":"bar"], databaseName: "exampledb", fields: ["foo"], sort: [Sort(field: "foo", sort: .Desc)], documentFoundHandler: { (document) in 101 | // Do something with the document. 102 | }) {(response, httpInfo, error) in 103 | if let error = error { 104 | // handle the error. 105 | } else { 106 | // do something on success. 107 | } 108 | } 109 | client.add(operation:find) 110 | 111 | ``` 112 | */ 113 | public class FindDocumentsOperation: CouchDatabaseOperation, MangoOperation, JSONOperation { 114 | 115 | /** 116 | 117 | Creates the operation. 118 | 119 | - parameter selector: the selector to use to select documents in the database. 120 | - parameter databaseName: the name of the database the find operation should be performed on. 121 | - parameter fields: the fields of a matching document which should be returned in the repsonse. 122 | - parameter limit: the maximium number of documents to be returned. 123 | - parameter skip: the number of matching documents to skip before returning matching documents. 124 | - parameter sort: how to sort the match documents 125 | - parameter bookmark: A bookmark from a previous index query, this is only valid for text indexes. 126 | - parameter useIndex: Which index to use when matching documents 127 | - parameter r: The read quorum for this request. 128 | - parameter documentFoundHandler: a handler to call for each document in the response. 129 | - parameter completionHandler: optional handler to call when the operations completes. 130 | 131 | 132 | - warning: `r` is an advanced option and is rarely, if ever, needed. It **will** be detrimental to 133 | performance. 134 | 135 | - remark: The `bookmark` option is only valid for text indexes, a `bookmark` is returned from 136 | the server and can be accessed in the completionHandler with the following line: 137 | 138 | ```` 139 | let bookmark = response["bookmark"] 140 | ```` 141 | 142 | - seealso: [Query sort syntax](https://docs.cloudant.com/cloudant_query.html#sort-syntax) 143 | - seealso: [Selector syntax](https://docs.cloudant.com/cloudant_query.html#selector-syntax) 144 | 145 | */ 146 | public init(selector: [String: Any], 147 | databaseName: String, 148 | fields: [String]? = nil, 149 | limit: UInt? = nil, 150 | skip: UInt? = nil, 151 | sort: [Sort]? = nil, 152 | bookmark: String? = nil, 153 | useIndex:String? = nil, 154 | r: UInt? = nil, 155 | documentFoundHandler: (([String: Any]) -> Void)? = nil, 156 | completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 157 | self.selector = selector 158 | self.databaseName = databaseName 159 | self.fields = fields 160 | self.limit = limit 161 | self.skip = skip 162 | self.sort = sort 163 | self.bookmark = bookmark 164 | self.useIndex = useIndex; 165 | self.r = r 166 | self.documentFoundHandler = documentFoundHandler 167 | self.completionHandler = completionHandler 168 | } 169 | 170 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 171 | public let databaseName: String 172 | 173 | /** 174 | The selector for the query, as a dictionary representation. See 175 | [the Cloudant documentation](https://docs.cloudant.com/cloudant_query.html#selector-syntax) 176 | for syntax information. 177 | */ 178 | public let selector: [String: Any]?; 179 | 180 | /** 181 | The fields to include in the results. 182 | */ 183 | public let fields: [String]? 184 | 185 | /** 186 | The number maximium number of documents to return. 187 | */ 188 | public let limit: UInt? 189 | 190 | /** 191 | Skip the first _n_ results, where _n_ is specified by skip. 192 | */ 193 | public let skip: UInt? 194 | 195 | /** 196 | An array that indicates how to sort the results. 197 | */ 198 | public let sort: [Sort]? 199 | 200 | /** 201 | A string that enables you to specify which page of results you require. 202 | */ 203 | public let bookmark: String? 204 | 205 | /** 206 | A specific index to run the query against. 207 | */ 208 | public let useIndex: String? 209 | 210 | /** 211 | The read quorum for this request. 212 | */ 213 | public let r: UInt? 214 | 215 | /** 216 | Handler to run for each document retrived from the database matching the query. 217 | 218 | - parameter document: a document matching the query. 219 | */ 220 | public let documentFoundHandler: (([String: Any]) -> Void)? 221 | 222 | private var json: [String: Any]? 223 | 224 | public var method: String { 225 | return "POST" 226 | } 227 | 228 | public var endpoint: String { 229 | return "/\(self.databaseName)/_find" 230 | } 231 | 232 | private var jsonData: Data? 233 | public var data: Data? { 234 | return self.jsonData 235 | } 236 | 237 | public func validate() -> Bool { 238 | let jsonObj = createJsonDict() 239 | if JSONSerialization.isValidJSONObject(jsonObj) { 240 | self.json = jsonObj 241 | return true 242 | } else { 243 | return false; 244 | } 245 | 246 | } 247 | 248 | private func createJsonDict() -> [String: Any] { 249 | // build the body dict, we will store this to save compute cycles. 250 | var jsonObj: [String: Any] = [:] 251 | if let selector = self.selector { 252 | jsonObj["selector"] = selector 253 | } 254 | 255 | if let limit = self.limit { 256 | jsonObj["limit"] = limit 257 | } 258 | 259 | if let skip = self.skip { 260 | jsonObj["skip"] = skip 261 | } 262 | 263 | if let r = self.r { 264 | jsonObj["r"] = r 265 | } 266 | 267 | if let sort = self.sort { 268 | jsonObj["sort"] = transform(sortArray: sort) 269 | } 270 | 271 | if let fields = self.fields { 272 | jsonObj["fields"] = fields 273 | } 274 | 275 | if let bookmark = self.bookmark { 276 | jsonObj["bookmark"] = bookmark 277 | } 278 | 279 | if let useIndex = self.useIndex { 280 | jsonObj["use_index"] = useIndex 281 | } 282 | 283 | return jsonObj 284 | } 285 | 286 | internal class func transform(sortArray: [Sort]) -> [Any] { 287 | 288 | var transfomed: [Any] = [] 289 | for s in sortArray { 290 | if let sort = s.sort { 291 | let dict = [s.field: sort.rawValue] 292 | transfomed.append(dict) 293 | } else { 294 | transfomed.append(s.field) 295 | } 296 | } 297 | 298 | return transfomed 299 | } 300 | 301 | public func serialise() throws { 302 | 303 | if self.json == nil { 304 | self.json = createJsonDict() 305 | } 306 | 307 | if let json = self.json { 308 | self.jsonData = try JSONSerialization.data(withJSONObject: json) 309 | } 310 | } 311 | 312 | public func processResponse(json: Any) { 313 | if let json = json as? [String: Any], 314 | let docs = json["docs"] as? [[String: Any]] { // Array of [String:Any] 315 | for doc: [String: Any] in docs { 316 | self.documentFoundHandler?(doc) 317 | } 318 | } 319 | } 320 | 321 | public func callCompletionHandler(response: Any?, httpInfo: HTTPInfo?, error: Error?) { 322 | self.completionHandler?(response as? [String: Any], httpInfo, error) 323 | } 324 | 325 | 326 | } 327 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Server/CouchDatabaseOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CouchDatabaseOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Stefan Kruger on 04/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | A protocol for an Operation which operates on a specfied database, rather than 21 | on CouchDB as a whole. 22 | */ 23 | public protocol CouchDatabaseOperation: CouchOperation { 24 | 25 | /** 26 | The name of the database that the operation will be operating on. 27 | */ 28 | var databaseName: String { get } 29 | } 30 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Server/CreateDatabaseOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateDatabaseOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | An operation to create a database in a CouchDB instance. 21 | 22 | Example usage: 23 | ``` 24 | let createDB = CreateDatabaseOperation(name: "exampleDB") { (response, info, error) in 25 | if let error = error { 26 | // handle the error case 27 | } else { 28 | //handle sucessful creation. 29 | } 30 | 31 | } 32 | client.add(operation: createDB) 33 | */ 34 | public class CreateDatabaseOperation: CouchOperation, JSONOperation { 35 | 36 | /** 37 | Creates the operation. 38 | 39 | - parameter name: The name of the database this operation will create. 40 | - parameter completionHandler: optional handler to call when the operation completes. 41 | */ 42 | public init(name: String, completionHandler: ((_ response: [String : Any]?, _ httpInfo: HTTPInfo?, _ error: Error?) -> Void)? = nil) { 43 | self.name = name 44 | self.completionHandler = completionHandler 45 | } 46 | 47 | public let completionHandler: ((_ response: [String : Any]?, _ httpInfo: HTTPInfo?, _ error: Error?) -> Void)? 48 | 49 | /** 50 | The name of the database to create. 51 | */ 52 | public let name: String 53 | 54 | public var method: String { 55 | get { 56 | return "PUT" 57 | } 58 | } 59 | 60 | public var endpoint: String { 61 | get { 62 | return "/\(self.name)" 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Server/DeleteDatabaseOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteDatabaseOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | Deletes a database from a CouchDB instance 21 | 22 | Usage example: 23 | 24 | ``` 25 | let deleteDB = DeleteDatabaseOperation(name: "exampleDB") { (response, httpInfo, error) in 26 | 27 | if let error = error { 28 | // handle the error 29 | } else { 30 | // check the response code to determine if the operaton was successful. 31 | } 32 | } 33 | */ 34 | public class DeleteDatabaseOperation: CouchOperation, JSONOperation { 35 | 36 | /** 37 | Creates the operation 38 | 39 | - parameter name: the name of the database to delete. 40 | - completionHandler: optional handler to reun when the operation completes. 41 | */ 42 | public init(name: String, completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) { 43 | self.name = name; 44 | self.completionHandler = completionHandler 45 | } 46 | 47 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 48 | 49 | /** 50 | The name of the database to delete. 51 | */ 52 | public let name: String 53 | 54 | public var method: String { 55 | get { 56 | return "DELETE" 57 | } 58 | } 59 | 60 | public var endpoint: String { 61 | get { 62 | return "/\(self.name)" 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Server/GetAllDatabasesOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetAllDatabasesOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 23/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | 19 | /** 20 | 21 | An operation to get **all** the databases on a server. 22 | 23 | Example usage: 24 | ``` 25 | let allDbs = GetAllDatabasesOperation( 26 | databaseHandler: { (databaseName) in 27 | // do something for the database name 28 | }) { (response, httpInfo, error) in 29 | if let error = error { 30 | // handle the error case 31 | } 32 | // process the response information 33 | } 34 | 35 | */ 36 | public class GetAllDatabasesOperation : CouchOperation, JSONOperation { 37 | public typealias Json = [Any] 38 | 39 | 40 | /** 41 | 42 | Creates the operation 43 | 44 | - parameter databaseHandler: optional handler to call for each database returned from the server. 45 | - parameter completionHander: optional handler to call when the operation completes. 46 | */ 47 | public init(databaseHandler: ((_ databaseName: String) -> Void)? = nil, 48 | completionHandler:(([Any]?, HTTPInfo?,Error?) -> Void)? = nil) { 49 | 50 | self.databaseHandler = databaseHandler 51 | self.completionHandler = completionHandler 52 | } 53 | 54 | /** 55 | Handler to run for each database returned. 56 | 57 | - parameter databaseName: the name of a database on the server. 58 | */ 59 | public let databaseHandler: ((_ databaseName: String) -> Void)? 60 | 61 | public let completionHandler: (([Any]?, HTTPInfo?, Error?) -> Void)? 62 | 63 | public var endpoint: String { 64 | return "/_all_dbs" 65 | } 66 | 67 | public func validate() -> Bool { 68 | return true 69 | } 70 | 71 | public func processResponse(json: Any) { 72 | if let json = json as? [String] { 73 | for dbName in json { 74 | self.databaseHandler?(dbName) 75 | } 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Views/GetAllDocsOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetAllDocsOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Copyright (c) 2016 IBM Corp. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 8 | // except in compliance with the License. You may obtain a copy of the License at 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // Unless required by applicable law or agreed to in writing, software distributed under the 11 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 12 | // either express or implied. See the License for the specific language governing permissions 13 | // and limitations under the License. 14 | // 15 | 16 | import Foundation 17 | 18 | /** 19 | An Operation to get all the documents in a database. 20 | 21 | Example usage: 22 | ``` 23 | let allDocs = GetAllDocsOperation(databaseName: "exampleDB", 24 | rowHandler: { doc in 25 | print("Got document: \(doc)") 26 | }) { response, info, error in 27 | if let error = error { 28 | // handle error 29 | } else { 30 | // handle successful response 31 | } 32 | } 33 | client.add(operation: allDocs) 34 | ``` 35 | */ 36 | public class GetAllDocsOperation : CouchOperation, ViewOperation, JSONOperation { 37 | public typealias Json = [String : Any] 38 | 39 | 40 | public let completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? 41 | 42 | public let rowHandler: (([String: Any]) -> Void)? 43 | 44 | public let databaseName: String 45 | 46 | public let descending: Bool? 47 | 48 | public let endKey: String? 49 | 50 | public let includeDocs: Bool? 51 | 52 | public let conflicts: Bool? 53 | 54 | public let inclusiveEnd: Bool? 55 | 56 | public let key: String? 57 | 58 | public let keys: [String]? 59 | 60 | public let limit: UInt? 61 | 62 | public let skip: UInt? 63 | 64 | public let startKeyDocumentID: String? 65 | 66 | public let endKeyDocumentID: String? 67 | 68 | public let stale: Stale? 69 | 70 | public let startKey: String? 71 | 72 | public let includeLastUpdateSequenceNumber: Bool? 73 | 74 | /** 75 | Creates the operation 76 | 77 | - parameter databaseName: The name of the database from which to get all the documents. 78 | - parameter descending: Should the documents be sorted in descending order. 79 | - parameter endKey: When this document ID is hit stop returning results. 80 | - parameter includeDocs: Include document content in the response. 81 | - parameter conflicts: Include infomration about conflicted revisions. 82 | - parameter key: Return the document with the specified ID. 83 | - parameter keys: Return the documents with the soecified IDs. 84 | - parameter limit: The number of documents the repsonse should be limited to. 85 | - parameter skip: the number of documents that should be skipped before returning results. 86 | - parameter startKeyDocumentID: the document ID from which to start the response. 87 | - parameter endKeyDocumentID: the document ID at which to to stop retuning results. 88 | - parameter includeLastUpdateSequenceNumber: Include the sequence number at which the view was last updated. 89 | - parameter inclusiveEnd: Detmerines if the `endKey` and `endKeyDocId` should be included when returning documents. 90 | - parameter stale: Whether stale views are ok, or should be updated after the response is returned. 91 | - parameter startKey: the document ID from where to start returning documents. 92 | - parameter rowHandler: a handler to call for each row returned from the view. 93 | - parameter completionHandler: optional handler to call when the operation completes. 94 | 95 | - warning: `stale` is an advanced option, it should not be used unless you fully understand the outcome of changing the value of this property. 96 | - warning: The option `key` and `keys` cannot be used together. 97 | */ 98 | public init(databaseName: String, 99 | descending: Bool? = nil, 100 | endKey: String? = nil, 101 | includeDocs: Bool? = nil, 102 | conflicts: Bool? = nil, 103 | key: String? = nil, 104 | keys: [String]? = nil, 105 | limit: UInt? = nil, 106 | skip: UInt? = nil, 107 | startKeyDocumentID: String? = nil, 108 | endKeyDocumentID: String? = nil, 109 | stale: Stale? = nil, 110 | startKey: String? = nil, 111 | includeLastUpdateSequenceNumber: Bool? = nil, 112 | inclusiveEnd: Bool? = nil, 113 | rowHandler: (([String: Any]) -> Void)? = nil, 114 | completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil){ 115 | 116 | self.databaseName = databaseName 117 | self.descending = descending 118 | self.endKey = endKey 119 | self.includeDocs = includeDocs 120 | self.conflicts = conflicts 121 | self.key = key 122 | self.keys = keys 123 | self.limit = limit 124 | self.skip = skip 125 | self.startKeyDocumentID = startKeyDocumentID 126 | self.stale = stale 127 | self.startKey = startKey 128 | self.includeLastUpdateSequenceNumber = includeLastUpdateSequenceNumber 129 | self.endKeyDocumentID = endKeyDocumentID 130 | self.rowHandler = rowHandler 131 | self.completionHandler = completionHandler 132 | self.inclusiveEnd = inclusiveEnd 133 | } 134 | 135 | private var jsonData: Data? 136 | 137 | 138 | public func validate() -> Bool { 139 | 140 | if conflicts != nil && includeDocs != true { 141 | return false 142 | } 143 | 144 | if keys != nil && key != nil { 145 | return false 146 | } 147 | 148 | return true 149 | } 150 | 151 | public var endpoint: String { 152 | return "/\(databaseName)/_all_docs" 153 | } 154 | 155 | public var parameters: [String : String] { 156 | get { 157 | var params:[String: String] = makeParams() 158 | 159 | if let endKeyJson = endKeyJson { 160 | params["endkey"] = endKeyJson 161 | } 162 | 163 | if let keyJson = keyJson { 164 | params["key"] = keyJson 165 | } 166 | 167 | if let startKeyJson = startKeyJson { 168 | params["startkey"] = startKeyJson 169 | } 170 | 171 | return params; 172 | } 173 | } 174 | 175 | private var keyJson: String? 176 | private var endKeyJson: String? 177 | private var startKeyJson: String? 178 | 179 | public func serialise() throws { 180 | if let keys = keys { 181 | jsonData = try JSONSerialization.data(withJSONObject: keys) 182 | } 183 | 184 | if let key = key { 185 | keyJson = try jsonValue(for: key) 186 | } 187 | if let endKey = endKey { 188 | endKeyJson = try jsonValue(for: endKey) 189 | } 190 | 191 | if let startKey = startKey { 192 | startKeyJson = try jsonValue(for: startKey) 193 | } 194 | } 195 | 196 | public var data: Data? { 197 | return jsonData 198 | } 199 | 200 | } 201 | 202 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/Operations/Views/ViewLikeOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewLikeOperation.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 06/07/2016. 6 | // 7 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 10 | // except in compliance with the License. You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software distributed under the 13 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 14 | // either express or implied. See the License for the specific language governing permissions 15 | // and limitations under the License. 16 | // 17 | 18 | import Foundation 19 | 20 | 21 | // This Can't be defined in the extension or the protocol defining outside for now. 22 | /** 23 | Acceptable values for allowing stale views with the `stale` property. To disallow stale views use the default `stale=nil`. 24 | */ 25 | public enum Stale: CustomStringConvertible { 26 | /** Allow stale views. */ 27 | case ok 28 | /** Allow stale views, but update them immediately after the request. */ 29 | case updateAfter 30 | 31 | public var description: String { 32 | switch self { 33 | case .ok: return "ok" 34 | case .updateAfter: return "update_after" 35 | } 36 | } 37 | } 38 | 39 | /** 40 | Denotes an operation that performs actions on a view. 41 | Some operations are not strictly a view externally, but internally 42 | they are effectively a view and have similar parameters. 43 | */ 44 | public protocol ViewOperation : CouchDatabaseOperation { 45 | associatedtype ViewParameter 46 | 47 | /** 48 | Should the result rows be returning in `descending by key` order for this request. 49 | 50 | - `true` if the result rows should be returned in `descending by key` order 51 | - `false` if the result rows should **not** be ordered in `descending by key` order 52 | - `nil` no preference, server defaults will apply. 53 | */ 54 | var descending: Bool? { get } 55 | 56 | /** 57 | The key from which result rows should start from. 58 | */ 59 | var startKey: ViewParameter? { get } 60 | 61 | /** 62 | Used in conjunction with startKey to further restrict the starting row for 63 | cases where two documents emit the same key. Specifying the doc ID allows 64 | the view to return result rows from the specified start key and document. 65 | */ 66 | var startKeyDocumentID: String? { get } 67 | 68 | /** 69 | Stop the view returning result rows when the specified key is reached. 70 | */ 71 | var endKey: ViewParameter? { get } 72 | 73 | /** 74 | Used in conjunction with endKey to further restrict the ending row for cases 75 | where two documents emit the same key. Specifying the doc ID allows the view 76 | to return result rows up to the specified end key and document. 77 | */ 78 | var endKeyDocumentID: String? { get } 79 | 80 | /** 81 | Include result rows with the specified `endKey`. 82 | */ 83 | var inclusiveEnd: Bool? { get } 84 | 85 | /** 86 | Return only result rows that match the specified key. 87 | */ 88 | var key: ViewParameter? { get } 89 | 90 | /** 91 | Return only result rows that match the specified keys. 92 | */ 93 | var keys: [ViewParameter]? { get } 94 | 95 | /** 96 | Limit the number of result rows returned from the view. 97 | */ 98 | var limit: UInt? { get } 99 | 100 | /** 101 | The number of rows to skip in the view results. 102 | */ 103 | var skip: UInt? { get } 104 | 105 | /** 106 | Include the full content of documents in the view results. 107 | */ 108 | var includeDocs: Bool? { get } 109 | 110 | /** 111 | Include informaion about conflicted revisions in the response. 112 | */ 113 | var conflicts: Bool? { get } 114 | 115 | /** 116 | Configures the view request to allow the return of stale results. This allows the view to return 117 | immediately rather than waiting for the view index to build. When this parameter is omitted (i.e. with the 118 | default of `stale=nil`) the server will not return stale results. 119 | 120 | - SeeAlso: `Stale` for descriptions of the available values for allowing stale views. 121 | */ 122 | var stale: Stale? { get } 123 | 124 | /** 125 | Determines if the last seqeunce number from which the view was updated should be included in 126 | the response 127 | */ 128 | var includeLastUpdateSequenceNumber: Bool? { get } 129 | 130 | /** 131 | A handler to run for each row retrieved by the view. 132 | 133 | - parameter row: dictionary of the JSON data from the view row 134 | */ 135 | var rowHandler: (([String: Any]) -> Void)? { get } 136 | 137 | } 138 | 139 | public extension ViewOperation { 140 | 141 | func processResponse(json: Any) { 142 | if let json = json as? [String: Any] { 143 | let rows = json["rows"] as! [[String: Any]] 144 | for row: [String: Any] in rows { 145 | self.rowHandler?(row) 146 | } 147 | } 148 | } 149 | 150 | var method: String { 151 | get { 152 | if keys != nil { 153 | return "POST" 154 | } else { 155 | return "GET" 156 | } 157 | } 158 | } 159 | 160 | /** 161 | Generates parameters for the following properties 162 | 163 | * descending 164 | * startKeyDocId 165 | * endKeyDocId 166 | * inclusiveEnd 167 | * limit 168 | * skip 169 | * includeDocs 170 | * conflicts 171 | 172 | 173 | - Note: Implementing types *have* to add parameters which use the `associatedtype` `ViewParameter` 174 | */ 175 | func makeParams() -> [String : String]{ 176 | var items: [String: String] = [:] 177 | 178 | if let descending = descending { 179 | items["descending"] = "\(descending)" 180 | } 181 | 182 | if let startKeyDocId = startKeyDocumentID { 183 | items["startkey_docid"] = startKeyDocId 184 | } 185 | 186 | if let endKeyDocId = endKeyDocumentID { 187 | items["endkey_docid"] = "\(endKeyDocId)" 188 | } 189 | 190 | if let inclusiveEnd = inclusiveEnd { 191 | items["inclusive_end"] = "\(inclusiveEnd)" 192 | } 193 | 194 | if let limit = limit { 195 | items["limit"] = "\(limit)" 196 | } 197 | 198 | if let skip = skip { 199 | items["skip"] = "\(skip)" 200 | } 201 | 202 | if let includeDocs = includeDocs { 203 | items["include_docs"] = "\(includeDocs)" 204 | } 205 | 206 | if let stale = stale { 207 | items["stale"] = "\(stale)" 208 | } 209 | 210 | if let conflicts = conflicts { 211 | items["conflicts"] = "\(conflicts)" 212 | } 213 | 214 | if let includeLastUpdateSequenceNumber = includeLastUpdateSequenceNumber { 215 | items["update_seq"] = "\(includeLastUpdateSequenceNumber)" 216 | } 217 | 218 | return items 219 | } 220 | 221 | func jsonValue(for key: Any) throws -> String { 222 | if JSONSerialization.isValidJSONObject(key) { 223 | let keyJson = try JSONSerialization.data(withJSONObject: key) 224 | return String(data: keyJson, encoding: .utf8)! 225 | } else if key is String { 226 | // we need to quote JSON primitive strings 227 | return "\"\(key)\"" 228 | } else { 229 | // anything else we just try as stringified JSON value 230 | return "\(key)" 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Source/SwiftCloudant/SwiftCloudant.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftCloudant.h 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | #import 18 | 19 | //! Project version number for SwiftCloudant. 20 | FOUNDATION_EXPORT double SwiftCloudantVersionNumber; 21 | 22 | //! Project version string for SwiftCloudant. 23 | FOUNDATION_EXPORT const unsigned char SwiftCloudantVersionString[]; 24 | 25 | // In this header, you should import all the public headers of your framework using statements like #import 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftCloudant.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "SwiftCloudant" 4 | s.version = "0.9.1-SNAPSHOT" 5 | s.summary = "SwiftCloudant is a client library for Apache CouchDB / IBM Cloudant" 6 | 7 | s.description = <<-DESC 8 | 9 | SwiftCloudant is a client library for interacting with 10 | Apache CouchDB / IBM Cloudant. 11 | 12 | It provides an operation based API for performing actions 13 | with the Apache CouchDB HTTP API. 14 | 15 | DESC 16 | 17 | s.homepage = "https://github.com/cloudant/swift-cloudant" 18 | 19 | s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } 20 | 21 | s.author = { "IBM Cloudant" => "support@cloudant.com" } 22 | 23 | s.ios.deployment_target = "8.0" 24 | s.osx.deployment_target = "10.10" 25 | 26 | s.source = { :git => "https://github.com/cloudant/swift-cloudant.git", :tag => s.version.to_s} 27 | s.source_files = "Classes", "Source/**/*.swift" 28 | s.swift_version = '4.2' 29 | end 30 | -------------------------------------------------------------------------------- /Tests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinuxMain.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 28/06/2016. 6 | // Copyright © 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import XCTest 18 | 19 | @testable import SwiftCloudantTests 20 | 21 | XCTMain( [ 22 | testCase(CreateDatabaseTests.allTests), 23 | testCase(PutAttachmentTests.allTests), 24 | testCase(CreateQueryIndexTests.allTests), 25 | testCase(DeleteAttachmentTests.allTests), 26 | testCase(GetDocumentTests.allTests), 27 | testCase(ReadAttachmentTests.allTests), 28 | testCase(QueryViewTests.allTests), 29 | testCase(GetAllDatabasesTest.allTests), 30 | testCase(DeleteDocumentTests.allTests), 31 | testCase(GetAllDocsTest.allTests), 32 | testCase(InterceptableSessionTests.allTests), 33 | testCase(DeleteQueryIndexTests.allTests), 34 | testCase(FindDocumentOperationTests.allTests), 35 | testCase(BulkDocsTests.allTests), 36 | testCase(PutDocumentTests.allTests), 37 | testCase(GetChangesTests.allTests),]) 38 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/BulkDocsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BulkDocsTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 27/07/2016. 6 | // 7 | // Copyright (C) 2016 IBM Corp. 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 10 | // except in compliance with the License. You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software distributed under the 13 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 14 | // either express or implied. See the License for the specific language governing permissions 15 | // and limitations under the License. 16 | // 17 | 18 | import Foundation 19 | import XCTest 20 | @testable import SwiftCloudant 21 | 22 | class BulkDocsTests : XCTestCase { 23 | 24 | static var allTests = { 25 | return [ 26 | ("testValidationOnlyDocs", testValidationOnlyDocs), 27 | ("testValidationAllOrNothing",testValidationAllOrNothing), 28 | ("testGeneratedPayloadAllOptions",testGeneratedPayloadAllOptions), 29 | ("testGeneratedPayloadNewEdits",testGeneratedPayloadNewEdits), 30 | ("testGeneratedPayloadAllOrNothing",testGeneratedPayloadAllOrNothing), 31 | ("testCompleteRequest",testCompleteRequest),] 32 | }() 33 | 34 | var dbName: String? = nil 35 | var client: CouchDBClient? = nil 36 | let docs:[[String:Any]] = [["hello":"world"], ["foo": "bar"]] 37 | 38 | override func setUp() { 39 | super.setUp() 40 | self.dbName = generateDBName() 41 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 42 | createDatabase(databaseName: dbName!, client: client!) 43 | } 44 | 45 | override func tearDown() { 46 | deleteDatabase(databaseName: dbName!, client: client!) 47 | super.tearDown() 48 | } 49 | 50 | func testValidationOnlyDocs() { 51 | 52 | 53 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs) 54 | XCTAssert(bulk.validate()) 55 | } 56 | 57 | func testValidationNewEdits() { 58 | 59 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs, newEdits: false) 60 | XCTAssert(bulk.validate()) 61 | } 62 | 63 | func testValidationAllOrNothing() { 64 | 65 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs, allOrNothing: true) 66 | XCTAssert(bulk.validate()) 67 | } 68 | 69 | func testGeneratedPayloadAllOptions() throws { 70 | 71 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs, newEdits: false, allOrNothing: true) 72 | XCTAssert(bulk.validate()) 73 | 74 | try bulk.serialise() 75 | 76 | let data = bulk.data 77 | XCTAssertNotNil(data) 78 | 79 | XCTAssertEqual("POST", bulk.method) 80 | if let data = data { 81 | 82 | let requestJson = try JSONSerialization.jsonObject(with: data) as! [String: Any] 83 | 84 | 85 | let expected: [String: Any] = ["docs":[["hello":"world"],["foo":"bar"]], "new_edits":false, "all_or_nothing":true] 86 | 87 | XCTAssertEqual(NSDictionary(dictionary: expected), NSDictionary(dictionary: requestJson)) 88 | } 89 | } 90 | 91 | func testGeneratedPayloadNewEdits() throws { 92 | 93 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs, newEdits: false) 94 | XCTAssert(bulk.validate()) 95 | 96 | try bulk.serialise() 97 | 98 | let data = bulk.data 99 | XCTAssertNotNil(data) 100 | 101 | XCTAssertEqual("POST", bulk.method) 102 | if let data = data { 103 | 104 | let requestJson = try JSONSerialization.jsonObject(with: data) as! [String: Any] 105 | 106 | let expected: [String: Any] = ["docs":[["hello":"world"],["foo":"bar"]], "new_edits":false] 107 | XCTAssertEqual(NSDictionary(dictionary: expected), NSDictionary(dictionary: requestJson)) 108 | } 109 | } 110 | 111 | func testGeneratedPayloadAllOrNothing() throws { 112 | 113 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs, allOrNothing: true) 114 | XCTAssert(bulk.validate()) 115 | 116 | try bulk.serialise() 117 | 118 | let data = bulk.data 119 | XCTAssertNotNil(data) 120 | 121 | XCTAssertEqual("POST", bulk.method) 122 | if let data = data { 123 | 124 | let requestJson = try JSONSerialization.jsonObject(with: data) as! [String: Any] 125 | 126 | let expected: [String: Any] = ["docs":[["hello":"world"],["foo":"bar"]], "all_or_nothing":true] 127 | XCTAssertEqual(NSDictionary(dictionary: expected), NSDictionary(dictionary: requestJson)) 128 | } 129 | } 130 | 131 | func testCompleteRequest() throws { 132 | let expectation = self.expectation(description: "bulk insert") 133 | 134 | let bulk = PutBulkDocsOperation(databaseName: dbName!, documents: docs) { (response, httpInfo, error) in 135 | XCTAssertNil(error) 136 | XCTAssertNotNil(response) 137 | XCTAssertNotNil(httpInfo) 138 | 139 | if let httpInfo = httpInfo { 140 | XCTAssert(httpInfo.statusCode / 100 == 2) 141 | } 142 | 143 | if let response = response { 144 | XCTAssertEqual(2,response.count) 145 | 146 | for doc in response { 147 | 148 | if let ok = doc["ok"] as? Bool { 149 | XCTAssertEqual(true, ok) 150 | } 151 | 152 | } 153 | } 154 | expectation.fulfill() 155 | 156 | } 157 | 158 | client?.add(operation: bulk) 159 | self.waitForExpectations(timeout: 10.0) 160 | 161 | // Check that all the documents are in the db 162 | let getDocsExpect = self.expectation(description: "get all docs") 163 | let allDocs = GetAllDocsOperation(databaseName: dbName!){ (response, httpInfo, error) in 164 | 165 | XCTAssertNotNil(response) 166 | XCTAssertNotNil(httpInfo) 167 | XCTAssertNil(error) 168 | 169 | if let response = response, let httpInfo = httpInfo { 170 | XCTAssertEqual(2, httpInfo.statusCode / 100) 171 | XCTAssertNotNil(response["rows"]) 172 | if let rows = response["rows"] as? [String] { 173 | XCTAssertEqual(2, rows.count) 174 | } 175 | } 176 | getDocsExpect.fulfill() 177 | } 178 | self.client?.add(operation: allDocs) 179 | self.waitForExpectations(timeout: 10.0) 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/CouchClientTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CouchClientTest.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Sam Smith on 19/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import SwiftCloudant 12 | 13 | class CouchClientTest: XCTestCase { 14 | 15 | private class VCAPGenerator { 16 | 17 | private var VCAPServices = ["cloudantNoSQLDB": []] 18 | 19 | public func addService(name: String?, url: String?) { 20 | var VCAPService = [String:Any]() 21 | if name != nil { 22 | VCAPService["name"] = name! 23 | } 24 | if url != nil { 25 | if url == "" { 26 | VCAPService["credentials"] = [] // empty credentials array 27 | } else { 28 | VCAPService["credentials"] = ["url": url!] 29 | } 30 | } 31 | 32 | VCAPServices["cloudantNoSQLDB"]!.append(VCAPService) 33 | } 34 | 35 | public func toJSONString() throws -> String { 36 | let json = try JSONSerialization.data(withJSONObject: VCAPServices) 37 | return String(data: json, encoding: .utf8)! 38 | } 39 | } 40 | 41 | override func setUp() { 42 | super.setUp() 43 | // Put setup code here. This method is called before the invocation of each test method in the class. 44 | } 45 | 46 | override func tearDown() { 47 | // Put teardown code here. This method is called after the invocation of each test method in the class. 48 | super.tearDown() 49 | } 50 | 51 | func testCanAllocateAndDeallocateCouchDBClient() { 52 | // Previously client would cause a crash where we deallocated an unsued client, i.e. no requests have been made. 53 | let url = URL(string: "https://example:xxxxxxx@example.cloudant.com")! 54 | let _ = CouchDBClient(url: url, username: url.user, password: url.password) 55 | } 56 | 57 | func testEmptyVCAPServiceJSONFailure() { 58 | XCTAssertThrowsError(try CouchDBClient(vcapServices: "{}", instanceName: "myInstance")) { (error) -> Void in 59 | XCTAssertEqual(error as? CouchDBClient.Error, CouchDBClient.Error.missingCloudantService) 60 | } 61 | } 62 | 63 | func testMissingVCAPServiceJSONFailure() { 64 | XCTAssertThrowsError(try CouchDBClient(vcapServices: "", instanceName: "myInstance")) { (error) -> Void in 65 | XCTAssertEqual(error as? CouchDBClient.Error, CouchDBClient.Error.invalidVCAP) 66 | } 67 | } 68 | 69 | func testGetInstanceWithNameFromSingleVCAPServiceJSON() throws { 70 | let vcap = VCAPGenerator() 71 | // create VCAP 72 | vcap.addService(name: "example1", url: "https://example1:xxxxxxx@example1.cloudant.com") 73 | 74 | let client = try CouchDBClient(vcapServices: vcap.toJSONString(), instanceName: "example1") 75 | XCTAssertEqual("example1", client.username) 76 | XCTAssertEqual("xxxxxxx", client.password) 77 | XCTAssertEqual("https://example1:xxxxxxx@example1.cloudant.com", client.rootURL.absoluteString) 78 | } 79 | 80 | func testGetInstanceWithoutNameFromSingleVCAPServiceJSON() throws { 81 | let vcap = VCAPGenerator() 82 | // create VCAP 83 | vcap.addService(name: "example1", url: "https://example1:xxxxxxx@example1.cloudant.com") 84 | 85 | let client = try CouchDBClient(vcapServices: vcap.toJSONString()) 86 | XCTAssertEqual("example1", client.username) 87 | XCTAssertEqual("xxxxxxx", client.password) 88 | XCTAssertEqual("https://example1:xxxxxxx@example1.cloudant.com", client.rootURL.absoluteString) 89 | } 90 | 91 | func testGetInstanceWithNameFromMultiVCAPServiceJSON() throws { 92 | let vcap = VCAPGenerator() 93 | // create VCAP 94 | vcap.addService(name: "example1", url: "https://example1:xxxxxxx@example1.cloudant.com") 95 | vcap.addService(name: "example2", url: "https://example2:xxxxxxx@example2.cloudant.com") 96 | 97 | let client = try CouchDBClient(vcapServices: vcap.toJSONString(), instanceName: "example1") 98 | XCTAssertEqual("example1", client.username) 99 | XCTAssertEqual("xxxxxxx", client.password) 100 | XCTAssertEqual("https://example1:xxxxxxx@example1.cloudant.com", client.rootURL.absoluteString) 101 | } 102 | 103 | func testGetInstanceWithoutNameFromMultiVCAPServiceJSON() throws { 104 | let vcap = VCAPGenerator() 105 | // create VCAP 106 | vcap.addService(name: "example1", url: "https://example1:xxxxxxx@example1.cloudant.com") 107 | vcap.addService(name: "example2", url: "https://example2:xxxxxxx@example2.cloudant.com") 108 | 109 | XCTAssertThrowsError(try CouchDBClient(vcapServices: vcap.toJSONString())) { (error) -> Void in 110 | XCTAssertEqual(error as? CouchDBClient.Error, CouchDBClient.Error.instanceNameRquired) 111 | } 112 | } 113 | 114 | func testMissingNamedVCAPServiceJSON() throws { 115 | let vcap = VCAPGenerator() 116 | // create VCAP 117 | vcap.addService(name: "example1", url: "https://example1:xxxxxxx@example1.cloudant.com") 118 | vcap.addService(name: "example2", url: "https://example2:xxxxxxx@example2.cloudant.com") 119 | 120 | XCTAssertThrowsError(try CouchDBClient(vcapServices: vcap.toJSONString(), instanceName: "example3")) { (error) -> Void in 121 | XCTAssertEqual(error as? CouchDBClient.Error, CouchDBClient.Error.missingCloudantService) 122 | } 123 | } 124 | 125 | func testMissingCredentialsFromVCAPServiceJSON() throws { 126 | let vcap = VCAPGenerator() 127 | // create VCAP 128 | vcap.addService(name: "example1", url: nil) // missing credentials 129 | 130 | XCTAssertThrowsError(try CouchDBClient(vcapServices: vcap.toJSONString(), instanceName: "example1")) { (error) -> Void in 131 | XCTAssertEqual(error as? CouchDBClient.Error, CouchDBClient.Error.invalidVCAP) 132 | } 133 | } 134 | 135 | func testMissingURLFromVCAPServiceJSON() throws { 136 | let vcap = VCAPGenerator() 137 | // create VCAP 138 | vcap.addService(name: "example1", url: "") // missing url 139 | 140 | XCTAssertThrowsError(try CouchDBClient(vcapServices: vcap.toJSONString(), instanceName: "example1")) { (error) -> Void in 141 | XCTAssertEqual(error as? CouchDBClient.Error, CouchDBClient.Error.invalidVCAP) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/CreateDatabaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateDatabaseTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 03/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | class CreateDatabaseTests: XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testCreateUsingPut", testCreateUsingPut),] 26 | }() 27 | var dbName: String? = nil 28 | var client: CouchDBClient? = nil 29 | 30 | override func setUp() { 31 | super.setUp() 32 | self.dbName = generateDBName() 33 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 34 | } 35 | 36 | override func tearDown() { 37 | deleteDatabase(databaseName: dbName!, client: client!) 38 | super.tearDown() 39 | } 40 | 41 | func testCreateUsingPut() { 42 | let createExpectation = self.expectation(description: "create database") 43 | 44 | let create = CreateDatabaseOperation(name: self.dbName!) { (response, httpInfo, error) in 45 | createExpectation.fulfill() 46 | XCTAssertNotNil(httpInfo) 47 | if let httpInfo = httpInfo { 48 | XCTAssertTrue(httpInfo.statusCode / 100 == 2) 49 | } 50 | XCTAssertNil(error) 51 | } 52 | 53 | client?.add(operation: create) 54 | 55 | self.waitForExpectations(timeout: 10.0, handler: nil) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/DeleteAttachmentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteAttachmentTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 17/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | class DeleteAttachmentTests : XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testDeleteAttachment", testDeleteAttachment), 26 | ("testDeleteAttachmentHTTPOperationProperties",testDeleteAttachmentHTTPOperationProperties),] 27 | }() 28 | 29 | var dbName: String? = nil 30 | var client: CouchDBClient? = nil 31 | let docId: String = "PutAttachmentTests" 32 | var revId: String? 33 | 34 | override func setUp() { 35 | super.setUp() 36 | 37 | dbName = generateDBName() 38 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 39 | createDatabase(databaseName: dbName!, client: client!) 40 | let createDoc = PutDocumentOperation(id: docId, 41 | body: createTestDocuments(count: 1).first!, 42 | databaseName: dbName!){[weak self] (response, info, error) in 43 | self?.revId = response?["rev"] as? String 44 | } 45 | let nsCreate = Operation(couchOperation: createDoc) 46 | client?.add(operation: nsCreate) 47 | nsCreate.waitUntilFinished() 48 | 49 | let attachment = "This is my awesome essay attachment for my document" 50 | let put = PutAttachmentOperation(name: "myAwesomeAttachment", 51 | contentType: "text/plain", 52 | data: attachment.data(using: String.Encoding.utf8, allowLossyConversion: false)!, 53 | documentID: docId, 54 | revision: revId!, 55 | databaseName: dbName! 56 | ) 57 | {[weak self] (response, info, error) in 58 | XCTAssertNil(error) 59 | XCTAssertNotNil(info) 60 | if let info = info { 61 | XCTAssert(info.statusCode / 100 == 2) 62 | } 63 | XCTAssertNotNil(response) 64 | self?.revId = response?["rev"] as? String 65 | 66 | } 67 | let nsPut = Operation(couchOperation: put) 68 | client?.add(operation: nsPut) 69 | nsPut.waitUntilFinished() 70 | } 71 | 72 | override func tearDown() { 73 | deleteDatabase(databaseName: dbName!, client: client!) 74 | 75 | super.tearDown() 76 | } 77 | 78 | func testDeleteAttachment(){ 79 | let deleteExpectation = self.expectation(description: "Delete expectation") 80 | let delete = DeleteAttachmentOperation(name: "myAwesomeAttachment", documentID: docId, revision: revId!, databaseName: dbName!) 81 | { (response, info, error) in 82 | XCTAssertNil(error) 83 | XCTAssertNotNil(info) 84 | if let info = info { 85 | XCTAssert(info.statusCode / 100 == 2) 86 | } 87 | XCTAssertNotNil(response) 88 | deleteExpectation.fulfill() 89 | } 90 | client?.add(operation: delete) 91 | self.waitForExpectations(timeout: 10.0, handler: nil) 92 | 93 | let getExpectation = self.expectation(description: "Get deleted Attachment") 94 | 95 | //make sure that the attachment has been deleted. 96 | let get = ReadAttachmentOperation(name: "myAwesomeAttachment", documentID: docId, databaseName: dbName!){ 97 | (response, info, error) in 98 | XCTAssertNotNil(error) 99 | XCTAssertNotNil(info) 100 | XCTAssertNotNil(response) 101 | 102 | if let info = info { 103 | XCTAssertEqual(404, info.statusCode) 104 | } 105 | getExpectation.fulfill() 106 | } 107 | 108 | client?.add(operation: get) 109 | self.waitForExpectations(timeout: 10.0, handler: nil) 110 | 111 | } 112 | 113 | func testDeleteAttachmentHTTPOperationProperties(){ 114 | let delete = DeleteAttachmentOperation(name: "myAwesomeAttachment", documentID: docId, revision: revId!, databaseName: dbName!) 115 | XCTAssertTrue(delete.validate()) 116 | XCTAssertEqual(["rev": revId!], delete.parameters) 117 | XCTAssertEqual("/\(self.dbName!)/\(docId)/myAwesomeAttachment", delete.endpoint) 118 | XCTAssertEqual("DELETE", delete.method) 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/DeleteDocumentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteDocumentTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 12/04/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | class DeleteDocumentTests: XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testDocumentCanBeDeleted", testDocumentCanBeDeleted), 26 | ("testDeleteDocumentOpCompletesWithoutCallback",testDeleteDocumentOpCompletesWithoutCallback)] 27 | }() 28 | 29 | var dbName: String? = nil 30 | var client: CouchDBClient? = nil 31 | 32 | override func setUp() { 33 | super.setUp() 34 | 35 | dbName = generateDBName() 36 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 37 | createDatabase(databaseName: dbName!, client: client!) 38 | 39 | print("Created database: \(dbName!)") 40 | } 41 | 42 | override func tearDown() { 43 | deleteDatabase(databaseName: dbName!, client: client!) 44 | super.tearDown() 45 | } 46 | 47 | func testDocumentCanBeDeleted() { 48 | let expectation = self.expectation(description: "Delete document") 49 | 50 | let create = PutDocumentOperation(id: "testId", 51 | body: ["hello": "world"], 52 | databaseName: dbName!){[weak self] (response, httpInfo, error) in 53 | 54 | let delete = DeleteDocumentOperation(id: response?["id"] as! String, 55 | revision: response?["rev"] as! String, 56 | databaseName: self!.dbName!) 57 | { (response, httpInfo, error) in 58 | expectation.fulfill() 59 | XCTAssertNotNil(httpInfo) 60 | if let httpInfo = httpInfo { 61 | XCTAssert(httpInfo.statusCode / 100 == 2) 62 | } 63 | XCTAssertNil(error) 64 | XCTAssertEqual(true, response?["ok"] as? Bool) 65 | } 66 | self?.client?.add(operation: delete) 67 | 68 | } 69 | 70 | let nsCreate = Operation(couchOperation: create) 71 | 72 | client?.add(operation: nsCreate) 73 | 74 | self.waitForExpectations(timeout: 10.0, handler: nil) 75 | 76 | } 77 | 78 | func testDeleteDocumentOpCompletesWithoutCallback() { 79 | let expectation = self.expectation(description: "Delete document") 80 | 81 | let create = PutDocumentOperation(id: "testId", 82 | body: ["hello": "world"], 83 | databaseName: dbName!) { [weak self](response, httpInfo, error) in 84 | 85 | let delete = DeleteDocumentOperation(id: response?["id"] as! String, revision: response!["rev"] as! String, databaseName: self!.dbName!) 86 | { [weak self](response, httpInfo, error) in 87 | XCTAssertNotNil(httpInfo) 88 | if let httpInfo = httpInfo { 89 | XCTAssert(httpInfo.statusCode / 100 == 2) 90 | } 91 | XCTAssertNil(error) 92 | 93 | let get = GetDocumentOperation(id: "testId", databaseName: self!.dbName!) 94 | { (response, httpInfo, error) in 95 | expectation.fulfill() 96 | XCTAssertNotNil(response) 97 | XCTAssertNotNil(error) 98 | } 99 | self?.client?.add(operation: get) 100 | } 101 | self?.client?.add(operation: delete) 102 | 103 | } 104 | 105 | 106 | 107 | client?.add(operation: create) 108 | 109 | self.waitForExpectations(timeout: 100.0, handler: nil) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/DeleteQueryIndexTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteQueryIndexTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 18/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | public class DeleteQueryIndexTests: XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testCanDeleteJSONIndex", testCanDeleteJSONIndex), 26 | ("testCanDeleteTextIndex",testCanDeleteTextIndex), 27 | ("testOperationPropertiesJSONIndex",testOperationPropertiesJSONIndex), 28 | ("testOperationPropertiesTextIndex",testOperationPropertiesTextIndex),] 29 | }() 30 | 31 | var client: CouchDBClient? = nil 32 | var dbName: String? = nil 33 | 34 | 35 | override public func setUp() { 36 | super.setUp() 37 | dbName = generateDBName() 38 | client = CouchDBClient(url: URL(string:self.url)!, username: self.username, password: self.password) 39 | } 40 | 41 | override public func tearDown() { 42 | super.tearDown() 43 | } 44 | 45 | func testCanDeleteJSONIndex() { 46 | let deleteExpectation = self.expectation(description: "delete json index") 47 | let deleteIndex = DeleteQueryIndexOperation(name: "jsonIndex", type: .json, designDocumentID: "ddoc", databaseName: dbName!) 48 | {(response, httpStatus, error) in 49 | XCTAssertNotNil(response) 50 | XCTAssertEqual(true, response?["ok"] as? Bool) 51 | XCTAssertNotNil(httpStatus) 52 | if let httpStatus = httpStatus { 53 | XCTAssert(httpStatus.statusCode / 100 == 2) 54 | } 55 | XCTAssertNil(error) 56 | deleteExpectation.fulfill() 57 | } 58 | self.simulateOkResponseFor(operation: deleteIndex) 59 | self.waitForExpectations(timeout:10.0, handler:nil) 60 | } 61 | 62 | public func testCanDeleteTextIndex() { 63 | let deleteExpectation = self.expectation(description: "delete json index") 64 | let deleteIndex = DeleteQueryIndexOperation(name: "textIndex", type: .text, designDocumentID: "ddoc", databaseName: dbName!) 65 | {(response, httpStatus, error) in 66 | XCTAssertNotNil(response) 67 | XCTAssertEqual(true, response?["ok"] as? Bool) 68 | XCTAssertNotNil(httpStatus) 69 | if let httpStatus = httpStatus { 70 | XCTAssert(httpStatus.statusCode / 100 == 2) 71 | } 72 | XCTAssertNil(error) 73 | deleteExpectation.fulfill() 74 | } 75 | self.simulateOkResponseFor(operation: deleteIndex) 76 | self.waitForExpectations(timeout:10.0, handler:nil) 77 | } 78 | 79 | public func testOperationPropertiesJSONIndex() { 80 | let deleteIndex = DeleteQueryIndexOperation(name: "jsonIndex", type: .json, designDocumentID: "ddoc", databaseName: "dbName") 81 | XCTAssert(deleteIndex.validate()) 82 | XCTAssertEqual("DELETE", deleteIndex.method) 83 | XCTAssertEqual("/dbName/_index/ddoc/json/jsonIndex", deleteIndex.endpoint) 84 | } 85 | 86 | public func testOperationPropertiesTextIndex() { 87 | let deleteIndex = DeleteQueryIndexOperation(name: "textIndex", type: .text, designDocumentID: "ddoc", databaseName: "dbName") 88 | XCTAssert(deleteIndex.validate()) 89 | XCTAssertEqual("DELETE", deleteIndex.method) 90 | XCTAssertEqual("/dbName/_index/ddoc/text/textIndex", deleteIndex.endpoint) 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/GetAllDatabasesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetAllDatabasesTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 23/05/2016. 6 | // Copyright © 2016 IBM. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | 22 | public class GetAllDatabasesTest : XCTestCase { 23 | 24 | static var allTests = { 25 | return [ 26 | ("testListAllDbs", testListAllDbs),] 27 | }() 28 | 29 | var dbName: String? = nil 30 | var client: CouchDBClient? 31 | 32 | override public func setUp() { 33 | super.setUp() 34 | dbName = generateDBName() 35 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 36 | createDatabase(databaseName: dbName!, client: client!) 37 | } 38 | 39 | override public func tearDown() { 40 | deleteDatabase(databaseName: dbName!, client: client!) 41 | super.tearDown() 42 | } 43 | 44 | public func testListAllDbs(){ 45 | let expectation = self.expectation(description: "all_dbs") 46 | var found = false 47 | let list = GetAllDatabasesOperation(databaseHandler: 48 | { (dbName) in 49 | if dbName.hasPrefix("_"){ 50 | return 51 | } 52 | XCTAssertNotNil(dbName) 53 | if self.dbName! == dbName { 54 | found = true 55 | } 56 | }) 57 | { (response, httpInfo, error ) in 58 | XCTAssertNil(error) 59 | XCTAssertNotNil(response) 60 | XCTAssertNotNil(httpInfo) 61 | if let httpInfo = httpInfo { 62 | XCTAssert(httpInfo.statusCode / 100 == 2) 63 | } 64 | if let response = response { 65 | let expected: [String] = [self.dbName!] 66 | 67 | if response is [String] { 68 | XCTAssertTrue(expected.contains(self.dbName!)) 69 | } else { 70 | XCTFail("response is not an array [String]") 71 | } 72 | 73 | } 74 | 75 | expectation.fulfill() 76 | } 77 | client?.add(operation: list) 78 | 79 | self.waitForExpectations(timeout: 10.0, handler: nil) 80 | XCTAssertTrue(found, "Did not find database \(self.dbName!) in a call to the database handler") 81 | 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/GetChangesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetChangesTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 23/08/2016. 6 | // 7 | // Copyright (C) 2016 IBM Corp. 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 10 | // except in compliance with the License. You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software distributed under the 13 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 14 | // either express or implied. See the License for the specific language governing permissions 15 | // and limitations under the License. 16 | // 17 | 18 | 19 | import Foundation 20 | import XCTest 21 | @testable import SwiftCloudant 22 | 23 | 24 | class GetChangesTests : XCTestCase { 25 | 26 | static var allTests = { 27 | return [ 28 | ("testChangesFeedDefaults", testChangesFeedDefaults), 29 | ("testChangesFeedMixedQuery",testChangesFeedMixedQuery), 30 | ("testRequestPropertiesDocIds",testRequestPropertiesDocIds), 31 | ("testRequestPropertiesConflicts",testRequestPropertiesConflicts), 32 | ("testRequestPropertiesDescending",testRequestPropertiesDescending), 33 | ("testRequestPropertiesFilter",testRequestPropertiesFilter), 34 | ("testRequestPropertiesIncludeDocs",testRequestPropertiesIncludeDocs), 35 | ("testRequestPropertiesincludeAttachments",testRequestPropertiesincludeAttachments), 36 | ("testRequestPropertiesIncludeAttachmentInfo",testRequestPropertiesIncludeAttachmentInfo), 37 | ("testRequestPropertiesLimit",testRequestPropertiesLimit), 38 | ("testReuqestPropertiesSince",testReuqestPropertiesSince), 39 | ("testRequestPropertiesStyle",testRequestPropertiesStyle), 40 | ("testRequestPropertiesView",testRequestPropertiesView),] 41 | }() 42 | 43 | var dbName: String? = nil 44 | var client: CouchDBClient? = nil 45 | 46 | override func setUp() { 47 | super.setUp() 48 | self.dbName = generateDBName() 49 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 50 | createDatabase(databaseName: dbName!, client: client!) 51 | 52 | let bulk = PutBulkDocsOperation(databaseName: dbName!, 53 | documents: self.createTestDocuments(count: 4)) 54 | client?.add(operation: bulk).waitUntilFinished() 55 | 56 | } 57 | 58 | override func tearDown() { 59 | deleteDatabase(databaseName: dbName!, client: client!) 60 | super.tearDown() 61 | } 62 | 63 | 64 | func testChangesFeedDefaults() throws { 65 | let expectation = self.expectation(description: "changes") 66 | 67 | var changeCount = 0 68 | let changes = GetChangesOperation(databaseName: dbName!, changeHandler: {(change) in 69 | changeCount += 1 70 | } 71 | ) { (response, info, error) in 72 | XCTAssertNotNil(response) 73 | XCTAssertNotNil(info) 74 | XCTAssertNil(error) 75 | if let info = info { 76 | XCTAssertEqual(200, info.statusCode) 77 | } 78 | 79 | expectation.fulfill() 80 | } 81 | 82 | client?.add(operation: changes) 83 | self.waitForExpectations(timeout: 10.0) 84 | XCTAssertEqual(4, changeCount) 85 | } 86 | 87 | func testChangesFeedMixedQuery() throws { 88 | let expectation = self.expectation(description: "changes") 89 | 90 | var docID: String? 91 | let allDocs = GetAllDocsOperation(databaseName: dbName!){ (response, info, error) in 92 | if let rows = response?["rows"] as? [[String: Any]], 93 | let first = rows.first { 94 | docID = first["key"] as? String 95 | } 96 | } 97 | client?.add(operation: allDocs).waitUntilFinished() 98 | var changeCount = 0 99 | let changes = GetChangesOperation(databaseName: dbName!, docIDs: [docID!], limit: 1, changeHandler: {(change) in 100 | changeCount += 1 101 | }){ 102 | (response, info, error) in 103 | XCTAssertNotNil(response) 104 | XCTAssertNotNil(info) 105 | XCTAssertNil(error) 106 | if let info = info { 107 | XCTAssertEqual(200, info.statusCode) 108 | } 109 | expectation.fulfill() 110 | } 111 | 112 | client?.add(operation: changes) 113 | self.waitForExpectations(timeout: 10.0) 114 | XCTAssertEqual(1, changeCount) 115 | 116 | 117 | } 118 | 119 | func testRequestPropertiesDocIds() throws { 120 | let changes = GetChangesOperation(databaseName: dbName!, docIDs: ["test"]) 121 | XCTAssert(changes.validate()) 122 | try changes.serialise() 123 | XCTAssertEqual("POST", changes.method) 124 | XCTAssertEqual(try JSONSerialization.data(withJSONObject: ["doc_ids": ["test"]]), changes.data) 125 | XCTAssertEqual("/\(dbName!)/_changes", changes.endpoint) 126 | XCTAssertEqual([:], changes.parameters) 127 | } 128 | 129 | func testRequestPropertiesConflicts() throws { 130 | let changes = GetChangesOperation(databaseName: dbName!, conflicts: true) 131 | XCTAssert(changes.validate()) 132 | try changes.serialise() 133 | XCTAssertEqual("GET", changes.method) 134 | XCTAssertEqual(["conflicts":"true"], changes.parameters) 135 | 136 | } 137 | 138 | func testRequestPropertiesDescending() throws { 139 | let changes = GetChangesOperation(databaseName: dbName!, descending: true) 140 | XCTAssert(changes.validate()) 141 | try changes.serialise() 142 | XCTAssertEqual("GET", changes.method) 143 | XCTAssertEqual(["descending":"true"], changes.parameters) 144 | } 145 | 146 | func testRequestPropertiesFilter() throws { 147 | let changes = GetChangesOperation(databaseName: dbName!, filter: "myfilter") 148 | XCTAssert(changes.validate()) 149 | try changes.serialise() 150 | XCTAssertEqual("GET", changes.method) 151 | XCTAssertEqual(["filter":"myfilter"], changes.parameters) 152 | } 153 | 154 | func testRequestPropertiesIncludeDocs() throws { 155 | let changes = GetChangesOperation(databaseName: dbName!, includeDocs: true) 156 | XCTAssert(changes.validate()) 157 | try changes.serialise() 158 | XCTAssertEqual("GET", changes.method) 159 | XCTAssertEqual(["include_docs":"true"], changes.parameters) 160 | } 161 | 162 | func testRequestPropertiesincludeAttachments() throws { 163 | let changes = GetChangesOperation(databaseName: dbName!, includeAttachments: true) 164 | XCTAssert(changes.validate()) 165 | try changes.serialise() 166 | XCTAssertEqual("GET", changes.method) 167 | XCTAssertEqual(["attachments":"true"], changes.parameters) 168 | } 169 | 170 | func testRequestPropertiesIncludeAttachmentInfo() throws { 171 | let changes = GetChangesOperation(databaseName: dbName!, includeAttachmentEncodingInformation: true) 172 | XCTAssert(changes.validate()) 173 | try changes.serialise() 174 | XCTAssertEqual("GET", changes.method) 175 | XCTAssertEqual(["att_encoding_info":"true"], changes.parameters) 176 | } 177 | 178 | func testRequestPropertiesLimit() throws { 179 | let changes = GetChangesOperation(databaseName: dbName!, limit: 4) 180 | XCTAssert(changes.validate()) 181 | try changes.serialise() 182 | XCTAssertEqual("GET", changes.method) 183 | XCTAssertEqual(["limit":"4"], changes.parameters) 184 | } 185 | 186 | func testReuqestPropertiesSince() throws { 187 | let changes = GetChangesOperation(databaseName: dbName!, since: 0) 188 | XCTAssert(changes.validate()) 189 | try changes.serialise() 190 | XCTAssertEqual("GET", changes.method) 191 | XCTAssertEqual(["since":"0"], changes.parameters) 192 | } 193 | 194 | func testRequestPropertiesStyle() throws { 195 | let changes = GetChangesOperation(databaseName: dbName!, style: .main) 196 | XCTAssert(changes.validate()) 197 | try changes.serialise() 198 | XCTAssertEqual("GET", changes.method) 199 | XCTAssertEqual(["style":"main_only"], changes.parameters) 200 | } 201 | 202 | func testRequestPropertiesView() throws { 203 | let changes = GetChangesOperation(databaseName: dbName!, view: "myView") 204 | XCTAssert(changes.validate()) 205 | try changes.serialise() 206 | XCTAssertEqual("GET", changes.method) 207 | XCTAssertEqual(["view":"myView"], changes.parameters) 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/GetDocumentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetDocumentTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Stefan Kruger on 04/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | class GetDocumentTests: XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testPutDocument", testPutDocument), 26 | ("testGetDocument", testGetDocument), 27 | ("testGetDocumentUsingDBAdd", testGetDocumentUsingDBAdd)] 28 | }() 29 | 30 | var dbName: String? = nil 31 | var client: CouchDBClient? = nil 32 | 33 | override func setUp() { 34 | super.setUp() 35 | 36 | dbName = generateDBName() 37 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 38 | createDatabase(databaseName: dbName!, client: client!) 39 | } 40 | 41 | override func tearDown() { 42 | deleteDatabase(databaseName: dbName!, client: client!) 43 | 44 | super.tearDown() 45 | 46 | print("Deleted database: \(dbName!)") 47 | } 48 | 49 | func testPutDocument() { 50 | let data = createTestDocuments(count: 1) 51 | 52 | let putDocumentExpectation = expectation(description: "put document") 53 | let client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 54 | 55 | let put = PutDocumentOperation(id: UUID().uuidString.lowercased(), 56 | body: data[0], 57 | databaseName: dbName!) { (response, httpInfo, error) in 58 | putDocumentExpectation.fulfill() 59 | XCTAssertNil(error) 60 | XCTAssertNotNil(response) 61 | if let httpInfo = httpInfo { 62 | XCTAssert(httpInfo.statusCode == 201 || httpInfo.statusCode == 202) 63 | } 64 | } 65 | 66 | client.add(operation: put) 67 | 68 | waitForExpectations(timeout: 10.0, handler: nil) 69 | } 70 | 71 | func testGetDocument() { 72 | let data = createTestDocuments(count: 1) 73 | let getDocumentExpectation = expectation(description: "get document") 74 | 75 | let putDocumentExpectation = self.expectation(description: "put document") 76 | let id = UUID().uuidString.lowercased() 77 | let put = PutDocumentOperation(id: id, 78 | body: data[0], 79 | databaseName: dbName!) { (response, httpInfo, operationError) in 80 | putDocumentExpectation.fulfill() 81 | XCTAssertEqual(id, response?["id"] as? String); 82 | XCTAssertNotNil(response?["rev"]) 83 | XCTAssertNil(operationError) 84 | XCTAssertNotNil(httpInfo) 85 | if let httpInfo = httpInfo { 86 | XCTAssertTrue(httpInfo.statusCode / 100 == 2) 87 | } 88 | 89 | let get = GetDocumentOperation(id: id, databaseName: self.dbName!) { (response, httpInfo, error) in 90 | getDocumentExpectation.fulfill() 91 | XCTAssertNil(error) 92 | XCTAssertNotNil(response) 93 | } 94 | 95 | self.client!.add(operation: get) 96 | }; 97 | 98 | let nsPut = Operation(couchOperation: put) 99 | client?.add(operation: nsPut) 100 | nsPut.waitUntilFinished() 101 | 102 | waitForExpectations(timeout: 10.0, handler: nil) 103 | } 104 | 105 | func testGetDocumentUsingDBAdd() { 106 | let data = createTestDocuments(count: 1) 107 | let getDocumentExpectation = expectation(description: "get document") 108 | let client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 109 | 110 | let id = UUID().uuidString.lowercased() 111 | let putDocumentExpectation = self.expectation(description: "put document") 112 | let put = PutDocumentOperation(id: id, 113 | body: data[0], 114 | databaseName: dbName!) { (response, httpInfo, operationError) in 115 | putDocumentExpectation.fulfill() 116 | XCTAssertEqual(id, response?["id"] as? String); 117 | XCTAssertNotNil(response?["rev"]) 118 | XCTAssertNil(operationError) 119 | XCTAssertNotNil(httpInfo) 120 | if let httpInfo = httpInfo { 121 | XCTAssertTrue(httpInfo.statusCode / 100 == 2) 122 | } 123 | 124 | }; 125 | 126 | let nsPut = Operation(couchOperation: put) 127 | client.add(operation: nsPut) 128 | nsPut.waitUntilFinished() 129 | 130 | let get = GetDocumentOperation(id: put.id!, databaseName: self.dbName!) { (response, httpInfo, error) in 131 | getDocumentExpectation.fulfill() 132 | XCTAssertNil(error) 133 | XCTAssertNotNil(response) 134 | XCTAssertNotNil(httpInfo) 135 | if let httpInfo = httpInfo { 136 | XCTAssertEqual(200, httpInfo.statusCode) 137 | } 138 | } 139 | 140 | client.add(operation: get) 141 | 142 | waitForExpectations(timeout: 10.0, handler: nil) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/PutAttachmentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 11/05/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | class PutAttachmentTests : XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testPutAttachment", testPutAttachment), 26 | ("testPutAttachmentHTTPOperationProperties",testPutAttachmentHTTPOperationProperties),] 27 | }() 28 | 29 | var dbName: String? = nil 30 | var client: CouchDBClient? = nil 31 | let docId: String = "PutAttachmentTests" 32 | var revId: String? 33 | 34 | override func setUp() { 35 | super.setUp() 36 | 37 | dbName = generateDBName() 38 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 39 | createDatabase(databaseName: dbName!, client: client!) 40 | let createDoc = PutDocumentOperation(id: docId, 41 | body: createTestDocuments(count: 1).first!, 42 | databaseName: dbName!) {[weak self] (response, info, error) in 43 | self?.revId = response?["rev"] as? String 44 | } 45 | let nsCreate = Operation(couchOperation: createDoc) 46 | client?.add(operation: nsCreate) 47 | nsCreate.waitUntilFinished() 48 | } 49 | 50 | override func tearDown() { 51 | deleteDatabase(databaseName: dbName!, client: client!) 52 | 53 | super.tearDown() 54 | } 55 | 56 | 57 | func testPutAttachment() { 58 | let putExpect = self.expectation(description: "put attachment") 59 | let put = self.createPutAttachmentOperation() 60 | {(response, info, error) in 61 | XCTAssertNil(error) 62 | XCTAssertNotNil(info) 63 | if let info = info { 64 | XCTAssert(info.statusCode / 100 == 2) 65 | } 66 | XCTAssertNotNil(response) 67 | 68 | putExpect.fulfill() 69 | } 70 | client?.add(operation: put) 71 | 72 | self.waitForExpectations(timeout: 10.0, handler: nil) 73 | 74 | } 75 | 76 | func testPutAttachmentHTTPOperationProperties(){ 77 | let put = self.createPutAttachmentOperation() 78 | XCTAssertTrue(put.validate()) 79 | XCTAssertEqual(["rev": revId!], put.parameters) 80 | XCTAssertEqual("/\(self.dbName!)/\(docId)/myAwesomeAttachment", put.endpoint) 81 | XCTAssertEqual("PUT", put.method) 82 | XCTAssertEqual(put.data, put.data) 83 | XCTAssertEqual(put.contentType, put.contentType) 84 | } 85 | 86 | func createPutAttachmentOperation(completionHandler: (([String : Any]?, HTTPInfo?, Error?) -> Void)? = nil) -> PutAttachmentOperation { 87 | let attachment = "This is my awesome essay attachment for my document" 88 | let put = PutAttachmentOperation(name: "myAwesomeAttachment", 89 | contentType: "text/plain", 90 | data: attachment.data(using: String.Encoding.utf8, allowLossyConversion: false)!, 91 | documentID: docId, 92 | revision: revId!, 93 | databaseName: dbName!, 94 | completionHandler: completionHandler 95 | ) 96 | return put 97 | } 98 | 99 | 100 | 101 | 102 | } 103 | 104 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/PutDocumentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 26/03/2016. 6 | // Copyright (c) 2016 IBM Corp. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Foundation 18 | import XCTest 19 | @testable import SwiftCloudant 20 | 21 | class PutDocumentTests: XCTestCase { 22 | 23 | static var allTests = { 24 | return [ 25 | ("testSaveDocument", testSaveDocument),] 26 | }() 27 | 28 | var client: CouchDBClient? = nil; 29 | var dbName: String? = nil 30 | 31 | override func setUp() { 32 | super.setUp() 33 | 34 | dbName = generateDBName() 35 | self.client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 36 | createDatabase(databaseName: dbName!, client: client!) 37 | } 38 | 39 | override func tearDown() { 40 | deleteDatabase(databaseName: dbName!, client: client!) 41 | super.tearDown() 42 | } 43 | 44 | func testSaveDocument() { 45 | let putExpectation = self.expectation(description: "Put Document expectation") 46 | let put = PutDocumentOperation(id: "Doc1", body:["hello": "world"], databaseName: dbName!) { (response, httpInfo, error) in 47 | putExpectation.fulfill() 48 | XCTAssertEqual("Doc1", response?["id"] as? String) 49 | XCTAssertNotNil(response?["rev"]) 50 | XCTAssertNotNil(httpInfo) 51 | if let httpInfo = httpInfo { 52 | XCTAssertEqual(2, httpInfo.statusCode / 100) 53 | } 54 | 55 | } 56 | client?.add(operation: put) 57 | 58 | self.waitForExpectations(timeout: 10) { (_) in 59 | 60 | } 61 | 62 | } 63 | 64 | func testSaveDocumentWithoutId() { 65 | let putExpectation = self.expectation(description: "Put Document expectation") 66 | let put = PutDocumentOperation(body:["hello": "world"], databaseName: dbName!) { (response, httpInfo, error) in 67 | putExpectation.fulfill() 68 | XCTAssertNotNil(response?["id"] as? String) 69 | XCTAssertNotNil(response?["rev"]) 70 | XCTAssertNotNil(httpInfo) 71 | if let httpInfo = httpInfo { 72 | XCTAssertEqual(2, httpInfo.statusCode / 100) 73 | } 74 | 75 | } 76 | client?.add(operation: put) 77 | 78 | self.waitForExpectations(timeout: 10) { (_) in 79 | 80 | } 81 | } 82 | 83 | func testOperationRequestMissingId() throws { 84 | let put = PutDocumentOperation(body:["hello": "world"], databaseName: dbName!) 85 | XCTAssertTrue(put.validate()) 86 | XCTAssertEqual(put.method, "POST") 87 | XCTAssertEqual(put.endpoint, "/\(self.dbName!)") 88 | XCTAssertEqual([:] as [String:String], put.parameters) 89 | try put.serialise() 90 | 91 | let json = try JSONSerialization.jsonObject(with: put.data!) as? [String:String] 92 | 93 | if let json = json { 94 | XCTAssertEqual(["hello":"world"], json) 95 | }else { 96 | XCTFail("JSON data was nil") 97 | } 98 | } 99 | 100 | func testOperationRequestWithId() throws { 101 | let put = PutDocumentOperation(id: "doc1", body:["hello": "world"], databaseName: dbName!) 102 | XCTAssertTrue(put.validate()) 103 | XCTAssertEqual(put.method, "PUT") 104 | XCTAssertEqual(put.endpoint, "/\(self.dbName!)/doc1") 105 | XCTAssertEqual([:] as [String:String], put.parameters) 106 | try put.serialise() 107 | 108 | let json = try JSONSerialization.jsonObject(with: put.data!) as? [String:String] 109 | 110 | if let json = json { 111 | XCTAssertEqual(["hello":"world"], json) 112 | }else { 113 | XCTFail("JSON data was nil") 114 | } 115 | } 116 | 117 | func testOperationRequestWithRev() throws { 118 | let put = PutDocumentOperation(id: "doc1", revision:"1-rev", body:["hello": "world"], databaseName: dbName!) 119 | XCTAssertTrue(put.validate()) 120 | XCTAssertEqual(put.method, "PUT") 121 | XCTAssertEqual(put.endpoint, "/\(self.dbName!)/doc1") 122 | XCTAssertEqual(["rev":"1-rev"] as [String:String], put.parameters) 123 | try put.serialise() 124 | 125 | let json = try JSONSerialization.jsonObject(with: put.data!) as? [String:String] 126 | 127 | if let json = json { 128 | XCTAssertEqual(["hello":"world"], json) 129 | }else { 130 | XCTFail("JSON data was nil") 131 | } 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/ReadAttachmentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadAttachmentTests.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 02/06/2016. 6 | // Copyright © 2016 IBM. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | 18 | import Foundation 19 | import XCTest 20 | @testable import SwiftCloudant 21 | 22 | class ReadAttachmentTests: XCTestCase { 23 | 24 | static var allTests = { 25 | return [ 26 | ("testReadAttachment", testReadAttachment), 27 | ("testReadAttachmentProperties", testReadAttachmentProperties)] 28 | }() 29 | 30 | var dbName: String? = nil 31 | var client: CouchDBClient? = nil 32 | let docId: String = "PutAttachmentTests" 33 | var revId: String? 34 | 35 | let attachment = "This is my awesome essay attachment for my document" 36 | let attachmentName = "myAwesomeAttachment" 37 | 38 | override func setUp() { 39 | super.setUp() 40 | 41 | dbName = generateDBName() 42 | client = CouchDBClient(url: URL(string: url)!, username: username, password: password) 43 | createDatabase(databaseName: dbName!, client: client!) 44 | let createDoc = PutDocumentOperation(id: docId, body: createTestDocuments(count: 1).first!, databaseName: dbName!) {[weak self] (response, info, error) in 45 | self?.revId = response?["rev"] as? String 46 | } 47 | client?.add(operation: createDoc).waitUntilFinished() 48 | 49 | 50 | let put = PutAttachmentOperation(name: attachmentName, contentType: "text/plain", data: attachment.data(using: String.Encoding.utf8, allowLossyConversion: false)!, 51 | documentID: docId, 52 | revision: revId!, 53 | databaseName: dbName! 54 | ) {[weak self] (response, info, error) in 55 | self?.revId = response?["rev"] as? String 56 | } 57 | client?.add(operation: put).waitUntilFinished() 58 | } 59 | 60 | override func tearDown() { 61 | deleteDatabase(databaseName: dbName!, client: client!) 62 | 63 | super.tearDown() 64 | } 65 | 66 | func testReadAttachment() { 67 | let expectation = self.expectation(description: "read attachment") 68 | let read = ReadAttachmentOperation(name: attachmentName, documentID: docId, databaseName: dbName!) {[weak self] (data, info, error) in 69 | XCTAssertNil(error) 70 | XCTAssertNotNil(info) 71 | if let info = info { 72 | XCTAssert(info.statusCode / 100 == 2) 73 | } 74 | 75 | XCTAssertNotNil(data) 76 | if let data = data { 77 | let attxt = String(data: data, encoding: .utf8) 78 | XCTAssertEqual(self?.attachment, attxt) 79 | } 80 | 81 | expectation.fulfill() 82 | } 83 | client?.add(operation: read) 84 | self.waitForExpectations(timeout: 10.0, handler: nil) 85 | } 86 | 87 | func testReadAttachmentProperties() { 88 | let read = ReadAttachmentOperation(name: attachmentName, documentID: docId, revision: revId, databaseName: dbName!) 89 | XCTAssert(read.validate()) 90 | XCTAssertEqual("GET", read.method) 91 | XCTAssertEqual("/\(dbName!)/\(docId)/\(attachmentName)", read.endpoint) 92 | XCTAssertEqual(["rev": revId!], read.parameters) 93 | XCTAssertNil(read.data) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Tests/SwiftCloudantTests/TestHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestHelpers.swift 3 | // SwiftCloudant 4 | // 5 | // Created by Rhys Short on 21/04/2016. 6 | // Copyright © 2016, 2019 IBM Corp. All rights reserved. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 9 | // except in compliance with the License. You may obtain a copy of the License at 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // Unless required by applicable law or agreed to in writing, software distributed under the 12 | // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 13 | // either express or implied. See the License for the specific language governing permissions 14 | // and limitations under the License. 15 | // 16 | 17 | import Dispatch 18 | import Foundation 19 | import XCTest 20 | @testable import SwiftCloudant 21 | 22 | // Extension to add functions for commonly used operations in tests. 23 | extension XCTestCase { 24 | 25 | 26 | var url: String { 27 | get { 28 | let defaultURL = "http://localhost:5984" 29 | if let url = TestSettings.getInstance().settings["SERVER_URL"] as? String { 30 | if url.isEmpty { 31 | return defaultURL 32 | } 33 | return url 34 | } else { 35 | NSLog("Failed to get URL from config, defaulting to localhost") 36 | return defaultURL 37 | } 38 | } 39 | } 40 | 41 | var username: String? { 42 | get { 43 | let username = TestSettings.getInstance().settings["SERVER_USER"] as? String 44 | if username != nil && username!.isEmpty { 45 | return nil; 46 | } else { 47 | return username 48 | } 49 | } 50 | } 51 | 52 | var password: String? { 53 | get { 54 | let password = TestSettings.getInstance().settings["SERVER_PASSWORD"] as? String 55 | if password != nil && password!.isEmpty { 56 | return nil 57 | } else { 58 | return password 59 | } 60 | } 61 | 62 | } 63 | 64 | func createTestDocuments(count: Int) -> [[String: Any]] { 65 | var docs = [[String: Any]]() 66 | for _ in 1 ... count { 67 | docs.append(["data": NSUUID().uuidString.lowercased()]) 68 | } 69 | 70 | return docs 71 | } 72 | 73 | func generateDBName() -> String { 74 | return "a-\(NSUUID().uuidString.lowercased())" 75 | } 76 | 77 | func createDatabase(databaseName: String, client: CouchDBClient) -> Void { 78 | let create = CreateDatabaseOperation(name: databaseName) { (response, httpInfo, error) in 79 | XCTAssertNotNil(httpInfo) 80 | if let httpInfo = httpInfo { 81 | XCTAssert(httpInfo.statusCode / 100 == 2) 82 | } 83 | XCTAssertNil(error) 84 | } 85 | let nsOperation = Operation(couchOperation: create) 86 | client.add(operation: nsOperation) 87 | nsOperation.waitUntilFinished() 88 | } 89 | 90 | func deleteDatabase(databaseName: String, client: CouchDBClient) -> Void { 91 | let delete = DeleteDatabaseOperation(name: databaseName) { (response, httpInfo, error) in 92 | XCTAssertNotNil(httpInfo) 93 | if let httpInfo = httpInfo { 94 | XCTAssert(httpInfo.statusCode / 100 == 2) 95 | } 96 | XCTAssertNil(error) 97 | } 98 | client.add(operation: delete).waitUntilFinished() 99 | } 100 | 101 | func simulateCreatedResponseFor(operation: CouchOperation, jsonResponse: JSONResponse = ["ok": true, "rev": "1-thisisarevision"]) { 102 | simulateExecutionOf(operation: operation, httpResponse: HTTPInfo(statusCode: 201, headers: [:]), response: jsonResponse) 103 | } 104 | 105 | func simulateOkResponseFor(operation: CouchOperation, jsonResponse: JSONResponse = ["ok" : true]) { 106 | simulateExecutionOf(operation: operation, httpResponse: HTTPInfo(statusCode: 200, headers: [:]), response: jsonResponse) 107 | } 108 | 109 | func simulateExecutionOf(operation: CouchOperation, httpResponse: HTTPInfo, response: JSONResponse) { 110 | do { 111 | let data = try JSONSerialization.data(withJSONObject: response.json) 112 | let httpInfo = HTTPInfo(statusCode: 200, headers: [:]) 113 | self.simulateExecutionOf(operation: operation, httpResponse: httpInfo, response: data) 114 | } catch { 115 | NSLog("Failed to seralise json, aborting simulation") 116 | } 117 | 118 | } 119 | 120 | func simulateExecutionOf(operation: CouchOperation, httpResponse: HTTPInfo, response: Data) { 121 | DispatchQueue.main.async { 122 | 123 | do { 124 | if !operation.validate() { 125 | operation.callCompletionHandler(error: Operation.Error.validationFailed) 126 | } 127 | 128 | try operation.serialise() 129 | 130 | operation.processResponse(data: response, httpInfo: httpResponse, error: nil) 131 | } catch { 132 | operation.callCompletionHandler(error: error) 133 | } 134 | 135 | 136 | } 137 | } 138 | 139 | } 140 | 141 | struct JSONResponse: ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral { 142 | 143 | let array: [Any]? 144 | let dictionary: [String:Any]? 145 | 146 | init(arrayLiteral:Any...){ 147 | 148 | array = Array(arrayLiteral: arrayLiteral) 149 | dictionary = nil 150 | } 151 | 152 | init(dictionaryLiteral elements: (String, Any)...){ 153 | array = nil 154 | var mutableDict: [String:Any] = [:] 155 | for (key, value) in elements { 156 | mutableDict[key] = value 157 | } 158 | dictionary = mutableDict 159 | } 160 | 161 | init(dictionary: [String: Any]){ 162 | self.dictionary = dictionary 163 | self.array = nil 164 | } 165 | 166 | var json: Any { 167 | get { 168 | if let array = array { 169 | return array 170 | } 171 | 172 | if let dictionary = dictionary { 173 | return dictionary 174 | } 175 | 176 | return Dictionary() // return empty dict just in case all else fails. 177 | } 178 | } 179 | 180 | 181 | } 182 | 183 | extension Array where Element: NSURLQueryItem { 184 | 185 | /** 186 | Checks if this array is equivalent to another array. For an array to be equivalent to another 187 | they need to contain the same elements, however they do __not__ need to be in the same order. 188 | 189 | - parameter to: the `[NSURLQueryItem]` to compare to. 190 | */ 191 | func isEquivalent(to: [NSURLQueryItem]) -> Bool { 192 | var to = to 193 | if (self.count != to.count) { 194 | return false 195 | } 196 | 197 | for queryItem in self { 198 | if let index = to.firstIndex(of: queryItem) { 199 | to.remove(at: index) 200 | } else { 201 | return false 202 | } 203 | } 204 | return to.count == 0 205 | } 206 | } 207 | 208 | class TestSettings { 209 | 210 | fileprivate let settings: [String: Any]; 211 | private static var instance: TestSettings? 212 | 213 | private init() { 214 | // load the settings from the evnvironment this is a workaround while we cannot 215 | // specify files to be part of the bundle for loading test cases. 216 | let keys = ["SERVER_URL", "SERVER_USER", "SERVER_PASSWORD"] 217 | let filtered = ProcessInfo.processInfo.environment.filter { 218 | return keys.contains($0.key) 219 | } 220 | 221 | var tempSettings: [String:Any] = [:] 222 | for (key, value) in filtered { 223 | tempSettings[key] = value 224 | } 225 | self.settings = tempSettings 226 | 227 | // uncomment this when it is possible to bundle files with SPM. 228 | // let bundle = Bundle(for: TestSettings.self) 229 | // 230 | // let testSettingsPath = bundle.path(forResource: "TestSettings", ofType: "plist") 231 | // 232 | // if let testSettingsPath = testSettingsPath, 233 | // let settingsDict = NSDictionary(contentsOfFile: testSettingsPath) as? [String: Any] { 234 | // settings = settingsDict 235 | // } else { 236 | // settings = [:] 237 | // } 238 | } 239 | 240 | class func getInstance() -> TestSettings { 241 | if let instance = instance { 242 | return instance 243 | } else { 244 | instance = TestSettings() 245 | return instance! 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Tests/TestSettings.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SERVER_URL 6 | 7 | SERVER_USER 8 | 9 | SERVER_PASSWORD 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /cloudant-service.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | swift-cloudant: 4 | environment: 5 | - SERVER_URL 6 | - SERVER_USER 7 | - SERVER_PASSWORD 8 | -------------------------------------------------------------------------------- /couchdb.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | couchdb: 4 | image: apache/couchdb:2 5 | swift-cloudant: 6 | depends_on: 7 | - couchdb 8 | environment: 9 | - SERVER_URL=http://couchdb:5984 10 | -------------------------------------------------------------------------------- /doc/Architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | SwiftCloudant follows a layered layout for classes. The foundation being HTTP 4 | classes, which the operations build on. 5 | 6 | ## HTTP 7 | 8 | The main class for HTTP is `InterceptableSession`. `InterceptableSession` initially 9 | provided support for the interceptor API that can be found in the other Cloudant 10 | Client libs. However the Interceptor API was removed in favour of building 11 | in support for Cookie Authentication and `429 - Too Many Requests` built in 12 | directly. Most of the classes such as `OperationRequestBuilder` effectively 13 | do a small amount of work and the classes are well documented. 14 | 15 | ### InterceptableSession 16 | 17 | #### 429 support 18 | 19 | 429 Support is built in using `Dispatch` (libdispatch in objc). It uses a custom 20 | extension on `DispatchTimeInterval` to provide calculations for the time delay. 21 | The queue on which the retry tasks are dispatched is the `delegate` queue. Unlike 22 | 403 Forbidden responses retries occur when the initial response is received from 23 | the server. This is because it is possible to stop loading the response from 24 | the server if we know the request has already failed. 25 | 26 | #### Quirks 27 | 28 | There are some quirks with the implementation, since it is not possible to use `self` 29 | as a parameter to another `init` method while in the init for `self` because the underlying 30 | `Foundation.URLSession` (`NSURLSession` in objc) is created in a `lazy` property. 31 | This means care has to be taken when the `InterceptableSession` is being deallocated. 32 | 33 | ### URLSessionTask 34 | 35 | `URLSessionTask` provides a slimmed down, `Foundation.URLSessionTask` like interface, 36 | it is mostly a state store for the inflight request that it encapsulates for 37 | the `URLSession` 38 | 39 | ## Operations 40 | 41 | All SwiftCloudant API calls are operations, and each operation class provides 42 | information to the underlying machinery with a well defined set of APIs. The 43 | `Operation` class is a custom implementation of `Foundation.Operation`, it makes 44 | it possible to run a `CouchOperation` on an `Foundation.OperationQueue`. `Operation` 45 | transforms the API exposed to each of the operations to the API expected by the HTTP classes. 46 | Such as converting an `[String:String]` of parameters to `[URLQueryItems]`. 47 | 48 | ### CouchOperation 49 | 50 | `CouchOperation` lays out the API for classes to define an API to call on the server, 51 | however it only provides basic functionality to provide the maximum flexibility 52 | to conforming classes. However this makes it more difficult to implement new APIs 53 | when there is not much work to be performed. For these cases the `JSONOperation` 54 | and `DataOperation` come in. 55 | 56 | `JSONOperation` makes it easier to deserialise JSON responses from the server. 57 | It defaults the method `processResponse(data:httpInfo:error:)` to correctly 58 | process the response data into a deserialised JSON structure. The structure type 59 | (eg `[String:Any]` or `[Any]`) is defined by the conforming class to match expected 60 | type when the API returns **correctly**. The response will be cast to this type 61 | using the `as?` so if a 403 is returned from `_all_dbs` the response **will** be 62 | `nil` this is expected. The response object is only guaranteed to be present when 63 | the request was successful. 64 | 65 | `DataOperation` makes it possible to interact with Attachments, and any other 66 | operation which does not return JSON as the response. The default implementation 67 | of `processResponse(data:httpInfo:error:)` calls the completion handler 68 | with the expected error semantics. 69 | 70 | Anyone implementing a new API for SwiftCloudant **should** conform to either 71 | `JSONOperation` or `DataOperation`. 72 | 73 | ### Operation API Call Order 74 | Classes which implement `CouchOperation`, `JSONOperation` or `DataOperation` have 75 | a guaranteed order in which the APIs **will** be called to make it possible 76 | to do processing of Data into the expected form for the CouchDB endpoint. 77 | That order is: 78 | 79 | 1. `validate()` 80 | - `callCompletionHandler` (if validation fails) 81 | 1. `serialise` 82 | - `callCompletionHandler` (if serialise throws) 83 | 1. `endpoint` 84 | 1. `parameters` 85 | 1. `method` 86 | 1. `data` 87 | 1. `httpContentType` (if `data` is not `nil`) 88 | 1. `processResponse(data:httpInfo:error)` 89 | 1. `callCompletionHandler` 90 | - should then be called by the operation during processing. 91 | 92 | 93 | ## Testing 94 | 95 | There are two types of testing for the client, Full End to End testing, and simulated execution. 96 | 97 | ### Full End to End 98 | 99 | Full End to End testing goes through the entire machinery of the library, making 100 | real requests to the server. If there is not a CouchDB server that can be contacted 101 | the tests tend to fail with a crash due to set-up methods forcibly unwrapping 102 | test methods. 103 | 104 | ### Simulated Execution 105 | The `TestHelpers.swift` file contains an extension to `XCTestCase` which adds 106 | `simulateXXXXResponse` which will simulate the API calls required to make an 107 | operation and will trigger a canned response without hitting the network. This 108 | is useful for running tests to make sure the `processResponse(data:httpInfo:error)` 109 | method processes responses correctly. 110 | 111 | There is a slight quirk that it does not call the properties used to create the `HTTP` 112 | request, only the method: 113 | 114 | - validate 115 | - serialise 116 | - processResponse 117 | - callCompletionHandler 118 | 119 | are called when using the simulation. 120 | 121 | The simulated operations are executed on the Main queue asynchronously so it is 122 | possible to use expectations as if it was an End to End Test. 123 | 124 | Since the tests do not hit the network, any test of operations that would 125 | ordinarily hit the network must check the expected request payloads and HTTP 126 | properties. The Query tests provide an example that performs these required checks. 127 | -------------------------------------------------------------------------------- /swift.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | swift-cloudant: 4 | command: bash -c "cd swift-cloudant && swift build && swift test" 5 | image: swift:5.0 6 | volumes: 7 | - ./:/swift-cloudant 8 | --------------------------------------------------------------------------------