Skip to content
This repository has been archived by the owner on Apr 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #48 from crichez/docc-init
Browse files Browse the repository at this point in the history
Refine documentation and support docc.
  • Loading branch information
crichez committed Aug 2, 2022
2 parents eb2981e + d6a2c5b commit 150d299
Show file tree
Hide file tree
Showing 24 changed files with 656 additions and 120 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ Package.resolved
.vscode
.swiftpm
.build
Sourcery-1.6.1

Sources/BisonWrite/BisonWrite.docc/.docc-build
Sources/BisonRead/BisonRead.docc/.docc-build
Sources/BisonEncode/BisonEncode.docc/.docc-build
Sources/BisonDecode/BisonDecode.docc/.docc-build
Sources/ObjectID/ObjectID.docc/.docc-build
135 changes: 84 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
# Bison
# 🦬 Bison

Binary JSON encoding and decoding in Swift.
BSON (binary JSON) encoding and decoding in Swift.

## Overview

The Bison package exposes two main modules:
* `Bison` for fast, flexible and type-safe document encoding and decoding.
* `BSONCodable` to adapt existing Swift `Codable` code already in your project.

Each of these modules is available for encoding or decoding only by importing:
* `BisonWrite` & `BisonRead` as alternatives to the full `Bison`.
* `BisonEncode` & `BisonDecode` as alternatives to the full `BisonCodable`.
The swift-bison package features **two flavors** of BSON document management:
* `BisonRead` & `BisonWrite`: custom APIs designed for flexibility and performance.
* `BisonEncode` & `BisonDecode`: Swift `Codable` adapters to start working with BSON today.

This project is tested in continuous integration on the following platforms:
* macOS 11
Expand All @@ -20,78 +16,115 @@ This project is tested in continuous integration on the following platforms:
* ubuntu 20.04
* Windows Server 2022

## Usage
## Version

You can import this package by adding the following line to your `Package.swift` dependencies:
```swift
.package(url: "https://github.com/crichez/swift-bson", .upToNextMinor("0.0.1"))
.package(url: "https://github.com/crichez/swift-bison", .upToNextMinor("0.0.1"))
```

*Note:* versions before 1.0.0 are considered pre-release, and code-breaking changes may be
*Note:* versions before 1.0.0 are considered pre-release, and sources-breaking changes may be
introduced with a minor version bump. Once the project graduates to 1.0.0, regular semantic
versioning rules will apply.

## Documentation

