Skip to content

Instantly share code, notes, and snippets.

@sjoerdvisscher
Created March 20, 2015 18:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjoerdvisscher/39398dc2bedaab74e803 to your computer and use it in GitHub Desktop.
Save sjoerdvisscher/39398dc2bedaab74e803 to your computer and use it in GitHub Desktop.
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