ddp-server 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/lib/ddp/ejson.rb +128 -0
- data/lib/ddp/server.rb +3 -3
- data/lib/ddp/server/version.rb +1 -1
- data/spec/ejson_spec.rb +105 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 950aa0c33840f83ddc2708dba9b8be77b6bbed71
|
4
|
+
data.tar.gz: 602b119c4b1c339c3f5395bc2ce23aa69d152d83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e2a9fb98f64ed089af30ee95f06bda60d29c61d69955ef62e90b78819f6e88033c2b413fd1bf2c54a9bb39671ed77aca511a475d5ff095131a61964da132f72
|
7
|
+
data.tar.gz: 15cebf56397143b80badf733327f2c6a17850ed8789c1f667d8d0d53d15a01a9dd92f6c505c942f093042f03bc8951207db61c6978fd655cba118ad8f6d99695
|
data/.rubocop.yml
CHANGED
data/lib/ddp/ejson.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module DDP
|
5
|
+
# EJSON is a way of embedding more than the built-in JSON types in JSON.
|
6
|
+
# It supports all types built into JSON as plain JSON, plus some custom
|
7
|
+
# types identified by a key prefixed with '$'.
|
8
|
+
class EJSON
|
9
|
+
def self.parse(string)
|
10
|
+
parsed = JSON.parse string
|
11
|
+
|
12
|
+
deserialize(parsed)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.generate(object)
|
16
|
+
JSON.generate as_ejson(object)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.deserialize(object)
|
20
|
+
if object.is_a? Hash
|
21
|
+
deserialize_hash(object)
|
22
|
+
elsif object.is_a? Array
|
23
|
+
object.map { |e| deserialize(e) }
|
24
|
+
else
|
25
|
+
object
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.as_ejson(object)
|
30
|
+
if object.respond_to? :as_ejson
|
31
|
+
object.as_ejson
|
32
|
+
elsif object.is_a? Hash
|
33
|
+
hash_as_ejson(object)
|
34
|
+
elsif object.is_a? Array
|
35
|
+
object.map { |i| as_ejson(i) }
|
36
|
+
else
|
37
|
+
object
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Hashes can contain keys that need to be escaped
|
42
|
+
def self.hash_as_ejson(hash)
|
43
|
+
result = hash.map do |k, v|
|
44
|
+
if k.is_a?(String) && k[0] == '$'
|
45
|
+
['$escape', { k => as_ejson(v) }]
|
46
|
+
else
|
47
|
+
[k, as_ejson(v)]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
Hash[result]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.deserialize_hash(hash)
|
54
|
+
deserialize_operation(hash) || hash.map do |k, v|
|
55
|
+
[k, deserialize(v)]
|
56
|
+
end.to_h
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.deserialize_operation(hash)
|
60
|
+
if hash['$escape']
|
61
|
+
return deserialize_escape(hash['$escape'])
|
62
|
+
elsif hash['$date']
|
63
|
+
return Time.at(hash['$date'] / 1000.0)
|
64
|
+
elsif hash['$binary']
|
65
|
+
return Base64.decode64(hash['$binary'])
|
66
|
+
elsif hash['$type']
|
67
|
+
return deserialize_type(hash)
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.deserialize_escape(hash)
|
73
|
+
hash.map do |k, v|
|
74
|
+
[k, deserialize(v)]
|
75
|
+
end.to_h
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.deserialize_type(hash)
|
79
|
+
klass = @classes[hash['$type']]
|
80
|
+
if klass
|
81
|
+
klass.from_ejson(hash['$value'])
|
82
|
+
else
|
83
|
+
raise UnknownTypeError, "Don't know how to deserialize #{hash['$type']}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.add_serializable_class(klass)
|
88
|
+
@classes ||= {}
|
89
|
+
@classes[klass.name] = klass
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.rename_serializable_class(klass, name)
|
93
|
+
@classes.delete(klass.name)
|
94
|
+
@classes[name] = klass
|
95
|
+
end
|
96
|
+
|
97
|
+
# Classes can include this module to be picked up by the EJSON parser
|
98
|
+
module Serializable
|
99
|
+
def self.extended(klass)
|
100
|
+
EJSON.add_serializable_class(klass)
|
101
|
+
end
|
102
|
+
|
103
|
+
def ejson_type_name(name)
|
104
|
+
EJSON.rename_serializable_class(self, name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def from_ejson(_object)
|
108
|
+
raise InvalidSerializableClassError, "Class #{name} must override from_ejson."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Raised when parsing an unknown type
|
113
|
+
class UnknownTypeError < StandardError
|
114
|
+
end
|
115
|
+
|
116
|
+
# Raised when serializable class does not implement from_ejson
|
117
|
+
class InvalidSerializableClassError < StandardError
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Builtin EJSON types:
|
123
|
+
class Time
|
124
|
+
def as_ejson
|
125
|
+
# milliseconds since epoch
|
126
|
+
{ '$date' => (to_f * 1000).to_i }
|
127
|
+
end
|
128
|
+
end
|
data/lib/ddp/server.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'ddp/server/version'
|
2
2
|
require 'ddp/server/protocol'
|
3
3
|
require 'celluloid/websocket/rack'
|
4
|
-
require '
|
4
|
+
require 'ddp/ejson'
|
5
5
|
require 'securerandom'
|
6
6
|
|
7
7
|
module DDP
|
@@ -15,11 +15,11 @@ module DDP
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def read_message
|
18
|
-
|
18
|
+
EJSON.parse read
|
19
19
|
end
|
20
20
|
|
21
21
|
def write_message(message)
|
22
|
-
write
|
22
|
+
write EJSON.generate(message)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
data/lib/ddp/server/version.rb
CHANGED
data/spec/ejson_spec.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ddp/ejson'
|
3
|
+
require 'json'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
# Tests in DDP Module
|
7
|
+
module DDP
|
8
|
+
describe EJSON do
|
9
|
+
describe 'generate' do
|
10
|
+
it 'generates normal json for simple structures' do
|
11
|
+
[
|
12
|
+
{ 'a' => 'b' },
|
13
|
+
['a'],
|
14
|
+
{ 'a' => [1] }
|
15
|
+
].each do |example|
|
16
|
+
expect(EJSON.generate(example)).to eq(JSON.generate(example))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'escapes keys that start with a dollar sign' do
|
21
|
+
example = { '$bla' => 'value' }
|
22
|
+
ejson = EJSON.generate(example)
|
23
|
+
expect(JSON.parse(ejson)['$escape']).to eq('$bla' => 'value')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'generates a special type for dates' do
|
27
|
+
time = Time.now
|
28
|
+
ms_since_epoch = (time.to_f * 1000).to_i
|
29
|
+
ejson = EJSON.generate('date' => time)
|
30
|
+
expect(JSON.parse(ejson)['date']).to eq('$date' => ms_since_epoch)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'parse' do
|
35
|
+
it 'parses generic json' do
|
36
|
+
[
|
37
|
+
{ 'a' => 'b' },
|
38
|
+
['a'],
|
39
|
+
{ 'a' => [1] }
|
40
|
+
].each do |example|
|
41
|
+
json = JSON.generate(example)
|
42
|
+
expect(EJSON.parse(json)).to eq(example)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'parses an escaped ejson' do
|
47
|
+
example = { '$escape' => { '$date' => 'a' } }
|
48
|
+
ejson = JSON.generate(example)
|
49
|
+
expect(EJSON.parse(ejson)).to eq('$date' => 'a')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'parses a date ejson' do
|
53
|
+
time = Time.now
|
54
|
+
ms_since_epoch = (time.to_f * 1000).to_i
|
55
|
+
example = { 'date' => { '$date' => ms_since_epoch } }
|
56
|
+
ejson = JSON.generate(example)
|
57
|
+
expect(EJSON.parse(ejson)['date'].to_s).to eq(time.to_s)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'parses a binary ejson' do
|
61
|
+
example = { '$binary' => Base64.encode64('Hello World') }
|
62
|
+
ejson = JSON.generate(example)
|
63
|
+
expect(EJSON.parse(ejson)).to eq('Hello World')
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'parses custom types' do
|
67
|
+
# Test class
|
68
|
+
class A
|
69
|
+
extend EJSON::Serializable
|
70
|
+
|
71
|
+
ejson_type_name 'A'
|
72
|
+
|
73
|
+
def as_ejson
|
74
|
+
{ '$type' => 'A', '$value' => '1234' }
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.from_ejson(object)
|
78
|
+
object.to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
ejson = EJSON.generate(A.new)
|
83
|
+
expect(EJSON.parse(ejson)).to eq(1234)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'raises an exception when parsing an unknown type' do
|
87
|
+
ejson = JSON.generate('$type' => 'B', '$value' => '1234')
|
88
|
+
expect do
|
89
|
+
EJSON.parse(ejson)
|
90
|
+
end.to raise_error(EJSON::UnknownTypeError)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'raises an exception when a serializable class does not override from_ejson' do
|
94
|
+
expect do
|
95
|
+
# Test class
|
96
|
+
class C
|
97
|
+
extend EJSON::Serializable
|
98
|
+
end
|
99
|
+
|
100
|
+
C.from_ejson(false)
|
101
|
+
end.to raise_error(EJSON::InvalidSerializableClassError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ddp-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tinco Andringa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,12 +68,14 @@ files:
|
|
68
68
|
- Rakefile
|
69
69
|
- ddp-server.gemspec
|
70
70
|
- examples/rack_example.ru
|
71
|
+
- lib/ddp/ejson.rb
|
71
72
|
- lib/ddp/server.rb
|
72
73
|
- lib/ddp/server/protocol.rb
|
73
74
|
- lib/ddp/server/protocol/data.rb
|
74
75
|
- lib/ddp/server/protocol/heartbeat.rb
|
75
76
|
- lib/ddp/server/protocol/rpc.rb
|
76
77
|
- lib/ddp/server/version.rb
|
78
|
+
- spec/ejson_spec.rb
|
77
79
|
- spec/spec_helper.rb
|
78
80
|
homepage: https://github.com/d-snp/ruby-ddp-server
|
79
81
|
licenses:
|
@@ -100,4 +102,5 @@ signing_key:
|
|
100
102
|
specification_version: 4
|
101
103
|
summary: DDP Protocol server for implementing Ruby DDP backends
|
102
104
|
test_files:
|
105
|
+
- spec/ejson_spec.rb
|
103
106
|
- spec/spec_helper.rb
|