chrisk-protopuffs 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +18 -0
- data/README.rdoc +3 -0
- data/VERSION.yml +4 -0
- data/lib/protopuffs/message/base.rb +90 -0
- data/lib/protopuffs/message/field.rb +226 -0
- data/lib/protopuffs/message/wire_type.rb +10 -0
- data/lib/protopuffs/parser/parser.rb +24 -0
- data/lib/protopuffs/parser/protocol_buffer.treetop +124 -0
- data/lib/protopuffs.rb +27 -0
- data/test/abstract_syntax_tree_test.rb +142 -0
- data/test/fixtures/proto/person.proto +5 -0
- data/test/message_base_test.rb +98 -0
- data/test/message_field_test.rb +34 -0
- data/test/parse_tree_test.rb +206 -0
- data/test/protopuffs_test.rb +36 -0
- data/test/test_helper.rb +71 -0
- data/test/wire_format_test.rb +231 -0
- metadata +105 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
2
|
+
a copy of this software and associated documentation files (the
|
3
|
+
"Software"), to deal in the Software without restriction, including
|
4
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
5
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
6
|
+
permit persons to whom the Software is furnished to do so, subject to
|
7
|
+
the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be
|
10
|
+
included in all copies or substantial portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
13
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
14
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
15
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
16
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
17
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
18
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/VERSION.yml
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module Protopuffs
|
2
|
+
module Message
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :fields
|
8
|
+
|
9
|
+
def define_message_class(name, fields)
|
10
|
+
name = name.capitalize
|
11
|
+
self.check_fields_for_errors(name, fields)
|
12
|
+
Message.send(:remove_const, name) if Message.const_defined?(name)
|
13
|
+
klass = Message.const_set(name, Class.new(self))
|
14
|
+
klass.instance_variable_set(:@fields, fields)
|
15
|
+
fields.each do |field|
|
16
|
+
klass.send(:attr_accessor, field.identifier)
|
17
|
+
end
|
18
|
+
klass
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_fields_for_errors(name, fields)
|
22
|
+
tags = fields.map { |field| field.tag }
|
23
|
+
if tags.uniq.size != fields.size
|
24
|
+
raise ParseError, "A tag was reused in the descriptor for message #{name}. Please check that each tag is unique."
|
25
|
+
end
|
26
|
+
|
27
|
+
if tags.any? { |tag| tag < 1 || tag > 536_870_911 }
|
28
|
+
raise ParseError, "A tag is out of range in the descriptor for message #{name}. Please check that each tag is in the range 1 <= x <= 536,870,911."
|
29
|
+
end
|
30
|
+
|
31
|
+
if tags.any? { |tag| (19000..19999).include?(tag) }
|
32
|
+
raise ParseError, "A tag is invalid in the descriptor for message #{name}. Please check that each tag is not in the reserved range 19,000 <= x <= 19,999."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :buffer
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
if self.class == Base
|
41
|
+
raise "#{self.class} should not be instantiated directly. Use the factory #{self.class}.define_message_class instead."
|
42
|
+
end
|
43
|
+
@buffer = StringIO.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_wire_format
|
47
|
+
self.class.fields.each do |field|
|
48
|
+
value = send(field.identifier)
|
49
|
+
@buffer.write field.to_wire_format_with_value(value) unless value.nil?
|
50
|
+
end
|
51
|
+
@buffer.string
|
52
|
+
end
|
53
|
+
|
54
|
+
def from_wire_format(buffer)
|
55
|
+
@buffer = buffer
|
56
|
+
until @buffer.eof?
|
57
|
+
tag, value_bytes = MessageField.shift_tag_and_value_bytes(@buffer)
|
58
|
+
field = self.class.fields.find { |field| field.tag == tag }
|
59
|
+
next if field.nil?
|
60
|
+
|
61
|
+
value = field.decode(value_bytes)
|
62
|
+
if field.repeated? && send(field.identifier).nil?
|
63
|
+
send("#{field.identifier}=", [value])
|
64
|
+
elsif field.repeated?
|
65
|
+
send(field.identifier) << value
|
66
|
+
else
|
67
|
+
send("#{field.identifier}=", value)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
set_values_for_missing_optional_fields
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_values_for_missing_optional_fields
|
74
|
+
self.class.fields.select { |field| field.optional? }.each do |field|
|
75
|
+
send("#{field.identifier}=", field.default) if send(field.identifier).nil?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def ==(other)
|
80
|
+
return false if self.class != other.class
|
81
|
+
self.class.fields.each do |field|
|
82
|
+
return false if send(field.identifier) != other.send(field.identifier)
|
83
|
+
end
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module Protopuffs
|
2
|
+
|
3
|
+
class MessageField
|
4
|
+
attr_reader :modifier, :type, :identifier, :tag, :default
|
5
|
+
|
6
|
+
def initialize(modifier, type, identifier, tag, default = nil)
|
7
|
+
@modifier = modifier
|
8
|
+
@type = type
|
9
|
+
@identifier = identifier
|
10
|
+
@tag = tag
|
11
|
+
@default = default
|
12
|
+
|
13
|
+
set_default_for_type if optional? && @default.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_default_for_type
|
17
|
+
if numeric?
|
18
|
+
@default = 0
|
19
|
+
elsif @type == "string"
|
20
|
+
@default = ""
|
21
|
+
elsif @type == "bool"
|
22
|
+
@default = false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def wire_type
|
27
|
+
case @type
|
28
|
+
when "int32", "int64", "uint32", "uint64", "bool" then WireType::VARINT
|
29
|
+
when "double", "fixed64" then WireType::FIXED64
|
30
|
+
when "string", "bytes" then WireType::LENGTH_DELIMITED
|
31
|
+
when "float", "fixed32" then WireType::FIXED32
|
32
|
+
else WireType::LENGTH_DELIMITED # embedded messages
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def key
|
37
|
+
(@tag << 3) | wire_type
|
38
|
+
end
|
39
|
+
|
40
|
+
def repeated?
|
41
|
+
@modifier == "repeated"
|
42
|
+
end
|
43
|
+
|
44
|
+
def optional?
|
45
|
+
@modifier == "optional"
|
46
|
+
end
|
47
|
+
|
48
|
+
def numeric?
|
49
|
+
%w(double float int32 int64 uint32 unit64 sint32 sint64 fixed32 fixed64
|
50
|
+
sfixed32 sfixed64).include?(@type)
|
51
|
+
end
|
52
|
+
|
53
|
+
def embedded_message?
|
54
|
+
wire_type == WireType::LENGTH_DELIMITED && @type != "string" && @type != "bytes"
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_wire_format_with_value(value)
|
58
|
+
field_encoder = lambda { |val| self.class.varint_encode(key) + encode(val) }
|
59
|
+
|
60
|
+
if repeated?
|
61
|
+
value.map(&field_encoder).join
|
62
|
+
else
|
63
|
+
field_encoder.call(value)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def encode(value)
|
68
|
+
case wire_type
|
69
|
+
when WireType::VARINT
|
70
|
+
value = (value ? 1 : 0) if @type == "bool"
|
71
|
+
value_bytes = self.class.varint_encode(value)
|
72
|
+
when WireType::LENGTH_DELIMITED
|
73
|
+
if value.respond_to?(:to_wire_format)
|
74
|
+
embedded_bytes = value.to_wire_format
|
75
|
+
value_bytes = self.class.varint_encode(embedded_bytes.size) + embedded_bytes
|
76
|
+
else
|
77
|
+
value_bytes = self.class.varint_encode(value.size)
|
78
|
+
value_bytes += self.class.string_encode(value) if @type == "string"
|
79
|
+
value_bytes += value if @type == "bytes"
|
80
|
+
end
|
81
|
+
when WireType::FIXED32
|
82
|
+
value_bytes = self.class.float_encode(value) if @type == "float"
|
83
|
+
value_bytes = self.class.fixed32_encode(value) if @type == "fixed32"
|
84
|
+
when WireType::FIXED64
|
85
|
+
value_bytes = self.class.double_encode(value) if @type == "double"
|
86
|
+
value_bytes = self.class.fixed64_encode(value) if @type == "fixed64"
|
87
|
+
end
|
88
|
+
value_bytes
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def self.varint_encode(value)
|
93
|
+
return [0].pack('C') if value.zero?
|
94
|
+
bytes = []
|
95
|
+
until value.zero?
|
96
|
+
byte = 0
|
97
|
+
7.times do |i|
|
98
|
+
byte |= (value & 1) << i
|
99
|
+
value >>= 1
|
100
|
+
end
|
101
|
+
byte |= 0b10000000
|
102
|
+
bytes << byte
|
103
|
+
end
|
104
|
+
bytes[-1] &= 0b01111111
|
105
|
+
bytes.pack('C*')
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.string_encode(value)
|
109
|
+
value.unpack('U*').pack('C*')
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.float_encode(value)
|
113
|
+
[value].pack('e')
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.double_encode(value)
|
117
|
+
[value].pack('E')
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.fixed64_encode(value)
|
121
|
+
[value].pack('Q')
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.fixed32_encode(value)
|
125
|
+
[value].pack('V')
|
126
|
+
end
|
127
|
+
|
128
|
+
# note: returns two values
|
129
|
+
def self.shift_tag_and_value_bytes(buffer)
|
130
|
+
bits = 0
|
131
|
+
bytes = shift_varint(buffer)
|
132
|
+
bytes.each_with_index do |byte, index|
|
133
|
+
byte &= 0b01111111
|
134
|
+
bits |= byte << (7 * index)
|
135
|
+
end
|
136
|
+
wire_type = bits & 0b00000111
|
137
|
+
tag = bits >> 3
|
138
|
+
|
139
|
+
case wire_type
|
140
|
+
when WireType::VARINT
|
141
|
+
value_bytes = shift_varint(buffer)
|
142
|
+
when WireType::LENGTH_DELIMITED
|
143
|
+
value_bytes = shift_length_delimited(buffer)
|
144
|
+
when WireType::FIXED32
|
145
|
+
value_bytes = shift_fixed32(buffer)
|
146
|
+
when WireType::FIXED64
|
147
|
+
value_bytes = shift_fixed64(buffer)
|
148
|
+
end
|
149
|
+
|
150
|
+
[tag, value_bytes]
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.shift_varint(buffer)
|
154
|
+
bytes = []
|
155
|
+
begin
|
156
|
+
byte = buffer.readchar
|
157
|
+
bytes << (byte & 0b01111111)
|
158
|
+
end while byte >> 7 == 1
|
159
|
+
bytes
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.shift_length_delimited(buffer)
|
163
|
+
bytes = shift_varint(buffer)
|
164
|
+
value_length = self.varint_decode(bytes)
|
165
|
+
buffer.read(value_length)
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.shift_fixed32(buffer)
|
169
|
+
buffer.read(4)
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.shift_fixed64(buffer)
|
173
|
+
buffer.read(8)
|
174
|
+
end
|
175
|
+
|
176
|
+
def decode(value_bytes)
|
177
|
+
case wire_type
|
178
|
+
when WireType::VARINT
|
179
|
+
value = self.class.varint_decode(value_bytes)
|
180
|
+
if @type == "bool"
|
181
|
+
value = true if value == 1
|
182
|
+
value = false if value == 0
|
183
|
+
end
|
184
|
+
when WireType::LENGTH_DELIMITED
|
185
|
+
if embedded_message?
|
186
|
+
value = Message.const_get(@type).new
|
187
|
+
value.from_wire_format(StringIO.new(value_bytes))
|
188
|
+
else
|
189
|
+
value = value_bytes
|
190
|
+
end
|
191
|
+
when WireType::FIXED32
|
192
|
+
value = self.class.float_decode(value_bytes) if @type == "float"
|
193
|
+
value = self.class.fixed32_decode(value_bytes) if @type == "fixed32"
|
194
|
+
when WireType::FIXED64
|
195
|
+
value = self.class.double_decode(value_bytes) if @type == "double"
|
196
|
+
value = self.class.fixed64_decode(value_bytes) if @type == "fixed64"
|
197
|
+
end
|
198
|
+
value
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.varint_decode(bytes)
|
202
|
+
value = 0
|
203
|
+
bytes.each_with_index do |byte, index|
|
204
|
+
value |= byte << (7 * index)
|
205
|
+
end
|
206
|
+
value
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.float_decode(bytes)
|
210
|
+
bytes.unpack('e').first
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.double_decode(bytes)
|
214
|
+
bytes.unpack('E').first
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.fixed64_decode(bytes)
|
218
|
+
bytes.unpack('Q').first
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.fixed32_decode(bytes)
|
222
|
+
bytes.unpack('V').first
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Protopuffs
|
2
|
+
|
3
|
+
# The Protopuffs::Parser module holds wrapper classes that parse input
|
4
|
+
# strings using Treetop. They also set off the building of an abstract
|
5
|
+
# syntax tree from Treetop's parse tree.
|
6
|
+
module Parser
|
7
|
+
|
8
|
+
class ProtocolBufferDescriptor
|
9
|
+
def initialize
|
10
|
+
Treetop.load "lib/protopuffs/parser/protocol_buffer"
|
11
|
+
@parser = Protopuffs::ProtocolBufferParser.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(input)
|
15
|
+
parse_tree = @parser.parse(input)
|
16
|
+
parse_tree.build
|
17
|
+
parse_tree
|
18
|
+
rescue
|
19
|
+
raise ParseError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Protopuffs
|
2
|
+
grammar ProtocolBuffer
|
3
|
+
|
4
|
+
rule proto
|
5
|
+
s? proto_entries s? {
|
6
|
+
def messages
|
7
|
+
proto_entries.entries.select { |p| p.respond_to?(:message?) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def build
|
11
|
+
messages.each { |message| message.build }
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
rule proto_entries
|
17
|
+
proto_entry rest:(s? proto_entry)* {
|
18
|
+
def entries
|
19
|
+
rest.elements.inject([proto_entry]) { |a, s_and_pe| a << s_and_pe.proto_entry }
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
rule proto_entry
|
25
|
+
message {
|
26
|
+
def message?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
rule message
|
33
|
+
"message" s identifier s "{" s? body:message_body? s? "}" {
|
34
|
+
def name
|
35
|
+
identifier.text_value
|
36
|
+
end
|
37
|
+
|
38
|
+
def build
|
39
|
+
Message::Base.define_message_class(name, body.empty? ? [] : body.build)
|
40
|
+
end
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
rule message_body
|
45
|
+
field rest:(s? field)* {
|
46
|
+
def fields
|
47
|
+
rest.elements.inject([field]) { |a, s_and_field| a << s_and_field.field }
|
48
|
+
end
|
49
|
+
|
50
|
+
def build
|
51
|
+
fields.map { |field| field.build }
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
rule field
|
57
|
+
modifier s type s identifier s "=" s integer s? option:field_option? ";" {
|
58
|
+
def default
|
59
|
+
option.empty? ? nil : option.literal.obj
|
60
|
+
end
|
61
|
+
|
62
|
+
def build
|
63
|
+
MessageField.new(modifier.text_value, type.text_value, identifier.text_value,
|
64
|
+
integer.text_value.to_i, default)
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
rule field_option
|
70
|
+
"[" s? "default" s? "=" s? literal s? "]"
|
71
|
+
end
|
72
|
+
|
73
|
+
rule modifier
|
74
|
+
"required" / "optional" / "repeated"
|
75
|
+
end
|
76
|
+
|
77
|
+
rule type
|
78
|
+
"double" / "float" / "int32" / "int64" / "uint32" / "uint64" /
|
79
|
+
"sint32" / "sint64" / "fixed32" / "fixed64" / "sfixed32" /
|
80
|
+
"sfixed64" / "bool" / "string" / "bytes" / identifier
|
81
|
+
end
|
82
|
+
|
83
|
+
rule literal
|
84
|
+
integer / string
|
85
|
+
end
|
86
|
+
|
87
|
+
rule integer
|
88
|
+
[\d]+ {
|
89
|
+
def obj
|
90
|
+
text_value.to_i
|
91
|
+
end
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
rule string
|
96
|
+
quote chars:[\w]* quote {
|
97
|
+
def obj
|
98
|
+
chars.text_value
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
rule quote
|
104
|
+
["']
|
105
|
+
end
|
106
|
+
|
107
|
+
rule identifier
|
108
|
+
[A-Za-z_] [\w_]*
|
109
|
+
end
|
110
|
+
|
111
|
+
rule s
|
112
|
+
(whitespace / comment_to_eol)+
|
113
|
+
end
|
114
|
+
|
115
|
+
rule comment_to_eol
|
116
|
+
'//' (!"\n" .)*
|
117
|
+
end
|
118
|
+
|
119
|
+
rule whitespace
|
120
|
+
[ \t\n\r]
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
data/lib/protopuffs.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "protopuffs/message/base"
|
2
|
+
require "protopuffs/message/field"
|
3
|
+
require "protopuffs/message/wire_type"
|
4
|
+
require "protopuffs/parser/parser"
|
5
|
+
|
6
|
+
|
7
|
+
module Protopuffs
|
8
|
+
class ParseError < StandardError; end
|
9
|
+
|
10
|
+
def self.proto_load_path
|
11
|
+
@proto_load_path ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.proto_load_path=(paths)
|
15
|
+
@proto_load_path = paths
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load_message_classes
|
19
|
+
parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
|
20
|
+
|
21
|
+
@proto_load_path.each do |path|
|
22
|
+
Dir.glob(File.join(path, "**/*.proto")) do |descriptor_path|
|
23
|
+
parser.parse(File.read(descriptor_path))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class AbstractSyntaxTreeTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "a protocol buffer descriptor" do
|
6
|
+
setup do
|
7
|
+
@parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with an empty Person message" do
|
11
|
+
setup do
|
12
|
+
@descriptor = "message Person {}"
|
13
|
+
end
|
14
|
+
|
15
|
+
should "create one MessageDescriptor" do
|
16
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", [])
|
17
|
+
@parser.parse(@descriptor)
|
18
|
+
end
|
19
|
+
|
20
|
+
should "not create any MessageFields" do
|
21
|
+
Protopuffs::MessageField.expects(:new).never
|
22
|
+
@parser.parse(@descriptor)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
context "with two empty messages Apple and Orange" do
|
28
|
+
setup do
|
29
|
+
@descriptor = <<-proto
|
30
|
+
message Apple {}
|
31
|
+
message Orange {}
|
32
|
+
proto
|
33
|
+
end
|
34
|
+
|
35
|
+
should "create two MessageDescriptors" do
|
36
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Apple", []).in_sequence
|
37
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Orange", []).in_sequence
|
38
|
+
@parser.parse(@descriptor)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "not create any MessageFields" do
|
42
|
+
Protopuffs::MessageField.expects(:new).never
|
43
|
+
@parser.parse(@descriptor)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
context "with a Person message including a name field" do
|
49
|
+
setup do
|
50
|
+
@descriptor = <<-proto
|
51
|
+
message Person {
|
52
|
+
required string name = 1;
|
53
|
+
}
|
54
|
+
proto
|
55
|
+
end
|
56
|
+
|
57
|
+
should "create one 'Person' MessageDescriptor with one field" do
|
58
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 1))
|
59
|
+
@parser.parse(@descriptor)
|
60
|
+
end
|
61
|
+
|
62
|
+
should "create one MessageField with correct options" do
|
63
|
+
Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).returns(stub(:tag => 1, :identifier => "name"))
|
64
|
+
@parser.parse(@descriptor)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
context "with a Person message including three fields" do
|
70
|
+
setup do
|
71
|
+
@descriptor = <<-proto
|
72
|
+
message Person {
|
73
|
+
required string name = 1;
|
74
|
+
required int32 id = 2;
|
75
|
+
optional string email = 3;
|
76
|
+
}
|
77
|
+
proto
|
78
|
+
end
|
79
|
+
|
80
|
+
should "create one 'Person' MessageDescriptor with three fields" do
|
81
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
|
82
|
+
@parser.parse(@descriptor)
|
83
|
+
end
|
84
|
+
|
85
|
+
should "create three MessageFields with correct options" do
|
86
|
+
Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
|
87
|
+
Protopuffs::MessageField.expects(:new).once.with("required", "int32", "id", 2, nil).in_sequence.returns(stub(:tag => 2, :identifier => "id"))
|
88
|
+
Protopuffs::MessageField.expects(:new).once.with("optional", "string", "email", 3, nil).in_sequence.returns(stub(:tag => 3, :identifier => "email"))
|
89
|
+
@parser.parse(@descriptor)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
context "with a message including two fields with defaults and one without" do
|
95
|
+
setup do
|
96
|
+
@descriptor = <<-proto
|
97
|
+
message Person {
|
98
|
+
required string name = 1;
|
99
|
+
optional string language = 2 [default = "en"];
|
100
|
+
optional int32 account_code = 3 [default = 0];
|
101
|
+
}
|
102
|
+
proto
|
103
|
+
end
|
104
|
+
|
105
|
+
should "create one 'Person' MessageDescriptor with three fields" do
|
106
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
|
107
|
+
@parser.parse(@descriptor)
|
108
|
+
end
|
109
|
+
|
110
|
+
should "create three MessageFields with correct options" do
|
111
|
+
Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
|
112
|
+
Protopuffs::MessageField.expects(:new).once.with("optional", "string", "language", 2, "en").in_sequence.returns(stub(:tag => 2, :identifier => "language"))
|
113
|
+
Protopuffs::MessageField.expects(:new).once.with("optional", "int32", "account_code", 3, 0).in_sequence.returns(stub(:tag => 3, :identifier => "account_code"))
|
114
|
+
@parser.parse(@descriptor)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
context "with a message including a user-typed field" do
|
120
|
+
setup do
|
121
|
+
@descriptor = <<-proto
|
122
|
+
message Person {
|
123
|
+
required string name = 1;
|
124
|
+
repeated Address addresses = 2;
|
125
|
+
}
|
126
|
+
proto
|
127
|
+
end
|
128
|
+
|
129
|
+
should "create one 'Person' MessageDescriptor with two fields" do
|
130
|
+
Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 2))
|
131
|
+
@parser.parse(@descriptor)
|
132
|
+
end
|
133
|
+
|
134
|
+
should "create two MessageFields with correct options" do
|
135
|
+
Protopuffs::MessageField.expects(:new).once.with("required", "string", "name", 1, nil).in_sequence.returns(stub(:tag => 1, :identifier => "name"))
|
136
|
+
Protopuffs::MessageField.expects(:new).once.with("repeated", "Address", "addresses", 2, nil).in_sequence.returns(stub(:tag => 2, :identifier => "addresses"))
|
137
|
+
@parser.parse(@descriptor)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|