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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96ce685d7db62b76f32cdd90b85c204d7893742d
4
- data.tar.gz: 610b8c0c7175efb662fa4b3116c0a5f7d37d0261
3
+ metadata.gz: 950aa0c33840f83ddc2708dba9b8be77b6bbed71
4
+ data.tar.gz: 602b119c4b1c339c3f5395bc2ce23aa69d152d83
5
5
  SHA512:
6
- metadata.gz: 07322599a18df3dd8e9c8c61280cc72c97d3c24afd5b95c2b8e64482d510a75c17abcbd61af5dcaac48a70130aad14f8c0bae06b398f5b6e3b1b82e684685a01
7
- data.tar.gz: fa7ebf54092ca0f93e413c7e13b3dc7a600c73ab5e4a5716594da9d4d3fb3a61d341602493d2072371b92dab6f9e53a94dd2a808a4438d7a5b8e365afb9fe0d8
6
+ metadata.gz: 7e2a9fb98f64ed089af30ee95f06bda60d29c61d69955ef62e90b78819f6e88033c2b413fd1bf2c54a9bb39671ed77aca511a475d5ff095131a61964da132f72
7
+ data.tar.gz: 15cebf56397143b80badf733327f2c6a17850ed8789c1f667d8d0d53d15a01a9dd92f6c505c942f093042f03bc8951207db61c6978fd655cba118ad8f6d99695
data/.rubocop.yml CHANGED
@@ -6,3 +6,5 @@ Metrics/LineLength:
6
6
  Max: 120
7
7
  Style/SignalException:
8
8
  Enabled: false
9
+ Style/MultilineOperationIndentation:
10
+ Enabled: false
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 'json'
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
- JSON.parse read
18
+ EJSON.parse read
19
19
  end
20
20
 
21
21
  def write_message(message)
22
- write JSON.generate(message)
22
+ write EJSON.generate(message)
23
23
  end
24
24
  end
25
25
  end
@@ -1,6 +1,6 @@
1
1
  module DDP
2
2
  # We use semantic versioning
3
3
  module Server
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
6
6
  end
@@ -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
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-02-23 00:00:00.000000000 Z
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