swift: Port golang's syncbase/model.go to Swift
Currently only the interface are ported -- the accompanying CGO is
not part of this CL.
However this CL does include the first stab at JSON support and an API
around it for Syncbase's get/put.
Change-Id: I8cc69fd5bc6298919bfa3f3fad10c876295619fc
diff --git a/lib/Syncbase/Batch.swift b/lib/Syncbase/Batch.swift
new file mode 100644
index 0000000..6640b7d
--- /dev/null
+++ b/lib/Syncbase/Batch.swift
@@ -0,0 +1,50 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+internal class BatchHandle {
+}
+
+public enum Batch {
+ public typealias BatchCompletionHandler = SyncbaseError? -> Void
+ public typealias Operation = Void -> BatchCompletionHandler
+
+ /**
+ Runs the given batch operation, managing retries and BatchDatabase's commit() and abort()s.
+
+ - Parameter db: database on which the batch operation is to be performed
+ - Parameter opts: batch configuration
+ - Parameter op: batch operation
+ - Parameter completionHandler: future result called when runInBatch finishes
+ */
+ public static func runInBatch(db: Database, opts: BatchOptions, op: Operation, completionHandler: BatchCompletionHandler) {
+ preconditionFailure("stub")
+ }
+}
+
+public struct BatchOptions {
+ /// Arbitrary string, typically used to describe the intent behind a batch.
+ /// Hints are surfaced to clients during conflict resolution.
+ /// TODO(sadovsky): Use "any" here?
+ public let hint: String? = nil
+
+ /// ReadOnly specifies whether the batch should allow writes.
+ /// If ReadOnly is set to true, Abort() should be used to release any resources
+ /// associated with this batch (though it is not strictly required), and
+ /// Commit() will always fail.
+ public let readOnly: Bool = false
+}
+
+public protocol BatchDatabase: DatabaseHandle {
+ /// Commit persists the pending changes to the database.
+ /// If the batch is readonly, Commit() will fail with ErrReadOnlyBatch; Abort()
+ /// should be used instead.
+ func commit() throws
+
+ /// Abort notifies the server that any pending changes can be discarded.
+ /// It is not strictly required, but it may allow the server to release locks
+ /// or other resources sooner than if it was not called.
+ func abort() throws
+}
diff --git a/lib/Syncbase/Blob.swift b/lib/Syncbase/Blob.swift
new file mode 100644
index 0000000..9562fe2
--- /dev/null
+++ b/lib/Syncbase/Blob.swift
@@ -0,0 +1,135 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+/// BlobRef is a reference to a blob.
+public typealias BlobRef = String
+
+public enum BlobDevType: Int {
+ /// Blobs migrate toward servers, which store them. (example: server in cloud)
+ case BlobDevTypeServer = 0
+ /// Ordinary devices (example: laptop)
+ case BlobDevTypeNormal = 1
+ /// Blobs migrate from leaves, which have less storage (examples: a camera, phone)
+ case BlobDevTypeLeaf = 2
+}
+
+let kNullBlobRef = BlobRef("")
+
+/// Blob is the interface for a Blob in the store.
+public protocol Blob {
+ /// Ref returns Syncbase's BlobRef for this blob.
+ func ref() -> BlobRef
+
+ /// Put appends the byte stream to the blob.
+ func put() throws -> BlobWriter
+
+ /// Commit marks the blob as immutable.
+ func commit() throws
+
+ /// Size returns the count of bytes written as part of the blob
+ /// (committed or uncommitted).
+ func size() throws -> Int64
+
+ /// Delete locally deletes the blob (committed or uncommitted).
+ func delete() throws
+
+ /// Get returns the byte stream from a committed blob starting at offset.
+ func get(offset: Int64) throws -> BlobReader
+
+ /// Fetch initiates fetching a blob if not locally found. priority
+ /// controls the network priority of the blob. Higher priority blobs are
+ /// fetched before the lower priority ones. However an ongoing blob
+ /// transfer is not interrupted. Status updates are streamed back to the
+ /// client as fetch is in progress.
+ func fetch(priority: UInt64) throws -> BlobStatus
+
+ /// Pin locally pins the blob so that it is not evicted.
+ func pin() throws
+
+ /// Unpin locally unpins the blob so that it can be evicted if needed.
+ func unpin() throws
+
+ /// Keep locally caches the blob with the specified rank. Lower
+ /// ranked blobs are more eagerly evicted.
+ func keep(rank: UInt64) throws
+}
+
+/// BlobWriter is an interface for putting a blob.
+public protocol BlobWriter {
+ /// Send places the bytes given by the client onto the output
+ /// stream. Returns throwss encountered while sending. Blocks if there is
+ /// no buffer space.
+ func Send(data: NSData) throws
+
+ /// Close indicates that no more bytes will be sent.
+ func Close() throws
+}
+
+/// BlobReader is an interface for getting a blob.
+public protocol BlobReader {
+ /// Advance() stages bytes so that they may be retrieved via
+ /// Value(). Returns true iff there are bytes to retrieve. Advance() must
+ /// be called before Value() is called. The caller is expected to read
+ /// until Advance() returns false, or to call Cancel().
+ func advance() -> Bool
+
+ /// Value() returns the bytes that were staged by Advance(). May panic if
+ /// Advance() returned false or was not called. Never blocks.
+ func value() -> NSData
+
+ /// Err() returns any throws encountered by Advance. Never blocks.
+ func err() throws
+
+ /// Cancel notifies the stream provider that it can stop producing
+ /// elements. The client must call Cancel if it does not iterate through
+ /// all elements (i.e. until Advance returns false). Cancel is idempotent
+ /// and can be called concurrently with a goroutine that is iterating via
+ /// Advance. Cancel causes Advance to subsequently return false. Cancel
+ /// does not block.
+ func cancel()
+}
+
+/// BlobStatus is an interface for getting the status of a blob transfer.
+public protocol BlobStatus {
+ /// Advance() stages an item so that it may be retrieved via
+ /// Value(). Returns true iff there are items to retrieve. Advance() must
+ /// be called before Value() is called. The caller is expected to read
+ /// until Advance() returns false, or to call Cancel().
+ func advance() -> Bool
+
+ /// Value() returns the item that was staged by Advance(). May panic if
+ /// Advance() returned false or was not called. Never blocks.
+ func value() -> BlobFetchStatus
+
+ /// Err() returns any throws encountered by Advance. Never blocks.
+ func err() -> SyncbaseError?
+
+ /// Cancel notifies the stream provider that it can stop producing
+ /// elements. The client must call Cancel if it does not iterate through
+ /// all elements (i.e. until Advance returns false). Cancel is idempotent
+ /// and can be called concurrently with a goroutine that is iterating via
+ /// Advance. Cancel causes Advance to subsequently return false. Cancel
+ /// does not block.
+ func cancel()
+}
+
+//// BlobFetchStatus describes the progress of an asynchronous blob fetch.
+public struct BlobFetchStatus {
+ //// State of the blob fetch request.
+ public let state: BlobFetchState
+ //// Total number of bytes received.
+ public let received: Int64
+ //// Blob size.
+ public let total: Int64
+}
+
+public enum BlobFetchState: Int {
+ case BlobFetchStatePending = 0
+ case BlobFetchStateLocating
+ case BlobFetchStateFetching
+ case BlobFetchStateDone
+}
+
diff --git a/lib/Syncbase/Collection.swift b/lib/Syncbase/Collection.swift
new file mode 100644
index 0000000..a889704
--- /dev/null
+++ b/lib/Syncbase/Collection.swift
@@ -0,0 +1,110 @@
+/// Copyright 2015 The Vanadium Authors. All rights reserved.
+/// Use of this source code is governed by a BSD-style
+/// license that can be found in the LICENSE file.
+
+import Foundation
+
+/// CollectionRow encapsulates a collection id and row key or row prefix.
+public struct CollectionRow {
+ let collectionId: CollectionId
+ let row: String
+}
+
+/// SyncgroupMemberInfo contains per-member metadata.
+public struct SyncgroupMemberInfo {
+ let syncPriority: UInt8
+ let blobDevType: BlobDevType /// See BlobDevType* constants.
+}
+
+/// Collection represents a set of Rows.
+///
+/// TODO(sadovsky): Currently we provide Get/Put/Delete methods on both
+/// Collection and Row, because we're not sure which will feel more natural.
+/// Eventually, we'll need to pick one.
+public protocol Collection {
+ /// Id returns the id of this Collection.
+ var collectionId: CollectionId { get }
+
+ /// FullName returns the object name (encoded) of this Collection.
+ var fullName: String { get }
+
+ /// Exists returns true only if this Collection exists. Insufficient
+ /// permissions cause Exists to return false instead of an throws.
+ /// TODO(ivanpi): Exists may fail with an throws if higher levels of hierarchy
+ /// do not exist.
+ func exists() throws -> Bool
+
+ /// Create creates this Collection.
+ /// TODO(sadovsky): Specify what happens if perms is nil.
+ func create(permissions: Permissions) throws
+
+ /// Destroy destroys this Collection, permanently removing all of its data.
+ /// TODO(sadovsky): Specify what happens to syncgroups.
+ func destroy() throws
+
+ /// GetPermissions returns the current Permissions for the Collection.
+ /// The Read bit on the ACL does not affect who this Collection's rows are
+ /// synced to; all members of syncgroups that include this Collection will
+ /// receive the rows in this Collection. It only determines which clients
+ /// are allowed to retrieve the value using a Syncbase RPC.
+ func getPermissions() throws -> Permissions
+
+ /// SetPermissions replaces the current Permissions for the Collection.
+ func setPermissions(permissions: Permissions) throws
+
+ /// Row returns the Row with the given key.
+ func row(key: String) -> Row
+
+ /**
+ Get loads the value stored under the given key into inout parameter value.
+
+ Sets value to nil if nothing is stored undeder the given key.
+
+ By passing the typed target output value using inout, get is able to cast the value to the
+ desired type automatically. If the types do not match, an exception is thrown.
+
+ For example:
+
+ ```
+ /// Using inout
+ var isRed: Bool?
+ try collection.get("isRed", &isRed)
+
+ /// Using return value
+ let isRed: Bool = try collection.get("isRed")
+ ```
+ */
+ func get<T: SyncbaseJsonConvertible>(key: String, inout value: T?) throws
+
+ /// Get loads the value stored under the given key.
+ func get<T: SyncbaseJsonConvertible>(key: String) throws -> T?
+
+ /// Put writes the given value to this Collection under the given key.
+ func put(key: String, value: SyncbaseJsonConvertible) throws
+
+ /// Delete deletes the row for the given key.
+ func delete(key: String) throws
+
+ /// DeleteRange deletes all rows in the given half-open range [start, limit).
+ /// If limit is "", all rows with keys >= start are included.
+ /// TODO(sadovsky): Document how this deletion is considered during conflict
+ /// detection: is it considered as a range deletion, or as a bunch of point
+ /// deletions?
+ /// See helpers Prefix(), Range(), SingleRow().
+ func deleteRange(r: RowRange) throws
+
+ /// Scan returns all rows in the given half-open range [start, limit). If limit
+ /// is "", all rows with keys >= start are included.
+ /// Concurrency semantics: It is legal to perform writes concurrently with
+ /// Scan. The returned stream reads from a consistent snapshot taken at the
+ /// time of the RPC (or at the time of BeginBatch, if in a batch), and will not
+ /// reflect subsequent writes to keys not yet reached by the stream.
+ /// See helpers Prefix(), Range(), SingleRow().
+ func scan(r: RowRange) -> ScanStream
+}
+
+/// Returns the decoded value, or throws an error if the value could not be decoded.
+public typealias GetValueFromScanStream = () throws -> SyncbaseJsonConvertible
+
+/// Stream resulting from a scan on a scollection for a given row range.
+public typealias ScanStream = AnonymousStream<(String, GetValueFromScanStream)>
diff --git a/lib/Syncbase/Database.swift b/lib/Syncbase/Database.swift
new file mode 100644
index 0000000..d6801e4
--- /dev/null
+++ b/lib/Syncbase/Database.swift
@@ -0,0 +1,107 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+/// DatabaseHandle is the set of methods that work both with and without a batch.
+/// It allows clients to pass the handle to helper methods that are batch-agnostic.
+public protocol DatabaseHandle {
+ /// Id returns the id of this DatabaseHandle..
+ var databaseId: DatabaseId { get }
+
+ /// FullName returns the object name (encoded) of this DatabaseHandle.
+ var fullName: String { get }
+
+ /// Collection returns the Collection with the given relative name.
+ /// The user blessing is derived from the context.
+ func collection(name: String) -> Collection?
+
+ /// CollectionForId returns the Collection with the given user blessing and name.
+ func collection(collectionId: CollectionId) -> Collection?
+
+ /// ListCollections returns a list of all Collection ids that the caller is
+ /// allowed to see. The list is sorted by blessing, then by name.
+ func listCollections() throws -> [CollectionId]
+
+ /// Returns a ResumeMarker that points to the current end of the event log.
+ func getResumeMarker() throws -> ResumeMarker
+}
+
+public protocol Database: DatabaseHandle, AccessController {
+ /// Create creates this Database.
+ /// TODO(sadovsky): Specify what happens if perms is nil.
+ func create(permissions: Permissions?) throws
+
+ /// Destroy destroys this Database, permanently removing all of its data.
+ /// TODO(sadovsky): Specify what happens to syncgroups.
+ func destroy() throws
+
+ // Exists returns true only if this Database exists. Insufficient permissions
+ // cause Exists to return false instead of an error.
+ func exists() throws -> Bool
+
+ /** BeginBatch creates a new batch. Instead of calling this function directly,
+ clients are encouraged to use the RunInBatch() helper function, which
+ detects "concurrent batch" errors and handles retries internally.
+
+ Default concurrency semantics:
+
+ - Reads (e.g. gets, scans) inside a batch operate over a consistent
+ snapshot taken during BeginBatch(), and will see the effects of prior
+ writes performed inside the batch.
+
+ - Commit() may fail with ErrConcurrentBatch, indicating that after
+ BeginBatch() but before Commit(), some concurrent routine wrote to a key
+ that matches a key or row-range read inside this batch.
+
+ - Other methods will never fail with error ErrConcurrentBatch, even if it
+ is known that Commit() will fail with this error.
+
+ Once a batch has been committed or aborted, subsequent method calls will
+ fail with no effect.
+
+ Concurrency semantics can be configured using BatchOptions.
+ TODO(sadovsky): Use varargs for options.
+ */
+ func beginBatch(options: BatchOptions?) throws -> BatchDatabase
+
+ /// Watch allows a client to watch for updates to the database. For each watch
+ /// request, the client will receive a reliable stream of watch events without
+ /// reordering. See watch.GlobWatcher for a detailed explanation of the
+ /// behavior.
+ ///
+ /// If a nil ResumeMarker is provided, the WatchStream will begin with a Change
+ /// batch containing the initial state. Otherwise, the WatchStream will contain
+ /// only changes since the provided ResumeMarker.
+ ///
+ /// TODO(sadovsky): Watch should return just a WatchStream, similar to how Scan
+ /// returns just a ScanStream.
+ func watch(collection: CollectionId, prefix: String, resumeMarker: ResumeMarker?) throws -> WatchStream
+
+ /// Syncgroup returns a handle to the syncgroup with the given name.
+ func syncgroup(sgName: String) -> Syncgroup
+
+ /// GetSyncgroupNames returns the names of all syncgroups attached to this
+ /// database.
+ /// TODO(sadovsky): Rename to ListSyncgroups, for parity with ListDatabases.
+ func getSyncgroupNames() throws -> [String]
+
+ /// CreateBlob creates a new blob and returns a handle to it.
+ func createBlob() throws -> Blob
+
+ /// Blob returns a handle to the blob with the given BlobRef.
+ func blob(blobRef: BlobRef) -> Blob
+
+ /// PauseSync pauses sync for this database. Incoming sync, as well as outgoing
+ /// sync of subsequent writes, will be disabled until ResumeSync is called.
+ /// PauseSync is idempotent.
+ func pauseSync() throws
+
+ /// ResumeSync resumes sync for this database. ResumeSync is idempotent.
+ func resumeSync() throws
+
+ /// Close cleans up any state associated with this database handle, including
+ /// closing the conflict resolution stream (if open).
+ func close()
+}
diff --git a/lib/Syncbase/Errors.swift b/lib/Syncbase/Errors.swift
new file mode 100644
index 0000000..95bd8ac
--- /dev/null
+++ b/lib/Syncbase/Errors.swift
@@ -0,0 +1,34 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+public enum SyncbaseError: ErrorType, CustomStringConvertible {
+ case NotInDevMode
+ case UnknownBatch
+ case NotBoundToBatch
+ case ReadOnlyBatch
+ case ConcurrentBatch
+ case BlobNotCommitted
+ case SyncgroupJoinFailed
+ case BadExecStreamHeader
+ case InvalidName(name: String)
+ case CorruptDatabase(path: String)
+
+ public var description: String {
+ switch (self) {
+ case NotInDevMode: return "not running with --dev=true"
+ case UnknownBatch: return "unknown batch, perhaps the server restarted"
+ case NotBoundToBatch: return "not bound to batch"
+ case ReadOnlyBatch: return "batch is read-only"
+ case ConcurrentBatch: return "concurrent batch"
+ case BlobNotCommitted: return "blob is not yet committed"
+ case SyncgroupJoinFailed: return "syncgroup join failed"
+ case BadExecStreamHeader: return "Exec stream header improperly formatted"
+ case InvalidName(let name): return "invalid name: \(name)"
+ case CorruptDatabase(let path):
+ return "database corrupt, moved to path \(path); client must create a new database"
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Syncbase/Id.swift b/lib/Syncbase/Id.swift
new file mode 100644
index 0000000..aefec42
--- /dev/null
+++ b/lib/Syncbase/Id.swift
@@ -0,0 +1,15 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+public struct DatabaseId {
+ let name: String
+ let blessing: String
+}
+
+public struct CollectionId {
+ let name: String
+ let blessing: String
+}
diff --git a/lib/Syncbase/Marshal.swift b/lib/Syncbase/Marshal.swift
new file mode 100644
index 0000000..b9c74dc
--- /dev/null
+++ b/lib/Syncbase/Marshal.swift
@@ -0,0 +1,201 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+public enum JsonErrors: ErrorType {
+ case ArrayContainsInvalidTypes
+ case DictionaryContainsInvalidTypes
+ case CastError(value: Any, target: Any)
+ case EncodingError
+}
+
+public enum JsonDataType: Int {
+ case Bool = 0,
+ Int, Int8, Int16, Int32, Int64,
+ UInt, UInt8, UInt16, UInt32, UInt64,
+ Float, Double,
+ String,
+ Array, Dictionary,
+ RawJson
+}
+
+public protocol SyncbaseJsonConvertible {
+ func toSyncbaseJson() throws -> (NSData, JsonDataType)
+}
+
+/// Serializes primitives to JSON using Apple's NSJSONSerializer. This function gets around
+/// Apple's limitation of requiring top
+private func hackNSJsonSerializer(obj: AnyObject) throws -> NSData {
+ var data: NSData?
+ try SyncbaseUtil.catchObjcException {
+ data = try? NSJSONSerialization.dataWithJSONObject([obj], options: [])
+ }
+ // Hack of first and last runes, which also happen to be single byte UTF8s
+ guard data != nil else {
+ throw JsonErrors.EncodingError
+ }
+ return data!.subdataWithRange(NSMakeRange(1, data!.length - 2))
+}
+
+extension Bool: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(self), JsonDataType.Bool)
+ }
+}
+
+// Wish we could put an extension on a protocol like IntegerLiterableConvertable or IntegerType
+// but we can't as of Swift 2.2, so we enumerate all the possiblities
+
+extension Int: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(self), JsonDataType.Int)
+ }
+}
+
+extension Int8: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(Int(self)), JsonDataType.Int8)
+ }
+}
+
+extension Int16: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(Int(self)), JsonDataType.Int16)
+ }
+}
+
+extension Int32: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(Int(self)), JsonDataType.Int32)
+ }
+}
+
+extension Int64: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(NSNumber(longLong: self)), JsonDataType.Int64)
+ }
+}
+
+extension UInt: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(self), JsonDataType.UInt)
+ }
+}
+
+extension UInt8: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(UInt(self)), JsonDataType.UInt8)
+ }
+}
+
+extension UInt16: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(UInt(self)), JsonDataType.UInt16)
+ }
+}
+
+extension UInt32: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(UInt(self)), JsonDataType.UInt32)
+ }
+}
+
+extension UInt64: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(NSNumber(unsignedLongLong: self)), JsonDataType.UInt64)
+ }
+}
+
+extension Float: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(self), JsonDataType.Float)
+ }
+}
+
+extension Double: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(self), JsonDataType.Double)
+ }
+}
+
+extension String: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try hackNSJsonSerializer(self), JsonDataType.String)
+ }
+}
+
+// Wish we could do this:
+// extension Array : SyncbaseJsonConvertible where Element: AnyObject {
+// but it's not valid Swift 2.2
+
+extension Array where Element: AnyObject {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try! NSJSONSerialization.dataWithJSONObject(self, options: []), JsonDataType.Array)
+ }
+}
+
+extension NSArray: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try! NSJSONSerialization.dataWithJSONObject(self, options: []), JsonDataType.Array)
+ }
+}
+
+extension NSDictionary: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (try! NSJSONSerialization.dataWithJSONObject(self, options: []), JsonDataType.Dictionary)
+ }
+}
+
+// Annoyingly we can't directly cast an Array that we have no type information on into an AnyObject
+// or similarly the same for Dictionary. So instead we're forced to copy all the elements and test
+// all individual types inside.
+extension Array: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ let copy: [AnyObject] = try self.map { elem in
+ guard let jsonable = elem as? AnyObject else {
+ throw JsonErrors.ArrayContainsInvalidTypes
+ }
+ return jsonable
+ }
+ var data: NSData?
+ try SyncbaseUtil.catchObjcException {
+ data = try? NSJSONSerialization.dataWithJSONObject(copy, options: [])
+ }
+ guard data != nil else {
+ throw JsonErrors.EncodingError
+ }
+ return (data!, JsonDataType.Array)
+ }
+}
+
+extension Dictionary: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ let copy = NSMutableDictionary()
+ try self.forEach { (key, value) in
+ guard let strKey = key as? String else {
+ throw JsonErrors.DictionaryContainsInvalidTypes
+ }
+ guard let jsonable = value as? AnyObject else {
+ throw JsonErrors.DictionaryContainsInvalidTypes
+ }
+ copy[strKey] = jsonable
+ }
+
+ var data: NSData?
+ try SyncbaseUtil.catchObjcException {
+ data = try? NSJSONSerialization.dataWithJSONObject(copy, options: [])
+ }
+ guard data != nil else {
+ throw JsonErrors.EncodingError
+ }
+ return (data!, JsonDataType.Dictionary)
+ }
+}
+
+extension NSData: SyncbaseJsonConvertible {
+ public func toSyncbaseJson() throws -> (NSData, JsonDataType) {
+ return (self, JsonDataType.RawJson)
+ }
+}
diff --git a/lib/Syncbase/Permissions.swift b/lib/Syncbase/Permissions.swift
new file mode 100644
index 0000000..0c8b356
--- /dev/null
+++ b/lib/Syncbase/Permissions.swift
@@ -0,0 +1,64 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+/// Permissions maps string tags to access lists specifying the blessings
+/// required to invoke methods with that tag.
+///
+/// These tags are meant to add a layer of interposition between the set of
+/// users (blessings, specifically) and the set of methods, much like "Roles" do
+/// in Role Based Access Control.
+/// (http://en.wikipedia.org/wiki/Role-based_access_control)
+public typealias Permissions = [String: BlessingPattern]
+
+/// BlessingPattern is a pattern that is matched by specific blessings.
+///
+/// A pattern can either be a blessing (slash-separated human-readable string)
+/// or a blessing ending in "/$". A pattern ending in "/$" is matched exactly
+/// by the blessing specified by the pattern string with the "/$" suffix stripped
+/// out. For example, the pattern "a/b/c/$" is matched by exactly by the blessing
+/// "a/b/c".
+///
+/// A pattern not ending in "/$" is more permissive, and is also matched by blessings
+/// that are extensions of the pattern (including the pattern itself). For example, the
+/// pattern "a/b/c" is matched by the blessings "a/b/c", "a/b/c/x", "a/b/c/x/y", etc.
+public typealias BlessingPattern = String
+
+public typealias PermissionsVersion = String
+
+/// AccessList represents a set of blessings that should be granted access.
+///
+/// See also: https://vanadium.github.io/glossary.html#access-list
+public struct AcccessList {
+ /// allowed denotes the set of blessings (represented as BlessingPatterns) that
+ /// should be granted access, unless blacklisted by an entry in notAllowed.
+ ///
+ /// For example:
+ /// allowed: {"alice:family"}
+ /// grants access to a principal that presents at least one of
+ /// "alice:family", "alice:family:friend", "alice:family:friend:spouse" etc.
+ /// as a blessing.
+ public let allowed: [BlessingPattern]
+
+ /// notAllowed denotes the set of blessings (and their delegates) that
+ /// have been explicitly blacklisted from the allowed set.
+ ///
+ /// For example:
+ /// allowed: {"alice:friend"}, notAllowed: {"alice:friend:bob"}
+ /// grants access to principals that present "alice:friend",
+ /// "alice:friend:carol" etc. but NOT to a principal that presents
+ /// "alice:friend:bob" or "alice:friend:bob:spouse" etc.
+ public let notAllowed: [BlessingPattern]
+}
+
+/// AccessController provides access control for various syncbase objects.
+public protocol AccessController {
+ /// setPermissions replaces the current Permissions for an object.
+ func setPermissions(perms: Permissions, version: PermissionsVersion) throws
+
+ /// GetPermissions returns the current Permissions for an object.
+ /// For detailed documentation, see Object.GetPermissions.
+ func getPermissions() throws -> (Permissions, PermissionsVersion)
+}
diff --git a/lib/Syncbase/Row.swift b/lib/Syncbase/Row.swift
new file mode 100644
index 0000000..556c7c8
--- /dev/null
+++ b/lib/Syncbase/Row.swift
@@ -0,0 +1,101 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+public protocol Row {
+ /// Key returns the key for this Row.
+ var key: String { get }
+
+ /// FullName returns the object name (encoded) of this Row.
+ var fullName: String { get }
+
+ /// Exists returns true only if this Row exists. Insufficient permissions cause
+ /// Exists to return false instead of an error.
+ /// TODO(ivanpi): Exists may fail with an error if higher levels of hierarchy
+ /// do not exist.
+ func exists() throws -> Bool
+
+ /**
+ Get loads the value stored in this Row into the given value. If the given
+ value's type does not match the stored value's type, get will throw an error.
+
+ Expected usage:
+
+ ```
+ var isRed: Bool?
+ try row.get(&isRed)
+ ```
+ */
+ func get<T: SyncbaseJsonConvertible>(inout value: T?) throws
+
+ /**
+ Get returns the value stored in this Row. If the given value's type does not match the
+ requested generic type, get will throw an error.
+
+ Expected usage:
+
+ ```
+ let isRed: Bool? = try row.get()
+ ```
+ */
+ func get<T: SyncbaseJsonConvertible>() throws -> T?
+
+ /// Put writes the given value for this Row.
+ func put(value: SyncbaseJsonConvertible) throws
+
+ /// Delete deletes this Row.
+ func delete() throws
+}
+
+/// RowRange represents all rows with keys in [start, limit).
+/// If limit is "", all rows with keys >= start are included.
+public protocol RowRange {
+ var start: String { get }
+ /// If limit is "", all rows with keys >= start are included.
+ var limit: String { get }
+}
+
+/// StandardRowRange represents all rows with keys in [start, limit).
+/// If limit is "", all rows with keys >= start are included.
+public struct StandardRowRange: RowRange {
+ public let start: String
+ /// If limit is "", all rows with keys >= start are included.
+ public let limit: String
+}
+
+public struct SingleRow: RowRange {
+ public let row: String
+
+ public var start: String {
+ return row
+ }
+
+ public var limit: String {
+ return row + "\0"
+ }
+}
+
+/// PrefixRange represents all rows with keys that have some prefix.
+public struct PrefixRange: RowRange {
+ public let prefix: String
+
+ /// Returns the start of the row range for the given prefix.
+ public var start: String {
+ return prefix
+ }
+
+ public var limit: String {
+ var utf8 = Array(prefix.utf8)
+ while utf8.count > 0 {
+ if utf8.last! == 255 {
+ utf8.removeLast() // chop off what would otherwise be a trailing \x00
+ } else {
+ utf8[utf8.count - 1] += 1 // add 1
+ break // no carry
+ }
+ }
+ return String(bytes: utf8, encoding: NSUTF8StringEncoding)!
+ }
+}
diff --git a/lib/Syncbase/Security.swift b/lib/Syncbase/Security.swift
index 34e1d8d..83d6e92 100644
--- a/lib/Syncbase/Security.swift
+++ b/lib/Syncbase/Security.swift
@@ -8,10 +8,10 @@
import VanadiumCore
public protocol VanadiumBlesser {
- func blessingsFromGoogle(googleOauthToken:String) -> Promise<NSData>
+ func blessingsFromGoogle(googleOauthToken: String) -> Promise<NSData>
}
-enum VanadiumUrls : String, URLStringConvertible {
+enum VanadiumUrls: String, URLStringConvertible {
case DevBlessings = "https://dev.v.io/auth/google/bless"
var URLString: String {
@@ -19,30 +19,30 @@
}
}
-enum BlessingOutputFormat : String {
+enum BlessingOutputFormat: String {
case Base64VOM = "base64vom"
}
-public enum VanadiumBlesserError : ErrorType {
+public enum VanadiumBlesserError: ErrorType {
case EmptyResult
- case NotBase64Encoded(invalid:String)
+ case NotBase64Encoded(invalid: String)
}
public extension VanadiumBlesser {
- public func blessingsFromGoogle(googleOauthToken:String) -> Promise<NSData> {
+ public func blessingsFromGoogle(googleOauthToken: String) -> Promise<NSData> {
// Make sure the Syncbase instance exists, which inits V23
let _ = Syncbase.instance
let p = Promise<NSData>()
do {
- let params:[String:AnyObject] = [
- "token": googleOauthToken,
- "public_key": try VanadiumCore.Principal.publicKey(),
- "output_format": BlessingOutputFormat.Base64VOM.rawValue]
+ let params: [String: AnyObject] = [
+ "token": googleOauthToken,
+ "public_key": try VanadiumCore.Principal.publicKey(),
+ "output_format": BlessingOutputFormat.Base64VOM.rawValue]
let request = Alamofire.request(.GET,
- VanadiumUrls.DevBlessings,
- parameters: params,
- encoding: ParameterEncoding.URLEncodedInURL,
- headers:nil)
+ VanadiumUrls.DevBlessings,
+ parameters: params,
+ encoding: ParameterEncoding.URLEncodedInURL,
+ headers: nil)
request.responseString { resp in
guard let base64 = resp.result.value else {
if let err = resp.result.error {
@@ -72,10 +72,10 @@
}
public protocol OAuthCredentials {
- var oauthToken:String { get }
+ var oauthToken: String { get }
}
-public struct GoogleCredentials : OAuthCredentials, VanadiumBlesser {
+public struct GoogleCredentials: OAuthCredentials, VanadiumBlesser {
public let oauthToken: String
public init(oauthToken: String) {
diff --git a/lib/Syncbase/Service.swift b/lib/Syncbase/Service.swift
new file mode 100644
index 0000000..d91508a
--- /dev/null
+++ b/lib/Syncbase/Service.swift
@@ -0,0 +1,22 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+/// Service represents a Vanadium Syncbase service.
+public protocol Service: AccessController {
+ /// FullName returns the object name (encoded) of this Service.
+ var fullName: String { get }
+
+ /// DatabaseForId returns the Database with the given app blessing and name (from the Id struct).
+ /// This is equivalent to calling database(id.name, id.blessing)
+ func database(databaseId: DatabaseId) -> Database
+
+ /// DatabaseForId returns the Database with the given name and app blessing.
+ func database(name: String) throws -> Database
+
+ /// ListDatabases returns a list of all Database ids that the caller is allowed to see.
+ /// The list is sorted by blessing, then by name.
+ func listDatabases() throws -> [DatabaseId]
+}
diff --git a/lib/Syncbase/Stream.swift b/lib/Syncbase/Stream.swift
new file mode 100644
index 0000000..6d13773
--- /dev/null
+++ b/lib/Syncbase/Stream.swift
@@ -0,0 +1,72 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+/// Base protocol for iterating through elements of unknown length.
+public protocol Stream: GeneratorType {
+ /// Err returns a non-nil error iff the stream encountered any errors. Err does
+ /// not block.
+ func err() -> SyncbaseError?
+
+ /// Cancel notifies the stream provider that it can stop producing elements.
+ /// The client must call Cancel if it does not iterate through all elements
+ /// (i.e. until Advance returns false). Cancel is idempotent and can be called
+ /// concurrently with a goroutine that is iterating via Advance.
+ /// Cancel causes Advance to subsequently return false. Cancel does not block.
+ mutating func cancel()
+}
+
+/// Typed-stream backed by anonymous callbacks
+public struct AnonymousStream<T>: Stream {
+ public typealias Element = T
+ public typealias FetchNextFunction = Void -> (T?, SyncbaseError?)
+ let fetchNextFunction: FetchNextFunction
+ public typealias CancelFunction = Void -> Void
+ let cancelFunction: CancelFunction
+ private var lastErr: SyncbaseError?
+ private var isCancelled: Bool = false
+ internal init(fetchNextFunction: FetchNextFunction, cancelFunction: CancelFunction) {
+ self.fetchNextFunction = fetchNextFunction
+ self.cancelFunction = cancelFunction
+ }
+
+ /// Advance to the next element and return it, or `nil` if no next
+ /// element exists.
+ ///
+ /// - Requires: `next()` has not been applied to a copy of `self`
+ /// since the copy was made, and no preceding call to `self.next()`
+ /// has returned `nil`. Specific implementations of this protocol
+ /// are encouraged to respond to violations of this requirement by
+ /// calling `preconditionFailure("...")`.
+ public mutating func next() -> T? {
+ guard !isCancelled else {
+ return nil
+ }
+ let (result, err) = fetchNextFunction()
+ if let ret = result {
+ return ret
+ }
+ lastErr = err
+ return nil
+ }
+
+ /// Err returns a non-nil error iff the stream encountered any errors. Err does
+ /// not block.
+ public func err() -> SyncbaseError? {
+ return lastErr
+ }
+
+ /// Cancel notifies the stream provider that it can stop producing elements.
+ /// The client must call Cancel if it does not iterate through all elements
+ /// (i.e. until Advance returns false). Cancel is idempotent and can be called
+ /// concurrently with a goroutine that is iterating via Advance.
+ /// Cancel causes Advance to subsequently return false. Cancel does not block.
+ public mutating func cancel() {
+ if !isCancelled {
+ cancelFunction()
+ isCancelled = true
+ }
+ }
+}
diff --git a/lib/Syncbase/Syncbase.h b/lib/Syncbase/Syncbase.h
index 514ed51..8e90958 100644
--- a/lib/Syncbase/Syncbase.h
+++ b/lib/Syncbase/Syncbase.h
@@ -9,3 +9,5 @@
//! Project version string for Syncbase.
FOUNDATION_EXPORT const unsigned char SyncbaseVersionString[];
+
+#import "Util.h"
\ No newline at end of file
diff --git a/lib/Syncbase/Syncbase.swift b/lib/Syncbase/Syncbase.swift
index 66056f0..3681463 100644
--- a/lib/Syncbase/Syncbase.swift
+++ b/lib/Syncbase/Syncbase.swift
@@ -3,44 +3,80 @@
// license that can be found in the LICENSE file.
import Foundation
-
import VanadiumCore
-public let instance = Syncbase.instance
+//public let instance = Syncbase.instance
-public class Syncbase {
- private static var _instance: Syncbase? = nil
+public struct Syncbase: Service {
+ // TODO(zinman): Figure out what the local name is supposed to be be
+ private static let kLocalName = ""
+ public let fullName: String
+ private static var _localInstance: Syncbase? = nil
- /// The singleton instance of Syncbase. This is the primary class that houses the simplified
- /// API.
+ /// The singleton instance of Syncbase that represents the local store. This is the primary
+ /// Syncbase client you want to work with, unless you explicitly want to connect to a remote
+ /// Syncbase via RPC (then use the constructor with its object name passed)
///
/// You won't be able to sync with anybody unless you grant yourself a blessing via the authorize
/// method.
public static var instance: Syncbase {
get {
- if (_instance == nil) {
+ if (_localInstance == nil) {
do {
- _instance = try Syncbase()
+ _localInstance = try Syncbase(fullName: kLocalName)
} catch let err {
VanadiumCore.log.warning("Couldn't instantiate an instance of Syncbase: \(err)")
}
}
- return _instance!
+ return _localInstance!
}
+
set {
- if (_instance != nil) {
- fatalError("You cannot create another instance of V23")
+ if (_localInstance != nil) {
+ fatalError("You cannot create another local instance of Syncbase")
}
- _instance = newValue
+ _localInstance = newValue
}
}
- /// Private constructor for V23.
- private init() throws {
- try V23.configure()
+ /**
+ Returns a new client handle to a syncbase service running at the given name. The common scenario
+ is to only use Syncbase.instance for the local store -- this constructor is used for connecting
+ to remote Syncbases.
+
+ - Parameter fullName: full (i.e., object) name of the syncbase service
+ */
+ public init(fullName: String) throws {
+ self.fullName = fullName
+ // STUB
}
- deinit {
+ /// Create a database using the relative name and user's blessings.
+ public func database(name: String) throws -> Database {
+ return database(DatabaseId(name: name, blessing: try Principal.blessingsDebugString()))
+ }
+ /// DatabaseForId returns the Database with the given app blessing and name (from the Id struct).
+ public func database(databaseId: DatabaseId) -> Database {
+ preconditionFailure("Implement me")
+ }
+
+ /// ListDatabases returns a list of all Database ids that the caller is allowed to see.
+ /// The list is sorted by blessing, then by name.
+ public func listDatabases() throws -> [DatabaseId] {
+ preconditionFailure("Implement me")
+ }
+}
+
+extension Syncbase: AccessController {
+ /// setPermissions replaces the current Permissions for an object.
+ public func setPermissions(perms: Permissions, version: PermissionsVersion) throws {
+ preconditionFailure("Implement me")
+ }
+
+ /// getPermissions returns the current Permissions for an object.
+ /// For detailed documentation, see Object.GetPermissions.
+ public func getPermissions() throws -> (Permissions, PermissionsVersion) {
+ preconditionFailure("Implement me")
}
}
diff --git a/lib/Syncbase/Syncgroup.swift b/lib/Syncbase/Syncgroup.swift
new file mode 100644
index 0000000..8ea8352
--- /dev/null
+++ b/lib/Syncbase/Syncgroup.swift
@@ -0,0 +1,89 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+public struct VersionedSpec {
+ let spec: SyncgroupSpec
+ let version: String
+}
+
+public protocol Syncgroup {
+ /// Create creates a new syncgroup with the given spec.
+ ///
+ /// Requires: Client must have at least Read access on the Database; all
+ /// Collections specified in prefixes must exist; Client must have at least
+ /// Read access on each of the Collection ACLs.
+ func create(spec: SyncgroupSpec, myInfo: SyncgroupMemberInfo) throws
+
+ /// Join joins a syncgroup.
+ ///
+ /// Requires: Client must have at least Read access on the Database and on the
+ /// syncgroup ACL.
+ func join(myInfo: SyncgroupMemberInfo) throws -> SyncgroupSpec
+
+ /// Leave leaves the syncgroup. Previously synced data will continue
+ /// to be available.
+ ///
+ /// Requires: Client must have at least Read access on the Database.
+ func leave() throws
+
+ /// Destroy destroys the syncgroup. Previously synced data will
+ /// continue to be available to all members.
+ ///
+ /// Requires: Client must have at least Read access on the Database, and must
+ /// have Admin access on the syncgroup ACL.
+ func destroy() throws
+
+ /// Eject ejects a member from the syncgroup. The ejected member
+ /// will not be able to sync further, but will retain any data it has already
+ /// synced.
+ ///
+ /// Requires: Client must have at least Read access on the Database, and must
+ /// have Admin access on the syncgroup ACL.
+ func eject(member: String) throws
+
+ /// GetSpec gets the syncgroup spec. version allows for atomic
+ /// read-modify-write of the spec - see comment for SetSpec.
+ ///
+ /// Requires: Client must have at least Read access on the Database and on the
+ /// syncgroup ACL.
+ func getSpec() throws -> VersionedSpec
+
+ /// SetSpec sets the syncgroup spec. version may be either empty or
+ /// the value from a previous Get. If not empty, Set will only succeed if the
+ /// current version matches the specified one.
+ ///
+ /// Requires: Client must have at least Read access on the Database, and must
+ /// have Admin access on the syncgroup ACL.
+ func setSpec(versionedSpec: VersionedSpec) throws
+
+ /// GetMembers gets the info objects for members of the syncgroup.
+ ///
+ /// Requires: Client must have at least Read access on the Database and on the
+ /// syncgroup ACL.
+ func getMembers() throws -> [String: SyncgroupMemberInfo]
+}
+
+public struct SyncgroupSpec {
+ /// Human-readable description of this syncgroup.
+ let description: String
+ /// Permissions governing access to this syncgroup.
+ let permissions: Permissions
+ /// Data (collectionId-rowPrefix pairs) covered by this syncgroup.
+ let prefixes: [CollectionRow]
+ /// Mount tables at which to advertise this syncgroup, for rendezvous purposes.
+ /// (Note that in addition to these mount tables, Syncbase also uses
+ /// network-neighborhood-based discovery for rendezvous.)
+ /// We expect most clients to specify a single mount table, but we accept an
+ /// array of mount tables to permit the mount table to be changed over time
+ /// without disruption.
+ /// TODO(hpucha): Figure out a convention for advertising syncgroups in the
+ /// mount table.
+ let mountTables: [String]
+ /// Specifies the privacy of this syncgroup. More specifically, specifies
+ /// whether blobs in this syncgroup can be served to clients presenting
+ /// blobrefs obtained from other syncgroups.
+ let isPrivate: Bool
+}
diff --git a/lib/Syncbase/Util.h b/lib/Syncbase/Util.h
new file mode 100644
index 0000000..f5ddc42
--- /dev/null
+++ b/lib/Syncbase/Util.h
@@ -0,0 +1,9 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#import <Foundation/Foundation.h>
+
+@interface SyncbaseUtil : NSObject
++ (BOOL) catchObjcException:(void (^ _Nonnull)())block error:(NSError * _Nullable * _Nonnull)error;
+@end
\ No newline at end of file
diff --git a/lib/Syncbase/Util.m b/lib/Syncbase/Util.m
new file mode 100644
index 0000000..750b0d5
--- /dev/null
+++ b/lib/Syncbase/Util.m
@@ -0,0 +1,24 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#import "Util.h"
+
+@implementation SyncbaseUtil
+
++ (BOOL) catchObjcException:(void (^ _Nonnull)())block error:(NSError * _Nullable * _Nonnull)error {
+ @try {
+ block();
+ return true;
+ } @catch (NSException *exception) {
+ if (!error) return false;
+ *error = [NSError errorWithDomain:@"io.v.Syncbase"
+ code:-100
+ userInfo:@{@"name": exception.name,
+ @"reason": exception.reason,
+ @"userInfo": exception.userInfo}];
+ return false;
+ }
+}
+
+@end
\ No newline at end of file
diff --git a/lib/Syncbase/Util.swift b/lib/Syncbase/Util.swift
new file mode 100644
index 0000000..4398139
--- /dev/null
+++ b/lib/Syncbase/Util.swift
@@ -0,0 +1,122 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+internal extension NSNumber {
+ /// Returns true if value can be cast to NSNumber or NSNumber?
+ internal static func isNSNumber<V>(value: V) -> Bool {
+ // Can't do this:
+ // if value is NSNumber {
+ // because it will always succeed through type coercion, even if value is an Int.
+ // Instead we use the dynamic (runtime) type
+ let type = value.dynamicType
+ return type is NSNumber.Type || type is NSNumber?.Type
+ }
+
+ /// Returns true if target can be set/cast from this NSNumber without precision-loss or type
+ /// conversion. For example ```NSNumber.init(bool: true) as Float``` will return a Swift Float of
+ /// value 1, where as this function would return false as the types are unrelated -- only
+ /// casting as Bool would return true. This function checks for 32/64-bit correctness with types.
+ ///
+ /// Caveat: Currently this function makes no attempt to determine signed/unsigned correctness of
+ /// the underlying data, although this is sometimes knowable with NSNumber.objCType.
+ internal func isTargetCastable<T>(inout target: T?) -> Bool {
+ // Allow matching types of this size, and bigger. Signed and unsigned of same size are not allowed.
+ // Swift: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html
+ // C types: https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Major64-BitChanges/Major64-BitChanges.html
+ switch CFNumberGetType(self as CFNumberRef) {
+ // Obj-C bool is stored as Char, but if it's a @(YES) or @(NO) they are all a shared instance.
+ case .CharType where (unsafeAddressOf(self) == unsafeAddressOf(kCFBooleanFalse) ||
+ unsafeAddressOf(self) == unsafeAddressOf(kCFBooleanTrue)):
+ // We know we have a bool... make sure it's compatible
+ let type = target.dynamicType
+ guard type == Bool?.self || type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ // Handle fixed lengths on 32/64 bit
+ case .SInt8Type, .CharType:
+ let type = target.dynamicType
+ guard type == Int?.self || type == UInt?.self ||
+ type == Int8?.self || type == Int16?.self || type == Int32?.self || type == Int64?.self ||
+ type == UInt8?.self || type == UInt16?.self || type == UInt32?.self || type == UInt64?.self ||
+ type == CChar?.self || type == CShort?.self || type == CInt?.self || type == CLong?.self || type == CLongLong?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ case .SInt16Type, .ShortType:
+ let type = target.dynamicType
+ guard type == Int?.self || type == UInt?.self ||
+ type == Int16?.self || type == Int32?.self || type == Int64?.self ||
+ type == UInt16?.self || type == UInt32?.self || type == UInt64?.self ||
+ type == CShort?.self || type == CInt?.self || type == CLong?.self || type == CLongLong?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ case .SInt32Type, .IntType:
+ let type = target.dynamicType
+ guard type == Int?.self || type == UInt?.self ||
+ type == Int32?.self || type == Int64?.self ||
+ type == UInt32?.self || type == UInt64?.self ||
+ type == CInt?.self || type == CLong?.self || type == CLongLong?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ case .SInt64Type, .LongLongType:
+ let type = target.dynamicType
+ guard (type == Int?.self && sizeof(Int) == sizeof(CLongLong)) ||
+ (type == UInt?.self && sizeof(UInt) == sizeof(CLongLong)) ||
+ type == Int64?.self || type == UInt64?.self ||
+ type == CLongLong?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ case .Float32Type, .FloatType:
+ let type = target.dynamicType
+ guard type == Float?.self ||
+ type == Double?.self ||
+ type == Float32?.self ||
+ type == Float64?.self ||
+ type == CFloat?.self || type == CDouble?.self || type == CGFloat?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ case .Float64Type, .DoubleType: /* 64-bit IEEE 754 */
+ let type = target.dynamicType
+ guard type == Double?.self ||
+ type == Float64?.self ||
+ type == CDouble?.self || type == CGFloat?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+
+ // Handle 32/64-bit types
+ case .LongType, .NSIntegerType:
+ let type = target.dynamicType
+ guard type == Int?.self || type == UInt?.self ||
+ (type == Int32?.self && sizeof(Int32) == sizeof(NSInteger)) ||
+ (type == UInt32?.self && sizeof(UInt32) == sizeof(NSInteger)) ||
+ type == Int64?.self || type == UInt64?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self ||
+ type == CLong?.self || type == CLongLong?.self else {
+ return false
+ }
+ case .CGFloatType:
+ let type = target.dynamicType
+ guard (type == Float?.self && sizeof(CGFloat) == sizeof(Float)) ||
+ type == Double?.self ||
+ (type == Float32?.self && sizeof(CGFloat) == sizeof(Float32)) ||
+ type == Float64?.self ||
+ type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else {
+ return false
+ }
+ // Misc
+ case .CFIndexType:
+ guard target.dynamicType == CFIndex?.self else {
+ return false
+ }
+ }
+ return true
+ }
+}
diff --git a/lib/Syncbase/Vom.swift b/lib/Syncbase/Vom.swift
new file mode 100644
index 0000000..ad4489f
--- /dev/null
+++ b/lib/Syncbase/Vom.swift
@@ -0,0 +1,7 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+typealias VomRawBytes = NSData
diff --git a/lib/Syncbase/Watch.swift b/lib/Syncbase/Watch.swift
new file mode 100644
index 0000000..3ec0ac6
--- /dev/null
+++ b/lib/Syncbase/Watch.swift
@@ -0,0 +1,48 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+
+public struct ResumeMarker {
+ internal let data: [UInt8]
+}
+
+public struct WatchChange {
+ public enum ChangeType: Int {
+ case PutChange = 0, DeleteDelete
+ }
+
+ /// Collection is the id of the collection that contains the changed row.
+ let collectionId: CollectionId
+
+ /// Row is the key of the changed row.
+ let row: String
+
+ /// ChangeType describes the type of the change. If ChangeType is PutChange,
+ /// then the row exists in the collection, and Value can be called to obtain
+ /// the new value for this row. If ChangeType is DeleteChange, then the row was
+ /// removed from the collection.
+ let changeType: ChangeType
+
+ /// value is the new value for the row if the ChangeType is PutChange, or nil
+ /// otherwise.
+ let value: VomRawBytes
+
+ /// ResumeMarker provides a compact representation of all the messages that
+ /// have been received by the caller for the given Watch call.
+ /// This marker can be provided in the Request message to allow the caller
+ /// to resume the stream watching at a specific point without fetching the
+ /// initial state.
+ let resumeMarker: ResumeMarker
+
+ /// FromSync indicates whether the change came from sync. If FromSync is false,
+ /// then the change originated from the local device.
+ let isFromSync: Bool
+
+ /// If true, this WatchChange is followed by more WatchChanges that are in the
+ /// same batch as this WatchChange.
+ let isContinued: Bool
+}
+
+public typealias WatchStream = AnonymousStream<WatchChange>
diff --git a/lib/SyncbaseTests/JsonTests.swift b/lib/SyncbaseTests/JsonTests.swift
new file mode 100644
index 0000000..3e6eaf5
--- /dev/null
+++ b/lib/SyncbaseTests/JsonTests.swift
@@ -0,0 +1,99 @@
+//
+// JsonTests.swift
+// Vanadium
+//
+// Created by zinman on 4/26/16.
+// Copyright © 2016 Google, Inc. All rights reserved.
+//
+
+import XCTest
+@testable import Syncbase
+
+class JsonTests: XCTestCase {
+ // Emulate syncbase's put
+ func toJson(any:SyncbaseJsonConvertible) -> (NSData, JsonDataType) {
+ return try! any.toSyncbaseJson()
+ }
+
+ func toString(json:NSData) -> String {
+ return String(data:json, encoding: NSUTF8StringEncoding)!
+ }
+
+ func testBasicEncoding() {
+ var (data, type) = try! 5.toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5")
+ XCTAssertEqual(type, JsonDataType.Int)
+
+ (data, type) = toJson(5)
+ XCTAssertEqual(toString(data), "5")
+ XCTAssertEqual(type, JsonDataType.Int)
+
+ (data, type) = try! true.toSyncbaseJson()
+ XCTAssertEqual(toString(data), "true")
+ XCTAssertEqual(type, JsonDataType.Bool)
+
+ (data, type) = try! false.toSyncbaseJson()
+ XCTAssertEqual(toString(data), "false")
+ XCTAssertEqual(type, JsonDataType.Bool)
+
+ (data, type) = try! Int8(5).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5")
+ XCTAssertEqual(type, JsonDataType.Int8)
+
+ (data, type) = try! Int16(5).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5")
+ XCTAssertEqual(type, JsonDataType.Int16)
+
+ (data, type) = try! Int32(5).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5")
+ XCTAssertEqual(type, JsonDataType.Int32)
+
+ (data, type) = try! Int64(5).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5")
+ XCTAssertEqual(type, JsonDataType.Int64)
+
+ (data, type) = try! Int64(Int64(Int32.max) + 10).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "\(Int64(Int32.max) + 10)")
+ XCTAssertEqual(type, JsonDataType.Int64)
+
+ (data, type) = try! Float(5.0).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5") // 5 is ok here since we pass knowledge it's a float
+ XCTAssertEqual(type, JsonDataType.Float)
+
+ (data, type) = try! Double(5.1).toSyncbaseJson()
+ XCTAssertEqual(toString(data), "5.1") // 5 is ok here since we pass knowledge it's a float
+ XCTAssertEqual(type, JsonDataType.Double)
+
+ (data, type) = try! "Hello world! 👠".toSyncbaseJson()
+ XCTAssertEqual(toString(data), "\"Hello world! 👠\"")
+ XCTAssertEqual(type, JsonDataType.String)
+
+ (data, type) = try! [1,2,3,4].toSyncbaseJson()
+ XCTAssertEqual(toString(data), "[1,2,3,4]")
+ XCTAssertEqual(type, JsonDataType.Array)
+
+ (data, type) = toJson([1,2,3,4])
+ XCTAssertEqual(toString(data), "[1,2,3,4]")
+ XCTAssertEqual(type, JsonDataType.Array)
+
+ (data, type) = try! ["a", "b", "c"].toSyncbaseJson()
+ XCTAssertEqual(toString(data), "[\"a\",\"b\",\"c\"]")
+ XCTAssertEqual(type, JsonDataType.Array)
+
+ (data, type) = try! ["a":1].toSyncbaseJson()
+ XCTAssertEqual(toString(data), "{\"a\":1}")
+ XCTAssertEqual(type, JsonDataType.Dictionary)
+
+ (data, type) = try! ["b": true].toSyncbaseJson()
+ XCTAssertEqual(toString(data), "{\"b\":true}")
+ XCTAssertEqual(type, JsonDataType.Dictionary)
+
+ (data, type) = try! ["c": "👠"].toSyncbaseJson()
+ XCTAssertEqual(toString(data), "{\"c\":\"👠\"}")
+ XCTAssertEqual(type, JsonDataType.Dictionary)
+
+ (data, type) = toJson(["c": "👠"])
+ XCTAssertEqual(toString(data), "{\"c\":\"👠\"}")
+ XCTAssertEqual(type, JsonDataType.Dictionary)
+ }
+}
diff --git a/lib/SyncbaseTests/NSNumberTests.swift b/lib/SyncbaseTests/NSNumberTests.swift
new file mode 100644
index 0000000..6e91b6b
--- /dev/null
+++ b/lib/SyncbaseTests/NSNumberTests.swift
@@ -0,0 +1,144 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import XCTest
+@testable import Syncbase
+
+class NSNumberTests: XCTestCase {
+ func testISNSNumber() {
+ XCTAssertTrue(NSNumber.isNSNumber(NSNumber(bool: true)))
+ XCTAssertFalse(NSNumber.isNSNumber(true))
+ XCTAssertTrue(NSNumber.isNSNumber(true as AnyObject))
+ XCTAssertTrue(NSNumber.isNSNumber(true as NSNumber))
+ XCTAssertFalse(NSNumber.isNSNumber(""))
+ XCTAssertFalse(NSNumber.isNSNumber(5))
+ XCTAssertTrue(NSNumber.isNSNumber(5 as AnyObject))
+ }
+
+ func testConservationOfPrecision() {
+ var anyobj:AnyObject?
+ var nsnumber:NSNumber?
+ var bool:Bool? = true
+ var int:Int? = Int.max
+ var int8:Int8? = Int8.max
+ var int16:Int16? = Int16.max
+ var int32:Int32? = Int32.max
+ var int64:Int64? = Int64.max
+ var float:Float? = Float(Int32.max)
+ var double:Double? = Double(Int64.max)
+ var long:CLong? = CLong.max
+ var uint:UInt? = UInt.max
+ var uint8:UInt8? = UInt8.max
+ var uint16:UInt16? = UInt16.max
+ var uint32:UInt32? = UInt32.max
+ var uint64:UInt64? = UInt64.max
+ var ulong:CUnsignedLong? = CUnsignedLong.max
+
+ // Preserve booleans
+ XCTAssertTrue((true as NSNumber).isTargetCastable(&bool))
+ XCTAssertTrue((true as NSNumber).isTargetCastable(&anyobj))
+ XCTAssertTrue((true as NSNumber).isTargetCastable(&nsnumber))
+ XCTAssertFalse((true as NSNumber).isTargetCastable(&int))
+ XCTAssertFalse((true as NSNumber).isTargetCastable(&float))
+ XCTAssertFalse((true as NSNumber).isTargetCastable(&long))
+
+ // Preserved size
+ XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int8))
+ XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int16))
+ XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int32))
+ XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&int))
+ XCTAssertTrue(NSNumber(char: int8!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(char: int8!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(char: int8!).isTargetCastable(&double))
+
+ XCTAssertFalse(NSNumber(short: int16!).isTargetCastable(&int8))
+ XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int16))
+ XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int32))
+ XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&int))
+ XCTAssertTrue(NSNumber(short: int16!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(short: int16!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(short: int16!).isTargetCastable(&double))
+
+ XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&int16))
+ XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&int32))
+ XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&int))
+ XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&uint))
+ XCTAssertTrue(NSNumber(int: int32!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(int: int32!).isTargetCastable(&double))
+
+ if sizeof(CLong) == sizeof(Int64) {
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int16))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int32))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&long))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&uint))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&double))
+ } else {
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&int16))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int32))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(long: int!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(long: int!).isTargetCastable(&double))
+ }
+
+ XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int16))
+ XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int32))
+ if sizeof(Int) == sizeof(Int64) {
+ XCTAssertTrue(NSNumber(longLong: int64!).isTargetCastable(&int))
+ } else {
+ XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&int))
+ }
+ XCTAssertTrue(NSNumber(longLong: int64!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(longLong: int64!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(longLong: int64!).isTargetCastable(&double))
+
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int16))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int32))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint8))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint16))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint32))
+ XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&uint64))
+ XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&ulong))
+ // TODO(zinman): Be smarter about when we allow this or not
+ if sizeof(Int) == sizeof(Int64) {
+ XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int))
+ } else {
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int))
+ }
+ XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&int64))
+ XCTAssertTrue(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&float))
+ XCTAssertFalse(NSNumber(unsignedLongLong: uint64!).isTargetCastable(&double))
+
+ XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int16))
+ XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int32))
+ XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int64))
+ XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&int))
+ XCTAssertFalse(NSNumber(float: float!).isTargetCastable(&long))
+ XCTAssertTrue(NSNumber(float: float!).isTargetCastable(&float))
+ XCTAssertTrue(NSNumber(float: float!).isTargetCastable(&double))
+
+ XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int8))
+ XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int16))
+ XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int32))
+ XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&int64))
+ XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&long))
+ XCTAssertFalse(NSNumber(double: double!).isTargetCastable(&float))
+ XCTAssertTrue(NSNumber(double: double!).isTargetCastable(&double))
+ }
+}
diff --git a/lib/SyncbaseTests/SyncbaseTests.swift b/lib/SyncbaseTests/SyncbaseTests.swift
deleted file mode 100644
index 08a20f0..0000000
--- a/lib/SyncbaseTests/SyncbaseTests.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-import XCTest
-@testable import Syncbase
-
-class SyncbaseTests: XCTestCase {
- override func setUp() {
- super.setUp()
- // Put setup code here. This method is called before the invocation of each test method in the class.
- }
-
- override func tearDown() {
- // Put teardown code here. This method is called after the invocation of each test method in the class.
- super.tearDown()
- }
-}
diff --git a/lib/Vanadium.xcodeproj/project.pbxproj b/lib/Vanadium.xcodeproj/project.pbxproj
index 5c3f4a2..bf40ad1 100644
--- a/lib/Vanadium.xcodeproj/project.pbxproj
+++ b/lib/Vanadium.xcodeproj/project.pbxproj
@@ -7,10 +7,28 @@
objects = {
/* Begin PBXBuildFile section */
+ 305A8E751CCF64E7007656D0 /* Batch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E641CCF64E7007656D0 /* Batch.swift */; };
+ 305A8E761CCF64E7007656D0 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E651CCF64E7007656D0 /* Blob.swift */; };
+ 305A8E771CCF64E7007656D0 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E661CCF64E7007656D0 /* Collection.swift */; };
+ 305A8E781CCF64E7007656D0 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E671CCF64E7007656D0 /* Database.swift */; };
+ 305A8E791CCF64E7007656D0 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E681CCF64E7007656D0 /* Errors.swift */; };
+ 305A8E7A1CCF64E7007656D0 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E691CCF64E7007656D0 /* Id.swift */; };
+ 305A8E7B1CCF64E7007656D0 /* Marshal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6A1CCF64E7007656D0 /* Marshal.swift */; };
+ 305A8E7C1CCF64E7007656D0 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6B1CCF64E7007656D0 /* Permissions.swift */; };
+ 305A8E7D1CCF64E7007656D0 /* Row.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6C1CCF64E7007656D0 /* Row.swift */; };
+ 305A8E7E1CCF64E7007656D0 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6D1CCF64E7007656D0 /* Service.swift */; };
+ 305A8E7F1CCF64E7007656D0 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6E1CCF64E7007656D0 /* Stream.swift */; };
+ 305A8E801CCF64E7007656D0 /* Syncgroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E6F1CCF64E7007656D0 /* Syncgroup.swift */; };
+ 305A8E811CCF64E7007656D0 /* Util.h in Headers */ = {isa = PBXBuildFile; fileRef = 305A8E701CCF64E7007656D0 /* Util.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 305A8E821CCF64E7007656D0 /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E711CCF64E7007656D0 /* Util.m */; };
+ 305A8E831CCF64E7007656D0 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E721CCF64E7007656D0 /* Util.swift */; };
+ 305A8E841CCF64E7007656D0 /* Vom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E731CCF64E7007656D0 /* Vom.swift */; };
+ 305A8E851CCF64E7007656D0 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A8E741CCF64E7007656D0 /* Watch.swift */; };
+ 30B111C41CCEA9C7000F3750 /* NSNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B111C31CCEA9C7000F3750 /* NSNumberTests.swift */; };
+ 30B1120B1CCF586C000F3750 /* JsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B1120A1CCF586C000F3750 /* JsonTests.swift */; };
93B980D31CAB9B6100CE42E0 /* Principal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980D21CAB9B6100CE42E0 /* Principal.swift */; };
93B980DC1CAB9D3D00CE42E0 /* Syncbase.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */; settings = {ATTRIBUTES = (Public, ); }; };
93B980E31CAB9D3D00CE42E0 /* Syncbase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B980D91CAB9D3D00CE42E0 /* Syncbase.framework */; };
- 93B980E81CAB9D3D00CE42E0 /* SyncbaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980E71CAB9D3D00CE42E0 /* SyncbaseTests.swift */; };
93B980F31CAB9D6E00CE42E0 /* Security.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980F21CAB9D6E00CE42E0 /* Security.swift */; };
93B980F51CAB9D9000CE42E0 /* Syncbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B980F41CAB9D9000CE42E0 /* Syncbase.swift */; };
93BC21591C9E199F0074C612 /* VanadiumCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 93BC21581C9E199F0074C612 /* VanadiumCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -118,12 +136,30 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ 305A8E641CCF64E7007656D0 /* Batch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Batch.swift; sourceTree = "<group>"; };
+ 305A8E651CCF64E7007656D0 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = "<group>"; };
+ 305A8E661CCF64E7007656D0 /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
+ 305A8E671CCF64E7007656D0 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
+ 305A8E681CCF64E7007656D0 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
+ 305A8E691CCF64E7007656D0 /* Id.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
+ 305A8E6A1CCF64E7007656D0 /* Marshal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Marshal.swift; sourceTree = "<group>"; };
+ 305A8E6B1CCF64E7007656D0 /* Permissions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
+ 305A8E6C1CCF64E7007656D0 /* Row.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Row.swift; sourceTree = "<group>"; };
+ 305A8E6D1CCF64E7007656D0 /* Service.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = "<group>"; };
+ 305A8E6E1CCF64E7007656D0 /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = "<group>"; };
+ 305A8E6F1CCF64E7007656D0 /* Syncgroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syncgroup.swift; sourceTree = "<group>"; };
+ 305A8E701CCF64E7007656D0 /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = "<group>"; };
+ 305A8E711CCF64E7007656D0 /* Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Util.m; sourceTree = "<group>"; };
+ 305A8E721CCF64E7007656D0 /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
+ 305A8E731CCF64E7007656D0 /* Vom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vom.swift; sourceTree = "<group>"; };
+ 305A8E741CCF64E7007656D0 /* Watch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Watch.swift; sourceTree = "<group>"; };
+ 30B111C31CCEA9C7000F3750 /* NSNumberTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSNumberTests.swift; sourceTree = "<group>"; };
+ 30B1120A1CCF586C000F3750 /* JsonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonTests.swift; sourceTree = "<group>"; };
93B980D21CAB9B6100CE42E0 /* Principal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Principal.swift; path = security/Principal.swift; sourceTree = "<group>"; };
93B980D91CAB9D3D00CE42E0 /* Syncbase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Syncbase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Syncbase.h; sourceTree = "<group>"; };
93B980DD1CAB9D3D00CE42E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
93B980E21CAB9D3D00CE42E0 /* SyncbaseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncbaseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 93B980E71CAB9D3D00CE42E0 /* SyncbaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncbaseTests.swift; sourceTree = "<group>"; };
93B980E91CAB9D3D00CE42E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
93B980F21CAB9D6E00CE42E0 /* Security.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Security.swift; sourceTree = "<group>"; };
93B980F41CAB9D9000CE42E0 /* Syncbase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syncbase.swift; sourceTree = "<group>"; };
@@ -202,10 +238,27 @@
93B980DA1CAB9D3D00CE42E0 /* Syncbase */ = {
isa = PBXGroup;
children = (
- 93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */,
+ 305A8E641CCF64E7007656D0 /* Batch.swift */,
+ 305A8E651CCF64E7007656D0 /* Blob.swift */,
+ 305A8E661CCF64E7007656D0 /* Collection.swift */,
+ 305A8E671CCF64E7007656D0 /* Database.swift */,
+ 305A8E681CCF64E7007656D0 /* Errors.swift */,
+ 305A8E691CCF64E7007656D0 /* Id.swift */,
93B980DD1CAB9D3D00CE42E0 /* Info.plist */,
+ 305A8E6A1CCF64E7007656D0 /* Marshal.swift */,
+ 305A8E6B1CCF64E7007656D0 /* Permissions.swift */,
+ 305A8E6C1CCF64E7007656D0 /* Row.swift */,
93B980F21CAB9D6E00CE42E0 /* Security.swift */,
+ 305A8E6D1CCF64E7007656D0 /* Service.swift */,
+ 305A8E6E1CCF64E7007656D0 /* Stream.swift */,
+ 93B980DB1CAB9D3D00CE42E0 /* Syncbase.h */,
93B980F41CAB9D9000CE42E0 /* Syncbase.swift */,
+ 305A8E6F1CCF64E7007656D0 /* Syncgroup.swift */,
+ 305A8E701CCF64E7007656D0 /* Util.h */,
+ 305A8E711CCF64E7007656D0 /* Util.m */,
+ 305A8E721CCF64E7007656D0 /* Util.swift */,
+ 305A8E731CCF64E7007656D0 /* Vom.swift */,
+ 305A8E741CCF64E7007656D0 /* Watch.swift */,
);
path = Syncbase;
sourceTree = "<group>";
@@ -213,8 +266,9 @@
93B980E61CAB9D3D00CE42E0 /* SyncbaseTests */ = {
isa = PBXGroup;
children = (
- 93B980E71CAB9D3D00CE42E0 /* SyncbaseTests.swift */,
93B980E91CAB9D3D00CE42E0 /* Info.plist */,
+ 30B111C31CCEA9C7000F3750 /* NSNumberTests.swift */,
+ 30B1120A1CCF586C000F3750 /* JsonTests.swift */,
);
path = SyncbaseTests;
sourceTree = "<group>";
@@ -365,6 +419,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 305A8E811CCF64E7007656D0 /* Util.h in Headers */,
93B980DC1CAB9D3D00CE42E0 /* Syncbase.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -612,8 +667,24 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 305A8E7C1CCF64E7007656D0 /* Permissions.swift in Sources */,
+ 305A8E7A1CCF64E7007656D0 /* Id.swift in Sources */,
+ 305A8E781CCF64E7007656D0 /* Database.swift in Sources */,
+ 305A8E831CCF64E7007656D0 /* Util.swift in Sources */,
+ 305A8E761CCF64E7007656D0 /* Blob.swift in Sources */,
+ 305A8E801CCF64E7007656D0 /* Syncgroup.swift in Sources */,
+ 305A8E821CCF64E7007656D0 /* Util.m in Sources */,
+ 305A8E851CCF64E7007656D0 /* Watch.swift in Sources */,
+ 305A8E771CCF64E7007656D0 /* Collection.swift in Sources */,
+ 305A8E7E1CCF64E7007656D0 /* Service.swift in Sources */,
+ 305A8E751CCF64E7007656D0 /* Batch.swift in Sources */,
+ 305A8E7D1CCF64E7007656D0 /* Row.swift in Sources */,
93B980F31CAB9D6E00CE42E0 /* Security.swift in Sources */,
+ 305A8E7F1CCF64E7007656D0 /* Stream.swift in Sources */,
+ 305A8E841CCF64E7007656D0 /* Vom.swift in Sources */,
93B980F51CAB9D9000CE42E0 /* Syncbase.swift in Sources */,
+ 305A8E7B1CCF64E7007656D0 /* Marshal.swift in Sources */,
+ 305A8E791CCF64E7007656D0 /* Errors.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -621,7 +692,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 93B980E81CAB9D3D00CE42E0 /* SyncbaseTests.swift in Sources */,
+ 30B111C41CCEA9C7000F3750 /* NSNumberTests.swift in Sources */,
+ 30B1120B1CCF586C000F3750 /* JsonTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/lib/VanadiumCoreTests/PromiseTests.swift b/lib/VanadiumCoreTests/PromiseTests.swift
index ae132cd..3590085 100644
--- a/lib/VanadiumCoreTests/PromiseTests.swift
+++ b/lib/VanadiumCoreTests/PromiseTests.swift
@@ -5,7 +5,7 @@
import XCTest
import VanadiumCore
-enum TestErrors : ErrorType {
+enum TestErrors: ErrorType {
case SomeError
case SomeOtherError
}
@@ -25,7 +25,7 @@
}
try! p.resolve(5)
XCTAssertTrue(alwaysRan)
-
+
var ranNow = false
p.onResolve { obj in
ranNow = true
@@ -68,7 +68,7 @@
}
try! p.reject(TestErrors.SomeError)
}
-
+
func testRejectionDoesntRunOnResolve() {
let p = Promise<Int>()
p.onReject { err in
@@ -76,12 +76,12 @@
}
try! p.resolve(5)
}
-
+
func testThenTransforms() {
let p = Promise<Int>()
try! p.resolve(5)
-
- var finalResult:String = ""
+
+ var finalResult: String = ""
p.then { obj -> String in
XCTAssertEqual(obj, 5)
return "hello"
@@ -94,7 +94,7 @@
func testThenPropagatesError() {
let p = Promise<Int>()
-
+
var didReject = false
p.then { obj -> String in
XCTAssertEqual(obj, 5)
@@ -105,7 +105,7 @@
try! p.reject(TestErrors.SomeError)
XCTAssertTrue(didReject)
}
-
+
func testThenReturningPromiseWorks() {
let p = Promise<Int>()
let bgQueue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
@@ -116,7 +116,7 @@
}
return newP
}
-
+
try! p.resolve(5)
let finalStatus = try! finalP.await()
switch (finalStatus) {