Created
March 20, 2015 18:58
-
-
Save sjoerdvisscher/39398dc2bedaab74e803 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
enum JSONValue: StringLiteralConvertible, FloatLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible, Equatable, DebugPrintable { | |
case JSONString(String) | |
case JSONNumber(Double) | |
case JSONObject([String: JSONValue]) | |
case JSONArray([JSONValue]) | |
init(stringLiteral value: String) { | |
self = JSONString(value) | |
} | |
init(extendedGraphemeClusterLiteral value: String) { | |
self = JSONString(value) | |
} | |
init(unicodeScalarLiteral value: String) { | |
self = JSONString(value) | |
} | |
init(floatLiteral value: Double) { | |
self = JSONNumber(value) | |
} | |
init(dictionaryLiteral elements: (String, JSONValue)...) { | |
var dict = [String: JSONValue]() | |
for (key, value) in elements { | |
dict[key] = value | |
} | |
self = JSONObject(dict) | |
} | |
init(arrayLiteral elements: (JSONValue)...) { | |
self = JSONArray(elements) | |
} | |
var debugDescription: String { | |
switch self { | |
case .JSONString(let value): | |
return value.debugDescription | |
case .JSONObject(let obj): | |
var s = "" | |
for (key, value) in obj { | |
s += "\(key): \(value.debugDescription), " | |
} | |
return "{\(s)}" | |
case .JSONArray(let obj): | |
var s = "" | |
for value in obj { | |
s += "\(value.debugDescription), " | |
} | |
return "[\(s)]" | |
default: | |
return "" | |
} | |
} | |
subscript(key: String) -> JSONValue? { | |
get { | |
switch self { | |
case .JSONObject(let value): | |
return value[key] | |
default: | |
return nil | |
} | |
} | |
} | |
} | |
func ==(lhs: JSONValue, rhs: JSONValue) -> Bool { | |
switch (lhs, rhs) { | |
case (.JSONString(let l), .JSONString(let r)): | |
return l == r | |
case (.JSONNumber(let l), .JSONNumber(let r)): | |
return l == r | |
case (.JSONObject(let l), .JSONObject(let r)): | |
return l == r | |
default: | |
return false | |
} | |
} | |
typealias JSONObject = [String: JSONValue] | |
typealias Field = JSONValue | |
typealias FieldName = String | |
typealias Document = [FieldName: Field] | |
typealias DocId = String | |
typealias PubName = String | |
typealias ClientId = String | |
class DDPServer { | |
var collections: [PubName: Collection] | |
typealias Subscription = [PubName: Callbacks] | |
var subscriptions: [ClientId: Subscription] | |
init() { | |
collections = [:] | |
subscriptions = [:] | |
} | |
func publish(name: PubName) -> Collection { | |
let coll = Collection(name: name, server: self); | |
collections[name] = coll; | |
return coll; | |
} | |
func fakeClient(messages: [JSONValue], sendMessage: JSONValue -> ()) { | |
let sessionId = arc4random().description | |
var subscription: Subscription = [:] | |
subscriptions[sessionId] = subscription | |
for data in messages { | |
switch data["msg"] { | |
case .Some(.JSONString("connect")): | |
sendMessage([ | |
"msg": "connected", | |
"session": .JSONString(sessionId) | |
]) | |
case .Some(.JSONString("sub")): | |
switch data["name"] { | |
case .Some(.JSONString(let name)): | |
if let docs = collections[name] { | |
let callback = Callbacks( | |
added: { (id, fields) in | |
sendMessage([ | |
"msg": "added", | |
"collection": .JSONString(name), | |
"id": .JSONString(id), | |
"fields": .JSONObject(fields) | |
]) | |
}, | |
changed: { (id, fields, cleared) in | |
sendMessage([ | |
"msg": "changed", | |
"collection": .JSONString(name), | |
"id": .JSONString(id), | |
"fields": .JSONObject(fields), | |
"cleared": .JSONArray(cleared.map { .JSONString($0) }) | |
]) | |
}, removed: { (id) in | |
sendMessage([ | |
"msg": "removed", | |
"collection": .JSONString(name), | |
"id": .JSONString(id) | |
]) | |
}) | |
subscription[name] = callback | |
subscriptions[sessionId] = subscription | |
for (id, doc) in docs.documents { | |
callback.added(id: id, fields: doc) | |
} | |
sendMessage([ | |
"msg": "ready", | |
"subs": [data["id"] ?? "unspecified-id"] | |
]) | |
} | |
default: | |
// invalid message | |
break; | |
} | |
case .Some("ping"): | |
sendMessage([ | |
"msg": "pong", | |
"id": data["id"] ?? "unspecified-id" | |
]) | |
default: | |
break | |
} | |
} | |
// on close | |
// subscriptions.removeValueForKey(sessionId) | |
} | |
func add(name: String, id: DocId, doc: Document) { | |
for (_, subscription) in subscriptions { | |
subscription[name]?.added(id: id, fields: doc) | |
} | |
} | |
func remove(name: String, id: DocId) { | |
for (_, subscription) in subscriptions { | |
subscription[name]?.removed(id: id) | |
} | |
} | |
func change(name: PubName, id: DocId, oldDoc: Document, newDoc: Document) { | |
let cleared = [FieldName](oldDoc.keys.filter { newDoc[$0] == nil }) | |
var changed = Dictionary<FieldName, Field>() | |
for (key, value) in newDoc { | |
if value != oldDoc[key] { | |
changed[key] = value | |
} | |
} | |
for (_, subscription) in subscriptions { | |
subscription[name]?.changed(id: id, fields: changed, cleared: cleared) | |
} | |
} | |
} | |
class Collection { | |
var documents: [String:Document] | |
let name: String | |
let server: DDPServer | |
init(name: String, server: DDPServer) { | |
self.name = name | |
self.server = server | |
self.documents = [:] | |
} | |
subscript(id: String) -> [FieldName: Field] { | |
get { return documents[id]? ?? [:] } | |
set { | |
if let oldValue = documents[id] { | |
server.change(name, id: id, oldDoc: oldValue, newDoc: newValue) | |
} else { | |
server.add(name, id: id, doc: newValue) | |
} | |
documents[id] = newValue | |
} | |
} | |
subscript(id: String) -> [FieldName: Field]? { | |
get { return documents[id] } | |
set { | |
documents[id] = newValue | |
if newValue == nil { | |
server.remove(name, id: id) | |
} | |
} | |
} | |
} | |
struct Callbacks { | |
let added: (id: DocId, fields: [FieldName: Field]) -> () | |
let changed: (id: DocId, fields: [FieldName: Field], cleared: [FieldName]) -> () | |
let removed: (id: DocId) -> () | |
} | |
// Testing | |
var server = DDPServer() | |
var toilets = server.publish("toilets") | |
server.fakeClient( | |
[ | |
["msg":"connect"], | |
["msg":"ping", "id": "test"], | |
["msg":"sub", "name": "toilets", "id": "test"] | |
], | |
sendMessage: { (obj: JSONValue) in | |
println("Received message:") | |
println(obj.debugDescription) | |
} | |
) | |
toilets["1"] = ["state": "available"] | |
toilets["2"] = ["state": "available"] | |
toilets["1"]["state"] = "occupied" | |
toilets["2"] = nil |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment