├── .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 | [](http://cocoadocs.org/docsets/SwiftCloudant)
14 | [](http://cocoadocs.org/docsets/SwiftCloudant)
15 | [](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 |
--------------------------------------------------------------------------------