**This package is documented using [swift-docc](https://github.com/apple/swift-docc)**. You can
build the documentation for a specific module by following instructions at that repository,
or read inline documentation for each source file. The following sections give a brief
introduction to the most common APIs of each module.

### BisonWrite

When using `BisonWrite`, document structure is declared using a custom result builder.
Import the `BisonWrite` module to compose and encode complex documents using a custom result
builder. The following example declares a simple BSON document with conditionals and nesting,
then encodes it to an Array of bytes.

```swift
import BisonWrite

// Use the => operator to pair keys and values
let doc = WritableDoc {
"one" => 1
"two" => 2.0
"three" => "3"
"doc" => WritableDoc {
"flag" => true
"maybe?" => String?.none
let ownerDoc = WritableDoc {
"name" => "Bob Belcher"
if season8 {
"age" => Int64(46)
} else {
"age" => Int64(45)
}
"children" => WritableArray {
"Tina"
"Louise"
"Gene"
}
}

// Encode the final document using your collection of choice.
let encodedDoc = doc.encode(as: Data.self)
.encode(as: [UInt8].self)
```

### BisonRead

When using `BisonRead`, decoding is done in two steps:
1. Validate the document's structure by intializing a `ReadableDoc`
2. Decode individual values using their `init(bsonBytes:)` initializer
Import the `BisonRead` module to parse and decode documents from raw bytes. The following example
parses a document, then extracts select values.

```swift
import BisonRead

// Parse the keys and structure first
let doc = try ReadableDoc(bsonBytes: encodedDoc)

// Get values individually from the document
let one = try Int(bsonBytes: doc["one"])
let two = try Double(bsonBytes: doc["two"])
let three = try String(bsonBytes: doc["three"])

// Nested documents are treated as values
let nestedDoc = try ReadableDoc(bsonBytes: doc["doc"])

// And they expose their contents the same way
let flag = try Bool(bsonBytes: nestedDoc["flag"])
let maybe = try String?(bsonBytes: nestedDoc["maybe?"])
do {
// Parse and validate the document.
let doc = try ReadableDoc(bsonBytes: ownerDoc)
// Get the "name" value and initialize it as a String.
guard let nameData = doc["name"] else { return }
let name = try String(bsonBytes: nameData)
// Get the "children" nested document and initialize each value.
guard let childrenData = doc["children"] else { return }
let childrenDoc = try ReadableDoc(bsonBytes: childrenData)
for childNameData in childrenDoc.values {
let childName = try String(bsonBytes: childNameData)
}
} catch DocError<[UInt8]>.unknownType(let type, let key, let progress) {
// Handle document parsing errors.
} catch ValueError.sizeMismatch {
// Handle value parsing errors.
}
```

### BisonCodable
### BisonEncode & BisonDecode

`BisonCodable` is meant as a drop-in replacement for `PropertyListEncoder` or `JSONDecoder`,
including error handling using Swift `EncodingError` and `DecodingError`.
You can use the `BSONEncoder` and `BSONDecoder` types as analogs that produce BSON documents.
Import the `BisonEncode` & `BisonDecode` modules to use the `BSONEncoder` and `BSONDecoder` types.
These expose a similar API to Foundation's JSON encoder and decoder, while still featuring support
for custom BSON types through `BisonWrite` & `BisonRead` APIs.

```swift
import BisonCodable
import BisonEncode
import BisonDecode
import Foundation

struct Person: Codable {
struct Owner: Codable {
let name: String
let age: Int
let age: Int64
let children: [String]
}

do {
let owner = Owner(name: "Bob Belcher", age: 46, children: ["Tina", "Louise", "Gene"])
let encodedOwnerDoc = try BSONEncoder<Data>().encode(owner)
let decodedOwner = try BSONDecoder().decode(Owner.self, from: encodedOwnerDoc)
} catch DecodingError.typeMismatch(let requested, let context) {
// Handle traditional Swift EncodingErrors and DecodingErrors
}
```

### ObjectID

let person = Person(name: "Bob Belcher", age: 41)
let encodedPerson = try BSONEncoder<Data>().encode(person)
let decodedPerson = try BSONDecoder().decode(Person.self, from: encodedPerson)
The `ObjectID` module includes a full-featured BSON ObjectID implementation.

```swift
import ObjectID

// Initialize IDs randomly or from hexadecimal data.
var randomID = ObjectID()
// Use the timestamp property for chronological tracking.
let created: Date = randomID.timestamp
// Use the increment property for version tracking.
let version: Int = randomID.increment
// Easily output hexadecimal strings using custom LosslessStringConvertible conformance
let hexID = String(describing: randomID)
```
9 changes: 9 additions & 0 deletions Sources/BisonDecode/BisonDecode.docc/BisonDecode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ``BisonDecode``

Use `BSONDecoder` to read `Decodable` values from BSON documents.

## Topics

### Decoders

- ``BSONDecoder``
17 changes: 9 additions & 8 deletions Sources/BisonEncode/BSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,21 @@

import Foundation

/// Use `BSONEncoder` to write `Encodable` values into fully-formed BSON documents.
/// Use `BSONEncoder` to write BSON documents from `Encodable` values.
///
/// `BSONEncoder` is generic over its encoded document type: `Doc`.
/// The only constraint on this type is it must be a `RangeReplaceableCollection` with an
/// element type of `UInt8`. In most cases, you can simply initialize your encoder over
/// element type of `UInt8`. In most cases, you can simply initialize your encoder
/// over the `Data` type as follows.
///
/// import Foundation
/// import BisonEncode
/// ```swift
/// import Foundation
/// import BisonEncode
///
/// let encoder = BSONEncoder<Data>()
/// let document = try encoder.encode(myEncodableInstance)
/// try document.write(to: file)
///
/// let encoder = BSONEncoder<Data>()
/// let document = try encoder.encode(myEncodableInstance)
/// try document.write(to: file)
/// ```
public struct BSONEncoder<Doc: RangeReplaceableCollection> where Doc.Element == UInt8 {
/// Initializes an encoder.
public init() {}
Expand Down
9 changes: 9 additions & 0 deletions Sources/BisonEncode/BisonEncode.docc/BisonEncode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ``BisonEncode``

Encode BSON documents using Swift Codable.

## Topics

### Encoders

- ``BSONEncoder``
18 changes: 18 additions & 0 deletions Sources/BisonRead/BisonRead.docc/BisonRead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ``BisonRead``

Read BSON documents.

## Topics

### Documents

- ``ReadableDoc``
- ``DocError``
- ``Progress``

### Values

- ``ReadableValue``
- ``CustomReadableValue``
- ``ValueError``

24 changes: 24 additions & 0 deletions Sources/BisonRead/BisonRead.docc/ReadableDoc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ``BisonRead/ReadableDoc``

@Metadata {
@DocumentationExtension(mergeBehavior: override)
}

A BSON document from which values can be read.

## Topics

### Parsing a Document

- ``BisonRead/ReadableDoc/init(bsonBytes:)``
- ``BisonRead/DocError``
- ``BisonRead/Progress``

### Extracting Values

- ``BisonRead/ReadableDoc/subscript(_:)``
- ``BisonRead/ReadableDoc/keys``
- ``BisonRead/ReadableDoc/values``
- ``BisonRead/ReadableDoc/min``
- ``BisonRead/ReadableDoc/max``

25 changes: 23 additions & 2 deletions Sources/BisonRead/CustomReadableValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,30 @@
// limitations under the License.
//

/// A BSON value whose encoded form declares the BSON binary type (5).
/// A protocol that defines how a custom value is read from a document.
///
/// Conform to `CustomReadableValue` for BSON binary values to inherit metadata parsing.
/// ## Conforming to CustomReadableValue
///
/// Conform to this protocol to decode a custom value from its raw BSON representation. The value's
/// raw bytes are passed to the ``init(bsonValueBytes:)`` initializer as a generic `Collection`.
/// The following example is the actual `UUID` conformance.
///
/// ```swift
/// extension UUID: CustomReadableValue {
/// public init<Data: Collection>(bsonValueBytes: Data) throws where Data.Element == UInt8 {
/// guard bsonValueBytes.count == 16 else {
/// throw BisonError.sizeMismatch(16, bsonValueBytes.count)
/// }
/// let copyBuffer = UnsafeMutableRawBufferPointer.allocate(capacity: 16, alignment: 16)
/// copyBuffer.copyBytes(from: bsonValueBytes)
/// self = copyBuffer.load(as: UUID.self)
/// }
/// }
/// ```
///
/// > Note: BSON "binary" types include size and type metadata. These are usually parsed for you,
/// so the data in `bsonValueBytes` is always the declared size. This may not be the size
/// you expect due to encoding errors, so you should always check.
public protocol CustomReadableValue: ReadableValue {
/// Initializes a value from the provided BSON data.
///
Expand Down
4 changes: 2 additions & 2 deletions Sources/BisonRead/DocError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ public enum DocError<Data: Collection>: Error where Data.Element == UInt8 {
/// the data was corrupted in transit or isn't a valid BSON document.
///
/// > Note: In the case of corruption, ``notTerminated`` will usually be triggered first.
/// There is a chance that the corrupted data happens to be null-terminated, in whcih case
/// There is a chance that the corrupted data happens to be null-terminated, in which case
/// this error may be triggered instead.
case docSizeMismatch(expectedExactly: Int)

/// An unknown or deprecated BSON type byte was found while parsing a key.
/// An unknown BSON type byte was found while parsing a key.
///
/// This error includes a ``Progress`` value. You may check the partially decoded document
/// within to recover from the error.
Expand Down
Loading

0 comments on commit 150d299

Please sign in to comment.