DistelliServiceMarshallers 1.0
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.
- data/lib/distelli/servicemarshallers.rb +431 -0
- metadata +77 -0
@@ -0,0 +1,431 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'date'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'distelli/serviceinterface'
|
7
|
+
|
8
|
+
module Distelli
|
9
|
+
DATA_TYPES = ["int", "string", "boolean", "long", "double"]
|
10
|
+
class Marshaller
|
11
|
+
def initialize()
|
12
|
+
@object_map = Hash.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_object(obj)
|
16
|
+
@object_map[obj.name] = obj
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class MarshallException < StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
################################
|
24
|
+
# JSON Marshaller
|
25
|
+
################################
|
26
|
+
|
27
|
+
class JsonMarshaller < Marshaller
|
28
|
+
def initialize()
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def encode(obj)
|
33
|
+
data = Hash.new
|
34
|
+
if obj.is_a?(Integer) or obj.is_a?(Float) or obj.is_a?(String) or obj.is_a?(TrueClass) or obj.is_a?(FalseClass)
|
35
|
+
return obj
|
36
|
+
elsif obj.is_a?(DateTime)
|
37
|
+
return obj.strftime("%Y-%m-%dT%H:%M:%S%z")
|
38
|
+
end
|
39
|
+
|
40
|
+
type_map_name = '_'+obj.class.name.split('::').last+'__type_map'
|
41
|
+
obj.instance_variables.each do |var|
|
42
|
+
var_name = var.to_s.delete('@')
|
43
|
+
if var_name == type_map_name
|
44
|
+
next
|
45
|
+
end
|
46
|
+
val = obj.instance_variable_get(var)
|
47
|
+
if val.is_a?(Integer) or val.is_a?(Float) or val.is_a?(String) or val.is_a?(TrueClass) or val.is_a?(FalseClass)
|
48
|
+
data[var_name] = val
|
49
|
+
elsif val.is_a?(DateTime)
|
50
|
+
data[var_name] = val.strftime("%Y-%m-%dT%H:%M:%S%z")
|
51
|
+
elsif val.instance_of?(Array)
|
52
|
+
array = Array.new
|
53
|
+
val.each do |elem|
|
54
|
+
encoded = encode(elem)
|
55
|
+
array.push(encoded)
|
56
|
+
end
|
57
|
+
data[var_name] = array
|
58
|
+
else val.kind_of?(Object)
|
59
|
+
encoded = encode(val)
|
60
|
+
data[var_name] = encoded
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return data
|
64
|
+
end
|
65
|
+
|
66
|
+
def marshall(request)
|
67
|
+
data = encode(request)
|
68
|
+
request_wrapper = Hash.new
|
69
|
+
request_wrapper[request.class.name.split('::').last] = data
|
70
|
+
return request_wrapper.to_json
|
71
|
+
end
|
72
|
+
|
73
|
+
def marshall_error(error)
|
74
|
+
if not error.is_a?(Distelli::BaseException)
|
75
|
+
raise StandardError.new("Cannot marshall error: "+error.inspect)
|
76
|
+
end
|
77
|
+
|
78
|
+
err_msg = error.err_msg
|
79
|
+
err_code = error.err_code
|
80
|
+
return '{"Error":{"code":"'+err_code.to_s+'", "message":"'+err_msg.to_s+'"}}'
|
81
|
+
end
|
82
|
+
|
83
|
+
def unmarshall_error(json_data)
|
84
|
+
data_hash = JSON.load(json_data)
|
85
|
+
err_obj = data_hash[ServiceConstants::ERROR_KEY]
|
86
|
+
if err_obj == nil
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
err_code = err_obj[ServiceConstants::ERR_CODE_KEY]
|
90
|
+
err_msg = err_obj[ServiceConstants::ERR_MSG_KEY]
|
91
|
+
return [err_code, err_msg]
|
92
|
+
end
|
93
|
+
|
94
|
+
def unmarshall(json_data)
|
95
|
+
data_hash = JSON.load(json_data)
|
96
|
+
data_hash.each_pair do |k,v|
|
97
|
+
if v.is_a?(Hash)
|
98
|
+
return unmarshall_obj(k, v)
|
99
|
+
else
|
100
|
+
raise MarshallException.new("Invalid data "+json_data)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def to_obj_list(obj_name, list_data)
|
107
|
+
obj_list = Array.new
|
108
|
+
# If its a list type then its a list of complex objects
|
109
|
+
if list_data.is_a?(Array)
|
110
|
+
list_data.each do |v|
|
111
|
+
obj = unmarshall_obj(obj_name, v)
|
112
|
+
obj_list.push(obj)
|
113
|
+
end
|
114
|
+
else # Else its a map (hopefully) and its only a single complex object
|
115
|
+
obj = unmarshall_obj(obj_name, list_data)
|
116
|
+
obj_list.push(obj)
|
117
|
+
end
|
118
|
+
return obj_list
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def to_date_list(list_data)
|
123
|
+
date_list = Array.new
|
124
|
+
list_data.each do |v|
|
125
|
+
date_val = DateTime.strptime(v, "%Y-%m-%dT%H:%M:%S%z")
|
126
|
+
date_list.push(date_val)
|
127
|
+
end
|
128
|
+
return date_list
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
def unmarshall_list(list_data, data_type)
|
133
|
+
if list_data.length == 0
|
134
|
+
return Array.new
|
135
|
+
end
|
136
|
+
actual_list = list_data
|
137
|
+
if data_type == "date"
|
138
|
+
return to_date_list(actual_list)
|
139
|
+
elsif DATA_TYPES.include?(data_type)
|
140
|
+
return actual_list
|
141
|
+
else
|
142
|
+
return to_obj_list(data_type, actual_list)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
def unmarshall_obj(obj_name, data)
|
148
|
+
# puts "Unmarshalling object: "+obj_name
|
149
|
+
if !@object_map.has_key?(obj_name)
|
150
|
+
raise MarshallException.new("Unknown object: "+obj_name.to_s)
|
151
|
+
end
|
152
|
+
class_obj = @object_map[obj_name]
|
153
|
+
instance = class_obj.new
|
154
|
+
type_map_name = '@_'+obj_name+'__type_map'
|
155
|
+
type_map = instance.instance_variable_get(type_map_name)
|
156
|
+
data.each_pair do |k,v|
|
157
|
+
if !type_map.include?(k)
|
158
|
+
raise MarshallException.new("Unknown type for field: "+k)
|
159
|
+
end
|
160
|
+
field_type = type_map[k]
|
161
|
+
if field_type.is_a?(Array)
|
162
|
+
field_type = field_type[0]
|
163
|
+
end
|
164
|
+
if v.is_a?(Hash)
|
165
|
+
um_obj = unmarshall_obj(field_type, v)
|
166
|
+
instance.instance_variable_set('@'+k, um_obj)
|
167
|
+
elsif v.is_a?(Array)
|
168
|
+
um_list = unmarshall_list(v, field_type)
|
169
|
+
instance.instance_variable_set('@'+k, um_list)
|
170
|
+
else
|
171
|
+
if field_type == "date"
|
172
|
+
date_val = DateTime.strptime(v, "%Y-%m-%dT%H:%M:%S%z")
|
173
|
+
instance.instance_variable_set('@'+k, date_val)
|
174
|
+
elsif field_type == "int"
|
175
|
+
instance.instance_variable_set('@'+k, v.to_i)
|
176
|
+
elsif field_type == "double"
|
177
|
+
instance.instance_variable_set('@'+k, v.to_f)
|
178
|
+
elsif field_type == "long"
|
179
|
+
instance.instance_variable_set('@'+k, v.to_i)
|
180
|
+
else
|
181
|
+
instance.instance_variable_set('@'+k, v)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
return instance
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
################################
|
190
|
+
# XML Marshaller
|
191
|
+
################################
|
192
|
+
|
193
|
+
class XmlMarshaller < Marshaller
|
194
|
+
def initialize()
|
195
|
+
super
|
196
|
+
end
|
197
|
+
|
198
|
+
def marshall(request)
|
199
|
+
root_tag_name = request.class.name.split('::').last
|
200
|
+
return ['<', root_tag_name,'>', encode_obj_xml(request), '</', root_tag_name, '>'].join('')
|
201
|
+
end
|
202
|
+
|
203
|
+
def unmarshall(xml_data)
|
204
|
+
xml_doc = Nokogiri::XML(xml_data)
|
205
|
+
root_node = xml_doc.root
|
206
|
+
return unmarshall_obj(root_node)
|
207
|
+
end
|
208
|
+
|
209
|
+
def marshall_error(error)
|
210
|
+
if not error.is_a?(Distelli::BaseException)
|
211
|
+
raise StandardError.new("Cannot marshall error: "+error.inspect)
|
212
|
+
end
|
213
|
+
|
214
|
+
err_msg = error.err_msg
|
215
|
+
err_code = error.err_code
|
216
|
+
return "<Error><code>"+err_code.to_s+"</code><message>"+err_msg.to_s+"</message></Error>"
|
217
|
+
end
|
218
|
+
|
219
|
+
##########################################################
|
220
|
+
# Unmarshalls an xml error response and returns the error
|
221
|
+
# code and message.
|
222
|
+
#
|
223
|
+
# This is what an xml error looks like:
|
224
|
+
#
|
225
|
+
# <Error>
|
226
|
+
# <code>MalformedRequest</code>
|
227
|
+
# <message>The request is malformed</message>
|
228
|
+
# </Error>
|
229
|
+
##########################################################
|
230
|
+
def unmarshall_error(xml_data)
|
231
|
+
xml_doc = Nokogiri::XML(xml_data)
|
232
|
+
err_code_elem = xml_doc.xpath('//'+ServiceConstants::ERROR_KEY+'//'+ServiceConstants::ERR_CODE_KEY)
|
233
|
+
err_msg_elem = xml_doc.xpath('//'+ServiceConstants::ERROR_KEY+'//'+ServiceConstants::ERR_MSG_KEY)
|
234
|
+
err_code = nil
|
235
|
+
err_msg = nil
|
236
|
+
if err_code_elem != nil
|
237
|
+
err_code = err_code_elem.text
|
238
|
+
end
|
239
|
+
if err_msg_elem != nil
|
240
|
+
err_msg = err_msg_elem.text
|
241
|
+
end
|
242
|
+
return [err_code, err_msg]
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
def get_node_text(node)
|
247
|
+
children = node.children
|
248
|
+
if children.length == 0
|
249
|
+
return nil
|
250
|
+
end
|
251
|
+
if children.length != 1
|
252
|
+
raise MarshallException.new(node.to_s+" is not a text node")
|
253
|
+
end
|
254
|
+
|
255
|
+
text_node = children[0]
|
256
|
+
return text_node.content
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
def unmarshall_list(obj_node, list_type)
|
261
|
+
# puts "Unmarshalling list at "+obj_node.to_s
|
262
|
+
list_elements = obj_node.children
|
263
|
+
list_data = Array.new
|
264
|
+
list_elements.each do |elem|
|
265
|
+
# If its a simple type list
|
266
|
+
# puts "Unmarshalling list elemnt "+elem.name+" "+list_type
|
267
|
+
if list_type == "date"
|
268
|
+
obj_node_content = get_node_text(elem)
|
269
|
+
date_val = DateTime.strptime(obj_node.content, "%Y-%m-%dT%H:%M:%S%z")
|
270
|
+
list_data.push(date_val)
|
271
|
+
elsif DATA_TYPES.include?(list_type)
|
272
|
+
# puts "Unmarshalling primitive list element "+elem.name+" "+list_type
|
273
|
+
elem_text = get_node_text(elem)
|
274
|
+
if list_type == "int"
|
275
|
+
list_data.push(elem_text.to_i)
|
276
|
+
elsif list_type == "long"
|
277
|
+
list_data.push(elem_text.to_i)
|
278
|
+
elsif list_type == "boolean"
|
279
|
+
if elem_text.downcase == "true"
|
280
|
+
list_data.push(true)
|
281
|
+
else
|
282
|
+
list_data.push(false)
|
283
|
+
end
|
284
|
+
elsif list_type == "double"
|
285
|
+
list_data.push(elem_text.to_f)
|
286
|
+
else
|
287
|
+
list_data.push(elem_text)
|
288
|
+
end
|
289
|
+
else
|
290
|
+
# puts "Unmarshalling complex list element"+elem.name+" "+list_type
|
291
|
+
# Else its a list of complex types
|
292
|
+
unmarshalled_obj = unmarshall_obj(elem)
|
293
|
+
list_data.push(unmarshalled_obj)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
return list_data
|
297
|
+
end
|
298
|
+
|
299
|
+
private
|
300
|
+
def unmarshall_obj(obj_node, parent_obj=nil)
|
301
|
+
# puts "Unmarshalling "+obj_node.name+" "+obj_node.inspect+" member of "+parent_obj.to_s
|
302
|
+
obj_name = obj_node.name
|
303
|
+
obj_instance = nil
|
304
|
+
if parent_obj == nil
|
305
|
+
if !@object_map.has_key?(obj_name)
|
306
|
+
raise MarshallException.new("Unknown object: "+obj_name.to_s)
|
307
|
+
end
|
308
|
+
class_obj = @object_map[obj_name]
|
309
|
+
obj_instance = class_obj.new
|
310
|
+
else
|
311
|
+
# Parent object is not none. So obj_node is a member of
|
312
|
+
# obj_instance and we can get the type from the parent obj
|
313
|
+
# First get the type map
|
314
|
+
# puts "Processing "+obj_name
|
315
|
+
type_map_name = '@_'+parent_obj.class.name.split('::').last+'__type_map'
|
316
|
+
type_map = parent_obj.instance_variable_get(type_map_name)
|
317
|
+
if type_map == nil
|
318
|
+
raise MarshallException.new(parent_obj.class.name+" cannot be unmarshalled. Missing type_map: "+type_map_name)
|
319
|
+
end
|
320
|
+
|
321
|
+
if !type_map.include?(obj_name)
|
322
|
+
raise MarshallException.new("Unknown type for field: "+obj_name+" in obj "+obj_instance.to_s)
|
323
|
+
end
|
324
|
+
obj_type = type_map[obj_name]
|
325
|
+
# puts "Unmarshalling "+obj_name+" "+obj_type.to_s
|
326
|
+
# Obj type can be either a list or one of the defined
|
327
|
+
# primitive types or a complex type
|
328
|
+
if obj_type.is_a?(Array)
|
329
|
+
list_type = obj_type[0]
|
330
|
+
# puts obj_name+" is a list in "+parent_obj.to_s+" of type "+list_type
|
331
|
+
unmarshalled_list = unmarshall_list(obj_node, list_type)
|
332
|
+
# puts "Unmarshalled list"+unmarshalled_list.to_s
|
333
|
+
parent_obj.instance_variable_set('@'+obj_name, unmarshalled_list)
|
334
|
+
return obj_instance
|
335
|
+
elsif obj_type == "date"
|
336
|
+
date_val = DateTime.strptime(obj_node.content, "%Y-%m-%dT%H:%M:%S%z")
|
337
|
+
parent_obj.instance_variable_set('@'+obj_name, date_val)
|
338
|
+
return obj_instance
|
339
|
+
elsif DATA_TYPES.include?(obj_type)
|
340
|
+
# puts obj_name+" is a primitive "+obj_type+" "+obj_node+" in "+parent_obj.to_s
|
341
|
+
if obj_type == "int" or obj_type == "long"
|
342
|
+
parent_obj.instance_variable_set('@'+obj_name, get_node_text(obj_node).to_i)
|
343
|
+
elsif obj_type == "boolean"
|
344
|
+
obj_text = get_node_text(obj_node)
|
345
|
+
if obj_text.downcase == "true"
|
346
|
+
parent_obj.instance_variable_set('@'+obj_name, true)
|
347
|
+
else
|
348
|
+
parent_obj.instance_variable_set('@'+obj_name, false)
|
349
|
+
end
|
350
|
+
elsif obj_type == "double"
|
351
|
+
parent_obj.instance_variable_set('@'+obj_name, get_node_text(obj_node).to_f)
|
352
|
+
else
|
353
|
+
parent_obj.instance_variable_set('@'+obj_name, get_node_text(obj_node))
|
354
|
+
end
|
355
|
+
return obj_instance
|
356
|
+
else
|
357
|
+
# puts obj_name+" is a complex type "+obj_type+" in "+parent_obj.to_s
|
358
|
+
if !@object_map.has_key?(obj_type)
|
359
|
+
raise MarshallException.new("Unknown object: "+obj_type.to_s)
|
360
|
+
end
|
361
|
+
|
362
|
+
class_obj = @object_map[obj_type]
|
363
|
+
obj_instance = class_obj.new
|
364
|
+
parent_obj.instance_variable_set('@'+obj_name, obj_instance)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
# puts "Processing children of "+obj_node.name
|
368
|
+
children = obj_node.children
|
369
|
+
children.each do |child|
|
370
|
+
unmarshall_obj(child, parent_obj=obj_instance)
|
371
|
+
end
|
372
|
+
return obj_instance
|
373
|
+
end
|
374
|
+
|
375
|
+
private
|
376
|
+
def encode_primitive_xml(tag_name, value)
|
377
|
+
if value.is_a?(DateTime)
|
378
|
+
return ['<', tag_name, '>', value.strftime("%Y-%m-%dT%H:%M:%S%z"), '</', tag_name, '>'].join('')
|
379
|
+
else
|
380
|
+
['<', tag_name, '>', value.to_s, '</', tag_name, '>'].join('')
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
private
|
385
|
+
def list_to_xml(list_obj, list_type)
|
386
|
+
xml_data = Array.new
|
387
|
+
list_obj.each do |item|
|
388
|
+
if item.is_a?(DateTime) or item.is_a?(Integer) or item.is_a?(Float) or item.is_a?(String) or item.is_a?(TrueClass) or item.is_a?(FalseClass)
|
389
|
+
xml_data.push(encode_primitive_xml(list_type, item))
|
390
|
+
else
|
391
|
+
xml_data.push(['<', list_type, '>', encode_obj_xml(item), '</', list_type, '>'].join(''))
|
392
|
+
end
|
393
|
+
end
|
394
|
+
return xml_data.join('')
|
395
|
+
end
|
396
|
+
|
397
|
+
private
|
398
|
+
def encode_obj_xml(obj)
|
399
|
+
type_map_name = '_'+obj.class.name.split('::').last+'__type_map'
|
400
|
+
type_map = obj.instance_variable_get('@'+type_map_name)
|
401
|
+
xml_data = Array.new
|
402
|
+
|
403
|
+
obj.instance_variables.each do |var|
|
404
|
+
k = var.to_s.delete('@')
|
405
|
+
# puts "Var: "+var.to_s+" TMN: "+type_map_name.to_s
|
406
|
+
if k == type_map_name
|
407
|
+
next
|
408
|
+
end
|
409
|
+
v = obj.instance_variable_get(var)
|
410
|
+
data_type = type_map[k]
|
411
|
+
|
412
|
+
if data_type.is_a?(Array)
|
413
|
+
data_type = data_type[0]
|
414
|
+
end
|
415
|
+
|
416
|
+
# print "Encoding "+k.to_s+" "+v.to_s
|
417
|
+
key = k
|
418
|
+
if v.is_a?(Array)
|
419
|
+
xml_data.push(['<', key, '>', list_to_xml(v, data_type), '</', key, '>'].join(''))
|
420
|
+
elsif v.is_a?(DateTime)
|
421
|
+
xml_data.push(encode_primitive_xml(key, v))
|
422
|
+
elsif DATA_TYPES.include?(data_type)
|
423
|
+
xml_data.push(encode_primitive_xml(key, v))
|
424
|
+
else
|
425
|
+
xml_data.push(['<', key, '>', encode_obj_xml(v), '</', key, '>'].join(''))
|
426
|
+
end
|
427
|
+
end
|
428
|
+
return xml_data.join('')
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: DistelliServiceMarshallers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rahul Singh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: DistelliServiceInterface
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: nokogiri
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Distelli Service Marshallers for Ruby Servers and Clients
|
47
|
+
email: rsingh@distelli.com
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- lib/distelli/servicemarshallers.rb
|
53
|
+
homepage: http://www.distelli.com/
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.8.23
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Distelli Service Marshaller classes
|
77
|
+
test_files: []
|