jordanekay/cubby
**Cubby** is a Swift wrapper around the [JSONBin](https://jsonbin.io) API that provides a simple interface for storing, categorizing, and fetching Swift structs in the cloud.
API Support
Cubby provides full support for both v2 and v3 of the JSONBin API.
Version 2.0
- [X] Bin
- [X] Create - [X] Read - [X] Update - [X] Delete
- [X] Collection
- [X] Create - [X] Read
- [X] Schema Doc
- [X] Create - [X] Read - [X] Update
- [X] Geolocation
- [X] Lookup
- [X] Experimental
- [X] Request Count
Version 3.0
- [X] Bin
- [X] Create - [X] Read - [X] Version Count - [X] Update - [X] Update Name - [X] Update Privacy - [X] Delete - [X] Delete Versions
- [X] Collection
- [X] Create - [X] Fetch in Collection - [X] Fetch Uncategorized - [X] Update Name - [X] Add Schema Doc - [X] Remove Schema Doc
- [X] Schema Doc
- [X] Create - [X] Read - [X] Update - [X] Update Name
- [X] Other
- [X] List Usage Logs - [X] Download Usage Log
Usage
Cubby uses specification protocols to indicate which endpoints each API version supports. Use an appropriate instantiation of `JSONBin.V2.API` or `JSONBin.V3.API` depending on the endpoint you want to call. (Note that version 2 of the JSONBin API was planned for deprecation on February 28, 2022.)
### Bin
Create, read, update, or delete bins that represent Swift structs. Each bin represents a single Swift struct and has a privacy setting, an optional name, and an optional collection it belongs to.
```swift
public protocol JSONBinV2APIBinSpec: JSONBinV2APISpec {
func createBin<Resource: Encodable>(named name: String?, with resource: Resource, inCollectionWith id: Collection.ID?, private: Bool?) -> Request<Creation<Resource>>
func readBin<Resource: Decodable>(with id: ID, of type: Resource.Type, at version: Version?) -> Request<Resource>
func updateBin<Resource: Encodable>(with id: ID, using resource: Resource, versioning: Bool?) -> Request<Update<Resource>>
func deleteBin(with id: ID) -> Request<Deletion>
}
public protocol JSONBinV3APIBinSpec: JSONBinV3APISpec {
func createBin<Resource: Encodable>(named name: String?, with resource: Resource, inCollectionWith id: Collection.ID?, private: Bool?) -> Request<Creation<Resource>>
func readBin<Resource: Decodable>(with id: ID, of type: Resource.Type, at version: Version?, includingMetadata: Bool?, usingDotPath dotPath: String?) -> Request<Read<Resource>>
func versionCount(ofBinWith id: ID) -> Request<VersionCount>
func updateBin<Resource: Encodable>(with id: ID, using resource: Resource, versioning: Bool?) -> Request<Update<Resource>>
func updateName(ofBinWith id: ID, toName name: String) -> Request<NameUpdate>
func updatePrivacy(ofBinWith id: ID, toPrivate private: Bool) -> Request<PrivacyUpdate>
func deleteBin(with id: ID) -> Request<Deletion>
func deleteVersions(ofBinWith id: ID, preservingLatest: Bool?) -> Request<Deletion>
}
```
<details>
<summary>Creation Example</summary>
```swift
var apple = Company(name: "Apple", remoteWorkPolicy: .hybrid)
let request = api.createBin(named: "Apple Computer", with: apple)
let creation = try await request.returnedResource
let id = creation.metadata.id
print(creation.resource) // Company(name: "Apple", remoteWorkPolicy: .hybrid)
```
</details>
<details>
<summary>Read Example</summary>
```swift
let request = api.readBin(with: id, of type: Company.self, at: .number(1), includingMetadata: false)
let company = try await request.returnedResource
print(company) // Company(name: "Apple Computer", remoteWorkPolicy: .hybrid)
```
</details>
<details>
<summary>Update Example</summary>
```swift
apple.remoteWorkPolicy = .disallowed
let request = api.updateBin(with: id, using: apple)
let update = try await request.returnedResource
print(update.resource) // Company(name: "Apple", remoteWorkPolicy: .disallowed)
```
</details>
<details>
<summary>Update Name Example</summary>
```swift
let request = api.updateName(ofBinWith: id, toName: "Apple Inc.")
let update = try await request.returnedResource
print(update.resource) // Company(name: "Apple", remoteWorkPolicy: .disallowed)
```
</details>
<details>
<summary>Version Count Example</summary>
```swift
let request = api.versionCount(ofBinWith: id)
let versionCount = try await request.returnedResource.metadata.versionCount
print(versionCount) // 1
```
</details>
<details>
<summary>Deletion Versions Example</summary>
```swift
let request = api.deleteVersions(ofBinWithID: id, preservingLatest: true)
let deletion = try await request.returnedResource
print(deletion.message) // "Versions for the Bin are deleted successfully and latest version preserved on the base record."
```
</details>
<details>
<summary>Deletion Example</summary>
```swift
let request = api.deleteBin(with: id)
let deletion = try await request.returnedResource
print(deletion.message) // "Bin deleted successfully"
```
</details>
### Collection
Create, fetch from, or update collections that contain your Swift structs. To ensure a collection only contains structs of the same type, attach a schema document (see below) representing that type to the collection. Once attached, attempting to create a bin in a collection of a different type (i.e., one that cannot be validated by the schema) will fail.
```swift
public protocol JSONBinV2APICollectionSpec: JSONBinV2APISpec {
func createCollection(named name: String) -> Request<Creation>
func updateCollection(with id: ID, using action: Action) -> Request<Update>
}
public protocol JSONBinV3APICollectionSpec: JSONBinV3APISpec {
func createCollection(named name: String) -> Request<Creation>
func fetchBins(inCollectionWith id: ID, sortedBy sortOrder: Fetch.SortOrder?) -> Request<[Fetch.Result]>
func fetchUncategorizedBins(sortedBy sortOrder: Fetch.SortOrder?) -> Request<[Fetch.Result]>
func updateName(ofCollectionWith id: ID, toName name: String) -> Request<NameUpdate>
func addSchemaDoc(with id: SchemaDoc.ID, toCollectionWith collectionID: ID) -> Request<Addition>
func removeSchemaDoc(fromCollectionWith id: ID) -> Request<Removal>
}
```
<details>
<summary>Creation Example</summary>
```swift
let request = api.createCollection(named: "WFH Companies")
let creation = try await request.returnedResource
let id = creation.metadata.id
print(creation.metadata.name) // "WFH Companies"
```
</details>
<details>
<summary>Fetch Example</summary>
```swift
let request = api.fetchBins(inCollectionWith: id)
let companies = try await request.returnedResource
print(companies) // [Company(name: "Twitter", remoteWorkPolicy: .allowed), Company(name: "GitHub", remoteWorkPolicy: .distributed)]
```
</details>
<details>
<summary>Update Name Example</summary>
```swift
let request = api.updateName(ofCollectionWith: id, toName: "Remote Companies")
let update = try await request.returnedResource
print(update.metadata.name) // "Remote Companies"
```
</details>
<details>
<summary>Add Schema Doc Example</summary>
```swift
let request = api.addSchemaDoc(with: schemaDocID, toCollectionWith: id)
request() // Adds a schema doc to the collection
```
</details>
<details>
<summary>Remove Schema Doc Example</summary>
```swift
let request = api.removeSchemaDoc(fromCollectionWith: id)
request() // Removes the schema doc from the collection
```
</details>
### Schema Doc
Create, read, or update schema documents that can be attached to collections.
```swift
public protocol JSONBinV2APISchemaDocSpec: JSONBinV2APISpec {
func createSchemaDoc<Resource>(for type: Resource.Type, named name: String) -> Request<Response<Resource>>
func readSchemaDoc<Resource>(with id: ID, for type: Resource.Type) -> Request<Schema<Resource>>
func updateSchemaDoc<Resource>(with id: ID, toSchemaFor type: Resource.Type) -> Request<Response<Resource>>
}
public protocol JSONBinV3APISchemaDocSpec: JSONBinV3APISpec {
func createSchemaDoc<Resource>(for type: Resource.Type, named name: String) -> Request<Response<Resource>>
func readSchemaDoc<Resource>(with id: ID, for type: Resource.Type) -> Request<Response<Resource>>
func updateSchemaDoc<Resource>(with id: ID, toSchemaFor type: Resource.Type) -> Request<Update<Resource>>
func updateName(ofSchemaDocWith id: ID, toName name: String) -> Request<NameUpdate>
}
```
<details>
<summary>Creation Example</summary>
```swift
extension Company: SchemaAdhering {
static var description: String? {
"A company has both a name and a remote work policy."
}
static var properties: [CodingKeys: SchemaType] {
[
.name: .string,
.remoteWorkPolicy: .string
]
}
}
let request = api.createSchemaDoc(for: Company.self, named: "Company Schema Doc")
let creation = try await request.returnedResource
let id = creation.metadata.id
print(creation.metadata.name) // "Company Schema Doc"
print(creation.schema.title) // "Company"
print(creation.schema.description) // "A company has both a name and a remote work policy."
print(creation.schema.properties) // [.name: .string, .remoteWorkPolicy: .string]
```
</details>
<details>
<summary>Read Example</summary>
```swift
let request = api.readSchemaDoc(with: id)
let read = try await request.returnedResource
print(read.metadata.name) // "Company Schema Doc"
print(read.schema.title) // "Company"
print(read.schema.description) // "A company has both a name and a remote work policy."
print(read.schema.properties) // [.name: .string, .remoteWorkPolicy: .string]
```
</details>
<details>
<summary>Update Example</summary>
```swift
extension Company: SchemaAdhering {
static var description: String? {
"A company has both a name, a remote work policy, and a number of employees."
}
static var properties: [CodingKeys: SchemaType] {
[
.name: .string,
.remoteWorkPolicy: .string,
.employeeCount: .integer
]
}
}
let request = api.updateSchemaDoc(with: id, toSchemaFor: Company.self)
let update = request.returnedResource
print(update.metadata.name) // "Company Schema Doc"
print(update.schema.title) // "Company"
print(update.schema.description) // "A company has, a name, a remote work policy, and a number of employees."
print(update.schema.properties) // [.name: .string, .remoteWorkPolicy: .string, .employeeCount: .integer]
```
</details>
<details>
<summary>Update Name Example</summary>
```swift
let request = api.updateName(ofSchemaDocWith: id, toName: "Company Schema Document")
let update = try await request.returnedResource
print(update.metadata.name) // "Company Schema Document"
```
</details>
### Geolocation
Look up geolocation data for an IP address.
```swift
public protocol JSONBinV2APIGeoIPSpec: JSONBinV2APISpec {
func lookUpGeolocation(for ipAddress: IPAddress) -> Request<Lookup>
}
```
<details>
<summary>Look Up Geolocation Example</summary>
```swift
let ipAddress = IPAddress("141.158.45.225")
let request = api.lookUpGeolocation(for: ipAddress)
let lookupData = await request.returnedResource.data
print(lookupData.range) // 2375953408..<2375953919
print(lookupData.countryCode) // "US"
print(lookupData.regiounCode) // "PA"
print(lookupData.timeZone) // "America/New_York"
print(lookupData.city) // "Philadelphia"
print(lookupData.coordinates.latitude) // 39.934
print(lookupData.coordinates.longitude) // -75.16
print(lookupData.metroCode) // 504
print(lookupData.accuracyRadius) // 1
```
</details>
### Experimental
Get the number of requests remaining for this account.
```swift
public protocol JSONBinV2APIExperimentalSpec: JSONBinV2APISpec {
func requestCount() -> Request<Count>
}
```
<details>
<summary>Request Count Example</summary>
```swift
let request = api.requestCount()
let count = try await request.returnedResource
print(count.value) // 1000000
```
</details>
### Other
List the usage logs for this account, or download a specific usage log.
```swift
public protocol JSONBinV3APIOtherSpec: JSONBinV3APISpec {
func listUsageLogs() -> Request<UsageLog.List>
func downloadUsageLog(named name: String) -> Request<UsageLog>
}
```
<details>
<summary>List Usage Logs Example</summary>
```swift
let ipAddress = IPAddress("141.158.45.225")
let request = api.listUsageLogs
let list = await request.returnedResource
print(list.logNames) // ["12-31-2022", "01-01-2022", "01-02-2022"]
```
</details>
<details>
<summary>Download Usage Log Example</summary>
```swift
let ipAddress = IPAddress("141.158.45.225")
let request = api.downloadUsageLog(named: "01-01-2022")
let usageLog = await request.returnedResource
print(usageLog.compressed) // ZIP data of log contents
```
</details>Installation
Cubby is distributed using the Swift Package Manager. To install it into a project, simply add it as a dependency within your Package.swift manifest:
let package = Package(
...
dependencies: [
.package(url: "https://github.com/Fleuronic/Cubby", from: "1.0.0")
],
...
)Then import Cubby wherever you’d like to use it:
import CubbyFor more information on how to use the Swift Package Manager, check out this article, or its official documentation.
Package Metadata
Repository: jordanekay/cubby
Default branch: main
README: README.md