From e95d8ea2c67b77c5137b5f4966d7f6a8d8ac1d2a Mon Sep 17 00:00:00 2001 From: Kaitlin Mahar Date: Tue, 26 May 2020 20:43:53 -0400 Subject: [PATCH 1/4] Update guides --- Guides/BSON.md | 214 ++++++++++++++++++++++++++------------- Guides/Change-Streams.md | 24 ++--- Guides/TLS.md | 20 ++-- README.md | 12 +-- 4 files changed, 174 insertions(+), 96 deletions(-) diff --git a/Guides/BSON.md b/Guides/BSON.md index 20d5c4579..f88bbf89d 100644 --- a/Guides/BSON.md +++ b/Guides/BSON.md @@ -2,7 +2,7 @@ MongoDB stores and transmits data in the form of [BSON](bsonspec.org) documents, and MongoSwift provides a libary that can be used to work with such documents. The following is an example of some of the functionality provided as part of that: ```swift // Document construction. -let doc: Document = [ +let doc: BSONDocument = [ "name": "Bob", "occupation": "Software Engineer", "projects": [ @@ -19,7 +19,7 @@ struct Person: Codable { let occupation: String } print(try BSONDecoder().decode(Person.self, from: doc)) // Person(name: "Bob", occupation: "Software Engineer") -print(try BSONEncoder().encode(Person(name: "Ted", occupation: "Janitor")) // { "name": "Ted", "occupation": "Janitor" } +print(try BSONEncoder().encode(Person(name: "Ted", occupation: "Janitor"))) // { "name": "Ted", "occupation": "Janitor" } ``` This guide will serve as an overview of various parts of the BSON library. To learn more specifics and cover the entirety of the API surface, please refer to the driver's [API reference](https://mongodb.github.io/mongo-swift-driver/). @@ -28,7 +28,7 @@ BSON values have many possible types, ranging from simple 32-bit integers to doc ```swift public enum BSON { case .null, - case .document(Document) + case .document(BSONDocument) case .double(Double) case .datetime(Date) case .string(String) @@ -48,7 +48,7 @@ let array: BSON = ["1", true, 5.5] // .array([.string("1"), .bool(true), .double All other cases must be initialized directly: ```swift let date = BSON.datetime(Date()) -let objectId = BSON.objectId(ObjectId()) +let objectId = BSON.objectID() // ...rest of cases... ``` ### Unwrapping a `BSON` @@ -58,7 +58,7 @@ func foo(x: BSON, y: BSON) throws { switch x { case let .int32(int32): print("got an Int32: \(int32)") - case let .objectId(oid): + case let .objectID(oid): print("got an objectId: \(oid.hex)") default: print("got something else") @@ -71,24 +71,24 @@ func foo(x: BSON, y: BSON) throws { ``` While these methods are good for branching, sometimes it is useful to get just the value (e.g. for optional chaining, passing as a parameter, or returning from a function). For those cases, `BSON` has computed properties for each case that wraps a type. These properties will return `nil` unless the underlying BSON value is an exact match to the return type of the property. ```swift -func foo(x: BSON) -> [Document] { - guard let documents = x.arrayValue?.compactMap { $0.documentValue } else { +func foo(x: BSON) -> [BSONDocument] { + guard let documents = x.arrayValue?.compactMap({ $0.documentValue }) else { print("x is not an array") - return + return [] } return documents } -print(.int64(5).int32Value) // nil -print(.int32(5).int32Value) // Int32(5) -print(.double(5).int64Value) // nil -print(.double(5).doubleValue) // Double(5.0) +print(BSON.int64(5).int32Value) // nil +print(BSON.int32(5).int32Value) // Int32(5) +print(BSON.double(5).int64Value) // nil +print(BSON.double(5).doubleValue) // Double(5.0) ``` ### Converting a `BSON` In some cases, especially when dealing with numbers, it may make sense to coerce a `BSON`'s wrapped value into a similar one. For those situations, there are several conversion methods defined on `BSON` that will unwrap the underlying value and attempt to convert it to the desired type. If that conversion would be lossless, a non-`nil` value is returned. ```swift func foo(x: BSON, y: BSON) throws -> Int { - guard let x = x.asInt(), let y = y.asInt() else { - throw UserError.invalidArugmentError(message: "provide two integer types") + guard let x = x.toInt(), let y = y.toInt() else { + throw InvalidArgumentError(message: "provide two integer types") } return x + y } @@ -97,9 +97,8 @@ try foo(x: 5, y: 5) // 10 try foo(x: 5.0, y: 5.0) // 10 try foo(x: .int32(5), y: .int64(5)) // 10 try foo(x: 5.01, y: 5) // error -try foo(x: "5", y: 5) // error ``` -There are similar conversion methods for the other types, namely `asInt32()`, `asDouble()`, `asInt64()`, and `asDecimal128()`. +There are similar conversion methods for the other types, namely `toInt32()`, `toDouble()`, `toInt64()`, and `toDecimal128()`. ### Using a `BSON` value `BSON` conforms to a number of useful Foundation protocols, namely `Codable`, `Equatable`, and `Hashable`. This allows them to be compared, encoded/decoded, and used as keys in maps: @@ -119,40 +118,39 @@ let map: [BSON: String] = [ "x": "string", false: "bool", [1, 2, 3]: "array", - .objectId(ObjectId()): "oid", + .objectID(): "oid", .null: "null", .maxKey: "maxKey" ] ``` ## Documents -BSON documents are the top-level structures that contain the aforementioned BSON values, and they are also BSON values themselves. The driver defines the `Document` struct to model this specific BSON type. +BSON documents are the top-level structures that contain the aforementioned BSON values, and they are also BSON values themselves. The driver defines the `BSONDocument` struct to model this specific BSON type. ### Initializing documents -Like `BSON`, `Document` can also be initialized by a dictionary literal. The elements within the literal must be `BSON`s, so further literals can be embedded within the top level literal definition: +Like `BSON`, `BSONDocument` can also be initialized by a dictionary literal. The elements within the literal must be `BSON`s, so further literals can be embedded within the top level literal definition: ```swift -let x: Document = [ +let x: BSONDocument = [ "x": 5, - "y": 5.5 + "y": 5.5, "z": [ "a": [1, true, .datetime(Date())] ] ] ``` -Documents can also be initialized directly by passing in a `Data` containing raw BSON bytes. If the bytes do not constitute valid BSON, an error is thrown. +Documents can also be initialized directly by passing in a `Data` containing raw BSON bytes: ```swift -try Document(fromBSON: Data(hexString: "0F00000010246B6579002A00000000")) // { "$key": 42 } -try Document(fromBSON: Data(hexString: "1200000002666F6F0004000000626172")) // error +try BSONDocument(fromBSON: Data(...)) ``` Documents may be initialized from an [extended JSON](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) string as well: ```swift -try Document(fromJSON: "{ \"x\": true }") // { "x": true } -try Document(fromJSON: "{ x: false }}}") // error +try BSONDocument(fromJSON: "{ \"x\": true }") // { "x": true } +try BSONDocument(fromJSON: "{ x: false }}}") // error ``` ### Using documents -Documents define the interface in which an application communicates with a MongoDB deployment. For that reason, `Document` has been fitted with functionality to make it both powerful and ergonomic to use for developers. -#### Reading / writing to `Document` -`Document` conforms to [`Collection`](https://developer.apple.com/documentation/swift/collection), which allows for easy reading and writing of elements via the subscript operator. On `Document`, this operator returns and accepts a `BSON?`: +Documents define the interface in which an application communicates with a MongoDB deployment. For that reason, `BSONDocument` has been fitted with functionality to make it both powerful and ergonomic to use for developers. +#### Reading / writing to `BSONDocument` +`BSONDocument` conforms to [`Collection`](https://developer.apple.com/documentation/swift/collection), which allows for easy reading and writing of elements via the subscript operator. On `BSONDocument`, this operator returns and accepts a `BSON?`: ```swift -var doc: Document = ["x": 1] +var doc: BSONDocument = ["x": 1] print(doc["x"]) // .int64(1) doc["x"] = ["y": .null] print(doc["x"]) // .document({ "y": null }) @@ -160,9 +158,9 @@ doc["x"] = nil print(doc["x"]) // nil print(doc) // { } ``` -`Document` also has the `@dynamicMemberLookup` attribute, meaning it's values can be accessed directly as if they were properties on `Document`: +`BSONDocument` also has the `@dynamicMemberLookup` attribute, meaning it's values can be accessed directly as if they were properties on `BSONDocument`: ```swift -var doc: Document = ["x": 1] +var doc: BSONDocument = ["x": 1] print(doc.x) // .int64(1) doc.x = ["y": .null] print(doc.x) // .document({ "y": null }) @@ -170,61 +168,65 @@ doc.x = nil print(doc.x) // nil print(doc) // { } ``` -`Document` also conforms to [`Sequence`](https://developer.apple.com/documentation/swift/sequence), which allows it to be iterated over: +`BSONDocument` also conforms to [`Sequence`](https://developer.apple.com/documentation/swift/sequence), which allows it to be iterated over: ```swift -for (k, v) in Document { +for (k, v) in doc { print("\(k) = \(v)") } ``` Conforming to `Sequence` also gives a number of useful methods from the functional programming world, such as `map` or `allSatisfy`: ```swift -let allEvens = doc.allSatisfy { _, v in v.asInt() ?? 1 % 2 == 0 } -let squares = doc.map { k, v in v.asInt()! * v.asInt()! } +let allEvens = doc.allSatisfy { _, v in v.toInt() ?? 1 % 2 == 0 } +let squares = doc.map { k, v in v.toInt()! * v.toInt()! } ``` -See the documentation for `Sequence` for a full list of methods that `Document` implements as part of this. +See the documentation for `Sequence` for a full list of methods that `BSONDocument` implements as part of this. -In addition to those protocol conformances, there are a few one-off helpers implemented on `Document` such as `filter` (that returns a `Document`) and `mapValues` (also returns a `Document`): +In addition to those protocol conformances, there are a few one-off helpers implemented on `BSONDocument` such as `filter` (that returns a `BSONDocument`) and `mapValues` (also returns a `BSONDocument`): ```swift -let doc = ["_id": .objectId(ObjectId()), "numCats": 2, "numDollars": 1.56, "numPhones": 1] -doc.filter { k, v in k.contains("num") && v.asInt() != nil }.mapValues { v in .int64(v.asInt64()! + 5) } // { "numCats": 7, "numPhones": 6 } +let doc: BSONDocument = ["_id": .objectID(), "numCats": 2, "numDollars": 1.56, "numPhones": 1] +doc.filter { k, v in k.contains("num") && v.toInt() != nil }.mapValues { v in .int64(v.toInt64()! + 5) } // { "numCats": 7, "numPhones": 6 } ``` -See the driver's documentation for a full listing of `Document`'s public API. -## `Codable` and `Document` +See the driver's documentation for a full listing of `BSONDocument`'s public API. +## `Codable` and `BSONDocument` [`Codable`](https://developer.apple.com/documentation/swift/codable) is a protocol defined in Foundation that allows for ergonomic conversion between various serialization schemes and Swift data types. As part of the BSON library, MongoSwift defines both `BSONEncoder` and `BSONDecoder` to facilitate this serialization and deserialization to and from BSON via `Codable`. This allows applications to work with BSON documents in a type-safe way, and it removes much of the runtime key presence and type checking required when working with raw documents. It is reccommended that users leverage `Codable` wherever possible in their applications that use the driver instead of accessing documents directly. For example, here is an function written using raw documents: ```swift -let person = [ +let person: BSONDocument = [ "name": "Bob", - "occupation": "Software Engineer" + "occupation": "Software Engineer", "projects": [ - ["id": 1, title: "Server Side Swift Application"], - ["id": 76, title: "Write documentation"], + ["id": 1, "title": "Server Side Swift Application"], + ["id": 76, "title": "Write documentation"], ] ] -func prettyPrint(doc: Document) throws { +func prettyPrint(doc: BSONDocument) { guard let name = doc["name"]?.stringValue else { - throw argumentError(message: "missing name") + print("missing name") + return } print("Name: \(name)") guard let occupation = doc["occupation"]?.stringValue else { - throw argumentError(message: "missing occupation") + print("missing occupation") + return } print("Occupation: \(occupation)") - guard let projects = doc["projects"]?.arrayValue.compactMap { $0.documentValue } else { - throw argumentError(message: "missing projects") + guard let projects = doc["projects"]?.arrayValue?.compactMap({ $0.documentValue }) else { + print("missing projects") + return } print("Projects:") for project in projects { guard let title = project["title"] else { - throw argumentError(message: "missing title") + print("missing title") + return } print(title) } } ``` -Due to the flexible nature of `Document`, a number of checks have to be put into the body of the function. This clutters the actual function's logic and requires a lot of boilerplate code. Now, consider the following function which does the same thing but is written leveraging `Codable`: +Due to the flexible nature of `BSONDocument`, a number of checks have to be put into the body of the function. This clutters the actual function's logic and requires a lot of boilerplate code. Now, consider the following function which does the same thing but is written leveraging `Codable`: ```swift struct Project: Codable { let id: BSON @@ -237,7 +239,7 @@ struct Person: Codable { let projects: [Project] } -func prettyPrint(doc: Document) throws { +func prettyPrint(doc: BSONDocument) throws { let person = try BSONDecoder().decode(Person.self, from: doc) print("Name: \(person.name)") print("Occupation: \(person.occupation)") @@ -250,27 +252,103 @@ func prettyPrint(doc: Document) throws { In this version, the definition of the data type and the logic of the function are defined completely separately, and it leads to far more readable and concise versions of both. ### `Codable` in MongoSwift -There are a number of ways for users to leverage `Codable` via driver's API. One such example is through `MongoCollection`. By default, `MongoDatabase.collection` returns a `MongoCollection`. Any `find` or `aggregate` method invocation on that returned collection would then return a `MongoCursor`, which when iterated returns a `Document?`: +There are a number of ways for users to leverage `Codable` via driver's API. One such example is through `MongoCollection`. By default, `MongoDatabase.collection` returns a `MongoCollection`. Any `find` or `aggregate` method invocation on that returned collection would then return a `MongoCursor`, which when iterated returns a `BSONDocument?`: ```swift -let collection = db.collection("person", withType: Person.self) +let collection = db.collection("person") + +// asynchronous API +collection.find(["occupation": "Software Engineer"]).flatMap { cursor in + cursor.toArray() +}.map { docs in + docs.forEach { person in + print(person["name"] ?? "nil") + } +} +collection.insertOne(["name": "New Hire", "occupation": "Doctor", "projects": []]).whenSuccess { _ in /* ... */ } + +// synchronous API for person in try collection.find(["occupation": "Software Engineer"]) { - print(person["name"] ?? "nil") + print(try person.get()["name"] ?? "nil") } -try collection.insert(["name": "New Hire", "occupation": "Doctor", "projects": []) +try collection.insertOne(["name": "New Hire", "occupation": "Doctor", "projects": []]) ``` -However, if the schema of the collection is known, `Codable` structs can be used to work with the data in a more type safe way. To facilitate this, the alternate `collection(name:asType)` method on `MongoDatabase`, which accepts a `Codable` generic type, can be used. The provided type defines the model for all the documents in that collection, and any cursor returned from `find` or `aggregate` on that collection will be generic over that type instead of `Document`. Iterating such cursors will automatically decode the result documents to the generic type specified. Similarly, `insert` on that collection will accept an instance of that type. +However, if the schema of the collection is known, `Codable` structs can be used to work with the data in a more type safe way. To facilitate this, the alternate `collection(name:asType)` method on `MongoDatabase`, which accepts a `Codable` generic type, can be used. The provided type defines the model for all the documents in that collection, and any cursor returned from `find` or `aggregate` on that collection will be generic over that type instead of `BSONDocument`. Iterating such cursors will automatically decode the result documents to the generic type specified. Similarly, `insert` on that collection will accept an instance of that type. ```swift +struct Project: Codable { + let id: BSON + let title: String +} + +struct Person: Codable { + let name: String + let occupation: String + let projects: [Project] +} + let collection = db.collection("person", withType: Person.self) + +// asynchronous API +collection.find(["occupation": "Software Engineer"]).flatMap { cursor in + cursor.toArray() +}.map { docs in + docs.forEach { person in + print(person.name) + } +} +collection.insertOne(Person(name: "New Hire", occupation: "Doctor", projects: [])).whenSuccess { _ in /* ... */ } + +// synchronous API for person in try collection.find(["occupation": "Software Engineer"]) { - print(person.name) + print(try person.get().name) } -try collection.insert(Person(name: "New Hire", occupation: "Doctor", projects: []) +try collection.insertOne(Person(name: "New Hire", occupation: "Doctor", projects: [])) ``` This allows applications that interact with the database to use well-defined Swift types, resulting in clearer and less error-prone code. Similar things can be done with `ChangeStream` and `ChangeStreamEvent`. -## Migrating from the old BSON API +## Migration Guides +### Migrating from the 0.2.0 through 1.0.0-rc1 API to the 1.0 API + +#### Name Changes +In order to avoid naming conflicts with other libraries, we have prefixed all BSON types that we own with `BSON`: +* `Document` is now `BSONDocument` +* `Binary` is now `BSONBinary` +* `ObjectId` is now `BSONObjectID` +* `RegularExpression` is now `BSONRegularExpression` +* `DBPointer` is now `BSONDBPointer` +* `Symbol` is now `BSONSymbol` +* `Code` is now `BSONCode` +* `CodeWithScope` is now `BSONCodeWithScope` +* `Timestamp` is now `BSONTimestamp` +* `Decimal128` is now `BSONDecimal128` + +#### ObjectID Updates + +Note that the `D` in `ID` is now capitalized in both the type name `BSONObjectID` and in the `BSON` enum case `.objectID`. We have also provided a default value of `BSONObjectID()` for the `BSON.objectID` case, which simplifies embedding `BSONObjectID`s in `BSONDocument` literals in cases where you are inserting a new ID: +```swift +let doc: Document = ["_id": .objectID(ObjectID())] +let doc: BSONDocument = ["_id": .objectID()] // new +``` + +If you need to use an existing `BSONObjectID` you can still provide one: +```swift +let doc: BSONDocument = ["_id": .objectID(myID)] +``` + +#### Conversion APIs +The BSON library contains a number of methods for converting between types. Many of these are defined on `BSON` and were previously named `asX()`, e.g. `asInt32()`. These are now all named `toX()` instead. + +Additionally, the driver previously supported conversions from `Binary` -> `UUID` and `RegularExpression` -> `NSRegularExpression` through initializers defined in extensions of the type being converted to. For discoverability, this logic has now been moved into `toX()` methods on the source types instead: +```swift +let regExp = try NSRegularExpression(from: myBSONRegExp) // old +let regExp = try myBSONRegExp.toNSRegularExpression() // new + +let uuid = try UUID(from: myBSONBinary) // old +let uuid = try myBSONBinary.toUUID() // new +``` + +### Migrating from the 0.0.1-0.1.3 API to the 0.2.0 BSON API In version 0.2.0 of `MongoSwift`, the public API for using BSON values was changed dramatically. This section will describe the process for migrating from the old API (BSON API v1) to this new one (BSON API v2). -### Overview of BSON API v1 +#### Overview of BSON API v1 The previous API was based around the `BSONValue` protocol. Types that conformed to this protocol could be inserted to or read out of `Document` and could aslo be used in `Document` literals. The protocol was also used in various places around the driver as an existential type or conformance requirement. A related protocol, `BSONNumber`, inherited from `BSONValue` and provided some numeric conversion helpers for the various BSON number types (e.g. `Double`, `Int32`, `Int`). ```swift var doc: Document = [ @@ -293,10 +371,10 @@ let x: Document = [ ] as Document ] ``` -### Required Updates +#### Required Updates In BSON API v2, `BSONNumber`, `BSONValue`, and `AnyBSONValue` no longer exist. They are all entirely replaced by the `BSON` enum. -#### Updating `BSONValue` references +##### Updating `BSONValue` references Anywhere in the driver that formerly accepted or returned a `BSONValue` will now accept or return a `BSON`. Wherever `BSONValue` is used as an existential value in your application, a `BSON` will probably work as a drop-in replacement. Any casts will need to be updated to call the appropriate helper property instead. ```swift @@ -335,7 +413,7 @@ func foo(x: BSON, y: BSON) { **Generic Requirement** Currently, there is no equivalent protocol in BSON API v2 to `BSONValue`, so if your application was using it as a generic requirement there is no alternative in the driver. You may have to implement your own similar protocol to achieve the same effect. If such a protocol would be useful to you, please [file a ticket on the driver's Jira project](https://github.com/mongodb/mongo-swift-driver#bugs--feature-requests). -#### Updating `BSONNumber` references +##### Updating `BSONNumber` references `BSON` should be a drop-in replacement for anywhere `BSONNumber` is used, except for as a generic requirement. One thing to note that `BSONNumber`'s properties (e.g. `.int32Value`) are _conversions_, whereas `BSON`'s are simple unwraps. The conversions on `BSON` are implemented as methods (e.g. `asInt32()`). ```swift @@ -369,7 +447,7 @@ func foo(doc: Document) -> Int? { return int * Int(otherInt) } ``` -#### Updating `AnyBSONValue` references +##### Updating `AnyBSONValue` references `BSON` should be able to serve as a complete replacement for `AnyBSONValue`. `Codable` usage: ```swift @@ -403,7 +481,7 @@ let a: [BSON, Int] = ["hello": 4, .objectId(ObjectId()): 26] print(a[true] ?? "nil") ``` -#### Updating `Document` literals +##### Updating `Document` literals `BSON` can be expressed by a dictionary literal, string literal, integer literal, float literal, boolean literal, and array literal, so document literals consisting of those literals can largely be left alone. All the other types that formerly conformed to `BSONValue` will need to have their cases explicitly constructed. The cast to `Document` will no longer be required for subdocuments and will need to be removed. All runtime variables will also need to have their cases explicitly constructed whereas in BSON API v1 they could just be inserted directly. ```swift // BSON API v1 diff --git a/Guides/Change-Streams.md b/Guides/Change-Streams.md index 83d947c7a..df58146cb 100644 --- a/Guides/Change-Streams.md +++ b/Guides/Change-Streams.md @@ -12,9 +12,9 @@ let elg = MultiThreadedEventLoopGroup(numberOfThreads: 4) let client = try MongoClient(using: elg) let inventory = client.db("example").collection("inventory") -inventory.watch().flatMap { stream in // a `ChangeStream>` +inventory.watch().flatMap { stream in // a `ChangeStream>` stream.forEach { event in - // process `ChangeStreamEvent` here + // process `ChangeStreamEvent` here } }.whenFailure { error in // handle error @@ -80,9 +80,9 @@ let elg = MultiThreadedEventLoopGroup(numberOfThreads: 4) let client = try MongoClient(using: elg) let db = client.db("example") -db.watch().flatMap { stream in // a `ChangeStream>` +db.watch().flatMap { stream in // a `ChangeStream>` stream.forEach { event in - // process `ChangeStreamEvent` here + // process `ChangeStreamEvent` here } }.whenFailure { error in // handle error @@ -98,9 +98,9 @@ Note: the types of the `fullDocument` property, as well as the return type of `C let elg = MultiThreadedEventLoopGroup(numberOfThreads: 4) let client = try MongoClient(using: elg) -client.watch().flatMap { stream in // a `ChangeStream>` +client.watch().flatMap { stream in // a `ChangeStream>` stream.forEach { event in - // process `ChangeStreamEvent` here + // process `ChangeStreamEvent` here } }.whenFailure { error in // handle error @@ -117,7 +117,7 @@ let elg = MultiThreadedEventLoopGroup(numberOfThreads: 4) let client = try MongoClient(using: elg) let inventory = client.db("example").collection("inventory") -inventory.watch().flatMap { stream -> EventLoopFuture>> in +inventory.watch().flatMap { stream -> EventLoopFuture>> in // read the first change event stream.next().flatMap { _ in // simulate an error by killing the stream @@ -129,7 +129,7 @@ inventory.watch().flatMap { stream -> EventLoopFuture` here + // process `ChangeStreamEvent` here } }.whenFailure { error in // handle error @@ -145,13 +145,13 @@ let client = try MongoClient(using: elg) let inventory = client.db("example").collection("inventory") // Only include events where the changed document's username = "alice" -let pipeline: [Document] = [ - ["$match": ["fullDocument.username": "alice"] as Document] +let pipeline: [BSONDocument] = [ + ["$match": ["fullDocument.username": "alice"]] ] -inventory.watch(pipeline).flatMap { stream in // a `ChangeStream>` +inventory.watch(pipeline).flatMap { stream in // a `ChangeStream>` stream.forEach { event in - // process `ChangeStreamEvent` here + // process `ChangeStreamEvent` here } }.whenFailure { error in // handle error diff --git a/Guides/TLS.md b/Guides/TLS.md index dc5fd6173..61a9bb753 100644 --- a/Guides/TLS.md +++ b/Guides/TLS.md @@ -27,24 +27,24 @@ macOS 10.13 (High Sierra) and newer support TLS 1.1+. ## Basic Configuration -To require that connections to MongoDB made by the driver use TLS/SSL, simply specify `tls: true` in the `ClientOptions` passed to a `MongoClient`'s initializer: +To require that connections to MongoDB made by the driver use TLS/SSL, specify `tls: true` in the `MongoClientOptions` passed to a `MongoClient`'s initializer: ```swift -let client = try MongoClient("mongodb://example.com", using: elg, options: ClientOptions(tls: true)) +let client = try MongoClient("mongodb://example.com", using: elg, options: MongoClientOptions(tls: true)) ``` Alternatively, `tls=true` can be specified in the [MongoDB Connection String](https://docs.mongodb.com/manual/reference/connection-string/) passed to the initializer: ```swift let client = try MongoClient("mongodb://example.com/?tls=true", using: elg) ``` -**Note:** Specifying any `tls`-prefixed option in the connection string or `ClientOptions` will require all connections made by the driver to use TLS/SSL. +**Note:** Specifying any `tls`-prefixed option in the connection string or `MongoClientOptions` will require all connections made by the driver to use TLS/SSL. ## Specifying a CA File The driver can be configured to use a specific set of CA certificates. This is most often used with "self-signed" server certificates. -A path to a file with either a single or bundle of certificate authorities to be considered trusted when making a TLS connection can be specified via the `tlsCAFile` option on `ClientOptions`: +A path to a file with either a single or bundle of certificate authorities to be considered trusted when making a TLS connection can be specified via the `tlsCAFile` option on `MongoClientOptions`: ```swift -let client = try MongoClient("mongodb://example.com", using: elg, options: ClientOptions(tlsCAFile: URL(string: "/path/to/ca.pem"))) +let client = try MongoClient("mongodb://example.com", using: elg, options: MongoClientOptions(tlsCAFile: URL(string: "/path/to/ca.pem"))) ``` Alternatively, the path can be specified via the `tlsCAFile` option in the [MongoDB Connection String](https://docs.mongodb.com/manual/reference/connection-string/) passed to the client's initializer: @@ -55,16 +55,16 @@ let client = try MongoClient("mongodb://example.com/?tlsCAFile=\(caFile)", using ## Specifying a Client Certificate or Private Key File -The driver can be configured to present the client certificate file or the client private key file via the `tlsCertificateKeyFile` option on `ClientOptions`: +The driver can be configured to present the client certificate file or the client private key file via the `tlsCertificateKeyFile` option on `MongoClientOptions`: ```swift -let client = try MongoClient("mongodb://example.com", using: elg, options: ClientOptions(tlsCertificateKeyFile: URL(string: "/path/to/cert.pem"))) +let client = try MongoClient("mongodb://example.com", using: elg, options: MongoClientOptions(tlsCertificateKeyFile: URL(string: "/path/to/cert.pem"))) ``` -If the private key is password protected, a password can be supplied via `tlsCertificateKeyFilePassword` on `ClientOptions`: +If the private key is password protected, a password can be supplied via `tlsCertificateKeyFilePassword` on `MongoClientOptions`: ```swift let client = try MongoClient( "mongodb://example.com", using: elg, - options: ClientOptions(tlsCertificateKeyFile: URL(string: "/path/to/cert.pem"), tlsCertificateKeyFilePassword: ) + options: MongoClientOptions(tlsCertificateKeyFile: URL(string: "/path/to/cert.pem"), tlsCertificateKeyFilePassword: ) ) ``` @@ -73,7 +73,7 @@ Alternatively, these options can be set via the `tlsCertificateKeyFile` and `tls let certificatePath = "/path/to/cert.pem".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! let password = "not a secure password".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! let client = try MongoClient( - "mongodb://example.com/?tlsCertificateKeyFile=\(certificatePath)&tlsCertificateKeyFilePassword=\(password)" + "mongodb://example.com/?tlsCertificateKeyFile=\(certificatePath)&tlsCertificateKeyFilePassword=\(password)", using: elg ) ``` diff --git a/README.md b/README.md index 6bdb5ebce..d33694196 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Note: we have included the client `connectionString` parameter for clarity, but ### Create and Insert a Document **Async**: ```swift -let doc: Document = ["_id": 100, "a": 1, "b": 2, "c": 3] +let doc: BSONDocument = ["_id": 100, "a": 1, "b": 2, "c": 3] collection.insertOne(doc).whenSuccess { result in print(result?.insertedId ?? "") // prints `.int64(100)` } @@ -127,7 +127,7 @@ collection.insertOne(doc).whenSuccess { result in **Sync**: ```swift -let doc: Document = ["_id": 100, "a": 1, "b": 2, "c": 3] +let doc: BSONDocument = ["_id": 100, "a": 1, "b": 2, "c": 3] let result = try collection.insertOne(doc) print(result?.insertedId ?? "") // prints `.int64(100)` ``` @@ -135,7 +135,7 @@ print(result?.insertedId ?? "") // prints `.int64(100)` ### Find Documents **Async**: ```swift -let query: Document = ["a": 1] +let query: BSONDocument = ["a": 1] let result = collection.find(query).flatMap { cursor in cursor.forEach { doc in print(doc) @@ -145,7 +145,7 @@ let result = collection.find(query).flatMap { cursor in **Sync**: ```swift -let query: Document = ["a": 1] +let query: BSONDocument = ["a": 1] let documents = try collection.find(query) for d in documents { print(try d.get()) @@ -154,7 +154,7 @@ for d in documents { ### Work With and Modify Documents ```swift -var doc: Document = ["a": 1, "b": 2, "c": 3] +var doc: BSONDocument = ["a": 1, "b": 2, "c": 3] print(doc) // prints `{"a" : 1, "b" : 2, "c" : 3}` print(doc["a"] ?? "") // prints `.int64(1)` @@ -182,7 +182,7 @@ let doubled = doc.map { elem -> Int in print(doubled) // prints `[2, 4, 6, 8]` ``` -Note that `Document` conforms to `Collection`, so useful methods from +Note that `BSONDocument` conforms to `Collection`, so useful methods from [`Sequence`](https://developer.apple.com/documentation/swift/sequence) and [`Collection`](https://developer.apple.com/documentation/swift/collection) are all available. However, runtime guarantees are not yet met for many of these From 64b0262743158840c709f6a52c3ca3f641a02a52 Mon Sep 17 00:00:00 2001 From: Kaitlin Mahar Date: Tue, 26 May 2020 20:49:57 -0400 Subject: [PATCH 2/4] update docs examples --- Examples/Docs/Sources/AsyncExamples/main.swift | 8 ++++---- Examples/Docs/Sources/SyncExamples/main.swift | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/Docs/Sources/AsyncExamples/main.swift b/Examples/Docs/Sources/AsyncExamples/main.swift index 6c114efe2..4de1fcbed 100644 --- a/Examples/Docs/Sources/AsyncExamples/main.swift +++ b/Examples/Docs/Sources/AsyncExamples/main.swift @@ -17,7 +17,7 @@ private func causalConsistency() throws { // Start Causal Consistency Example 1 let s1 = client1.startSession(options: ClientSessionOptions(causalConsistency: true)) let currentDate = Date() - var dbOptions = DatabaseOptions( + var dbOptions = MongoDatabaseOptions( readConcern: .majority, writeConcern: try .majority(wtimeoutMS: 1000) ) @@ -126,19 +126,19 @@ private func changeStreams() throws { do { // Start Changestream Example 4 - let pipeline: [Document] = [ + let pipeline: [BSONDocument] = [ ["$match": ["fullDocument.username": "alice"]], ["$addFields": ["newField": "this is an added field!"]] ] let inventory = db.collection("inventory") // Option 1: use next() to iterate - let next = inventory.watch(pipeline, withEventType: Document.self).flatMap { changeStream in + let next = inventory.watch(pipeline, withEventType: BSONDocument.self).flatMap { changeStream in changeStream.next() } // Option 2: register a callback to execute for each document - let result = inventory.watch(pipeline, withEventType: Document.self).flatMap { changeStream in + let result = inventory.watch(pipeline, withEventType: BSONDocument.self).flatMap { changeStream in changeStream.forEach { event in // process event print(event) diff --git a/Examples/Docs/Sources/SyncExamples/main.swift b/Examples/Docs/Sources/SyncExamples/main.swift index 74b223eac..f907c289f 100644 --- a/Examples/Docs/Sources/SyncExamples/main.swift +++ b/Examples/Docs/Sources/SyncExamples/main.swift @@ -11,7 +11,7 @@ private func causalConsistency() throws { // Start Causal Consistency Example 1 let s1 = client1.startSession(options: ClientSessionOptions(causalConsistency: true)) let currentDate = Date() - var dbOptions = DatabaseOptions( + var dbOptions = MongoDatabaseOptions( readConcern: .majority, writeConcern: try .majority(wtimeoutMS: 1000) ) @@ -80,12 +80,12 @@ private func changeStreams() throws { do { // Start Changestream Example 4 - let pipeline: [Document] = [ + let pipeline: [BSONDocument] = [ ["$match": ["fullDocument.username": "alice"]], ["$addFields": ["newField": "this is an added field!"]] ] let inventory = db.collection("inventory") - let changeStream = try inventory.watch(pipeline, withEventType: Document.self) + let changeStream = try inventory.watch(pipeline, withEventType: BSONDocument.self) let next = changeStream.next() // End Changestream Example 4 } From ccdfa50d2c73ec6fb2ab285a1a9f09d787a597f3 Mon Sep 17 00:00:00 2001 From: Kaitlin Mahar Date: Tue, 26 May 2020 20:50:51 -0400 Subject: [PATCH 3/4] Update driver dependencies --- Examples/KituraExample/Package.swift | 2 +- Examples/PerfectExample/Package.swift | 2 +- Examples/VaporExample/Package.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/KituraExample/Package.swift b/Examples/KituraExample/Package.swift index 468c11e4f..5cd18c41c 100644 --- a/Examples/KituraExample/Package.swift +++ b/Examples/KituraExample/Package.swift @@ -5,7 +5,7 @@ let package = Package( name: "KituraExample", dependencies: [ .package(url: "https://github.com/IBM-Swift/Kitura", .upToNextMajor(from: "2.9.1")), - .package(url: "https://github.com/mongodb/mongo-swift-driver", .upToNextMajor(from: "1.0.0-rc0")), + .package(url: "https://github.com/mongodb/mongo-swift-driver", .upToNextMajor(from: "1.0.0")), .package(url: "https://github.com/apple/swift-nio", .upToNextMajor(from: "2.14.0")) ], targets: [ diff --git a/Examples/PerfectExample/Package.swift b/Examples/PerfectExample/Package.swift index 0624863af..a29e57d57 100644 --- a/Examples/PerfectExample/Package.swift +++ b/Examples/PerfectExample/Package.swift @@ -5,7 +5,7 @@ let package = Package( name: "PerfectExample", dependencies: [ .package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.0"), - .package(url: "https://github.com/mongodb/mongo-swift-driver", .upToNextMajor(from: "1.0.0-rc0")), + .package(url: "https://github.com/mongodb/mongo-swift-driver", .upToNextMajor(from: "1.0.0")), .package(url: "https://github.com/apple/swift-nio", .upToNextMajor(from: "2.14.0")) ], targets: [ diff --git a/Examples/VaporExample/Package.swift b/Examples/VaporExample/Package.swift index 13d40ba35..fb4ac700a 100644 --- a/Examples/VaporExample/Package.swift +++ b/Examples/VaporExample/Package.swift @@ -9,7 +9,7 @@ let package = Package( dependencies: [ // The driver depends on SwiftNIO 2 and therefore is only compatible with Vapor 4. .package(url: "https://github.com/vapor/vapor.git", from: "4.2.1"), - .package(url: "https://github.com/mongodb/mongo-swift-driver", .upToNextMajor(from: "1.0.0-rc0")) + .package(url: "https://github.com/mongodb/mongo-swift-driver", .upToNextMajor(from: "1.0.0")) ], targets: [ .target(name: "VaporExample", dependencies: [ From 4747678ed68894b869aab59749d566cbe3550aaa Mon Sep 17 00:00:00 2001 From: Kaitlin Mahar Date: Tue, 26 May 2020 20:55:30 -0400 Subject: [PATCH 4/4] get rid of errors in BSON guide --- Guides/BSON.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Guides/BSON.md b/Guides/BSON.md index f88bbf89d..ef980336f 100644 --- a/Guides/BSON.md +++ b/Guides/BSON.md @@ -54,7 +54,7 @@ let objectId = BSON.objectID() ### Unwrapping a `BSON` To get a `BSON` value as a specific type, you can use `switch` or `if/guard case let` like any other enum in Swift: ```swift -func foo(x: BSON, y: BSON) throws { +func foo(x: BSON, y: BSON) { switch x { case let .int32(int32): print("got an Int32: \(int32)") @@ -64,7 +64,8 @@ func foo(x: BSON, y: BSON) throws { print("got something else") } guard case let .double(d) = y else { - throw MongoError.InvalidArgumentError(message: "y must be a double") + print("y must be a double") + return } print(d * d) } @@ -86,17 +87,18 @@ print(BSON.double(5).doubleValue) // Double(5.0) ### Converting a `BSON` In some cases, especially when dealing with numbers, it may make sense to coerce a `BSON`'s wrapped value into a similar one. For those situations, there are several conversion methods defined on `BSON` that will unwrap the underlying value and attempt to convert it to the desired type. If that conversion would be lossless, a non-`nil` value is returned. ```swift -func foo(x: BSON, y: BSON) throws -> Int { +func foo(x: BSON, y: BSON) { guard let x = x.toInt(), let y = y.toInt() else { - throw InvalidArgumentError(message: "provide two integer types") + print("provide two integer types") + return } - return x + y + print(x + y) } -try foo(x: 5, y: 5.0) // 10 -try foo(x: 5, y: 5) // 10 -try foo(x: 5.0, y: 5.0) // 10 -try foo(x: .int32(5), y: .int64(5)) // 10 -try foo(x: 5.01, y: 5) // error +foo(x: 5, y: 5.0) // 10 +foo(x: 5, y: 5) // 10 +foo(x: 5.0, y: 5.0) // 10 +foo(x: .int32(5), y: .int64(5)) // 10 +foo(x: 5.01, y: 5) // error ``` There are similar conversion methods for the other types, namely `toInt32()`, `toDouble()`, `toInt64()`, and `toDecimal128()`.