safrano 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/multipart.rb +92 -91
- data/lib/odata/attribute.rb +15 -6
- data/lib/odata/collection.rb +109 -106
- data/lib/odata/collection_filter.rb +14 -485
- data/lib/odata/collection_order.rb +15 -22
- data/lib/odata/entity.rb +31 -41
- data/lib/odata/filter/error.rb +53 -0
- data/lib/odata/filter/parse.rb +171 -0
- data/lib/odata/filter/sequel.rb +208 -0
- data/lib/odata/filter/token.rb +59 -0
- data/lib/odata/filter/tree.rb +368 -0
- data/lib/odata/relations.rb +36 -0
- data/lib/odata/url_parameters.rb +58 -0
- data/lib/odata/walker.rb +55 -42
- data/lib/request.rb +1 -3
- data/lib/safrano.rb +17 -0
- data/lib/safrano_core.rb +30 -7
- data/lib/sequel/plugins/join_by_paths.rb +239 -0
- data/lib/sequel_join_by_paths.rb +5 -0
- data/lib/service.rb +84 -112
- metadata +11 -3
data/lib/service.rb
CHANGED
@@ -9,14 +9,15 @@ module OData
|
|
9
9
|
# this module has all methods related to expand/defered output preparation
|
10
10
|
# and will be included in Service class
|
11
11
|
module ExpandHandler
|
12
|
+
PATH_SPLITTER = %r{\A(\w+)\/?(.*)\z}.freeze
|
12
13
|
def get_deferred_odata_h(entity:, attrib:, uribase:)
|
13
14
|
{ '__deferred' => { 'uri' => "#{entity.uri(uribase)}/#{attrib}" } }
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
# split expand argument into first and rest part
|
18
|
+
def split_entity_expand_arg(exp_one)
|
18
19
|
if exp_one.include?('/')
|
19
|
-
m =
|
20
|
+
m = PATH_SPLITTER.match(exp_one)
|
20
21
|
cur_exp = m[1].strip
|
21
22
|
rest_exp = m[2]
|
22
23
|
# TODO: check errorhandling
|
@@ -27,17 +28,24 @@ module OData
|
|
27
28
|
k = exp_one.strip.to_sym
|
28
29
|
rest_exp = nil
|
29
30
|
end
|
31
|
+
yield k, rest_exp
|
32
|
+
end
|
33
|
+
|
34
|
+
# handle a single expand
|
35
|
+
def handle_entity_expand_one(entity:, exp_one:, nav_values_h:, nav_coll_h:,
|
36
|
+
uribase:)
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
split_entity_expand_arg(exp_one) do |first, rest_exp|
|
39
|
+
if (enval = entity.nav_values[first])
|
40
|
+
nav_values_h[first.to_s] = get_entity_odata_h(entity: enval,
|
41
|
+
expand: rest_exp,
|
42
|
+
uribase: uribase)
|
43
|
+
elsif (encoll = entity.nav_coll[first])
|
44
|
+
# nav attributes that are a collection (x..n)
|
45
|
+
nav_coll_h[first.to_s] = encoll.map do |xe|
|
46
|
+
if xe
|
47
|
+
get_entity_odata_h(entity: xe, expand: rest_exp, uribase: uribase)
|
48
|
+
end
|
41
49
|
end
|
42
50
|
end
|
43
51
|
end
|
@@ -83,7 +91,9 @@ module OData
|
|
83
91
|
def get_entity_odata_h(entity:, expand: nil, uribase:)
|
84
92
|
hres = {}
|
85
93
|
hres['__metadata'] = entity.metadata_h(uribase: uribase)
|
86
|
-
|
94
|
+
|
95
|
+
# hres.merge!(entity.values)
|
96
|
+
hres.merge!(entity.casted_values)
|
87
97
|
|
88
98
|
nav_values_h = {}
|
89
99
|
nav_coll_h = {}
|
@@ -97,7 +107,6 @@ module OData
|
|
97
107
|
end
|
98
108
|
|
99
109
|
# handle not expanded (deferred) nav attributes
|
100
|
-
# TODO: better design/perf
|
101
110
|
handle_entity_deferred_attribs(entity: entity,
|
102
111
|
nav_values_h: nav_values_h,
|
103
112
|
nav_coll_h: nav_coll_h,
|
@@ -176,6 +185,8 @@ module OData
|
|
176
185
|
end
|
177
186
|
|
178
187
|
DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
|
188
|
+
TRAILING_SLASH = %r{/\z}.freeze
|
189
|
+
|
179
190
|
# input is the DataServiceVersion request header string, eg.
|
180
191
|
# '2.0;blabla' ---> Version -> 2
|
181
192
|
def self.parse_data_service_version(inp)
|
@@ -213,7 +224,7 @@ module OData
|
|
213
224
|
end
|
214
225
|
|
215
226
|
def path_prefix(path_pr)
|
216
|
-
@xpath_prefix = path_pr.sub(
|
227
|
+
@xpath_prefix = path_pr.sub(TRAILING_SLASH, '')
|
217
228
|
end
|
218
229
|
# end public API
|
219
230
|
|
@@ -339,6 +350,51 @@ module OData
|
|
339
350
|
XML_PREAMBLE + doc.to_pretty_xml
|
340
351
|
end
|
341
352
|
|
353
|
+
def add_metadata_xml_entity_type(schema)
|
354
|
+
@collections.each do |klass|
|
355
|
+
enty = klass.add_metadata_rexml(schema)
|
356
|
+
klass.add_metadata_navs_rexml(enty, @cmap, @relman, @xnamespace)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def add_metadata_xml_associations(schema)
|
361
|
+
@relman.each_rel do |rel|
|
362
|
+
rel.with_metadata_info(@xnamespace) do |name, bdinfo|
|
363
|
+
assoc = schema.add_element('Association', 'Name' => name)
|
364
|
+
bdinfo.each do |bdi|
|
365
|
+
assoend = { 'Type' => bdi[:type],
|
366
|
+
'Role' => bdi[:role],
|
367
|
+
'Multiplicity' => bdi[:multiplicity] }
|
368
|
+
assoc.add_element('End', assoend)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def add_metadata_xml_entity_container(schema)
|
375
|
+
ec = schema.add_element('EntityContainer',
|
376
|
+
'Name' => @xname,
|
377
|
+
'm:IsDefaultEntityContainer' => 'true')
|
378
|
+
@collections.each do |klass|
|
379
|
+
# 3.a Entity set's
|
380
|
+
ec.add_element('EntitySet',
|
381
|
+
'Name' => klass.entity_set_name,
|
382
|
+
'EntityType' => "#{@xnamespace}.#{klass.type_name}")
|
383
|
+
end
|
384
|
+
# 3.b Association set's
|
385
|
+
@relman.each_rel do |rel|
|
386
|
+
assoc = ec.add_element('AssociationSet',
|
387
|
+
'Name' => rel.name,
|
388
|
+
'Association' => "#{@xnamespace}.#{rel.name}")
|
389
|
+
|
390
|
+
rel.each_endobj do |eo|
|
391
|
+
clazz = Object.const_get(eo)
|
392
|
+
assoend = { 'EntitySet' => clazz.entity_set_name.to_s, 'Role' => eo }
|
393
|
+
assoc.add_element('End', assoend)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
342
398
|
def metadata_xml(_req)
|
343
399
|
doc = REXML::Document.new
|
344
400
|
doc.add_element('edmx:Edmx', 'Version' => '1.0')
|
@@ -370,101 +426,21 @@ module OData
|
|
370
426
|
schema = serv.add_element('Schema',
|
371
427
|
'Namespace' => @xnamespace,
|
372
428
|
'xmlns' => XMLNS::MSFT_ADO_2009_EDM)
|
373
|
-
|
374
|
-
|
375
|
-
enty = schema.add_element('EntityType', 'Name' => klass.to_s)
|
376
|
-
# with their properties
|
377
|
-
klass.db_schema.each do |pnam, prop|
|
378
|
-
if prop[:primary_key] == true
|
379
|
-
enty.add_element('Key').add_element('PropertyRef',
|
380
|
-
'Name' => pnam.to_s)
|
381
|
-
end
|
382
|
-
# attrs = {'Name'=>pnam.to_s,
|
383
|
-
# 'Type'=>OData::TypeMap[ prop[:db_type] ]}
|
384
|
-
attrs = { 'Name' => pnam.to_s,
|
385
|
-
'Type' => OData.get_edm_type(db_type: prop[:db_type]) }
|
386
|
-
attrs['Nullable'] = 'false' if prop[:allow_null] == false
|
387
|
-
enty.add_element('Property', attrs)
|
388
|
-
end
|
389
|
-
# and their Nav attributes == Sequel Model association
|
390
|
-
klass.associations.each do |assoc|
|
391
|
-
# associated objects need to be in the map...
|
392
|
-
next unless @cmap[assoc.to_s]
|
393
|
-
|
394
|
-
from = klass.type_name
|
395
|
-
to = @cmap[assoc.to_s].type_name
|
396
|
-
|
397
|
-
rel = @relman.get([from, to])
|
398
|
-
|
399
|
-
# use Sequel reflection to get multiplicity (will be used later
|
400
|
-
# in 2. Associations below)
|
401
|
-
reflect = klass.association_reflection(assoc)
|
402
|
-
case reflect[:type]
|
403
|
-
# TODO: use multiplicity 1 when needed instead of '0..1'
|
404
|
-
when :one_to_one
|
405
|
-
rel.set_multiplicity(from, '0..1')
|
406
|
-
rel.set_multiplicity(to, '0..1')
|
407
|
-
when :one_to_many
|
408
|
-
rel.set_multiplicity(from, '0..1')
|
409
|
-
rel.set_multiplicity(to, '*')
|
410
|
-
when :many_to_one
|
411
|
-
rel.set_multiplicity(from, '*')
|
412
|
-
rel.set_multiplicity(to, '0..1')
|
413
|
-
when :many_to_many
|
414
|
-
rel.set_multiplicity(from, '*')
|
415
|
-
rel.set_multiplicity(to, '*')
|
416
|
-
end
|
417
|
-
# <NavigationProperty Name="Supplier"
|
418
|
-
# Relationship="ODataDemo.Product_Supplier_Supplier_Products"
|
419
|
-
# FromRole="Product_Supplier" ToRole="Supplier_Products"/>
|
420
|
-
|
421
|
-
nattrs = { 'Name' => to,
|
422
|
-
'Relationship' => "#{@xnamespace}.#{rel.name}",
|
423
|
-
'FromRole' => from,
|
424
|
-
'ToRole' => to }
|
425
|
-
enty.add_element('NavigationProperty', nattrs)
|
426
|
-
end
|
427
|
-
end
|
428
|
-
# 2. Associations
|
429
|
-
@relman.each_rel do |rel|
|
430
|
-
assoc = schema.add_element('Association', 'Name' => rel.name)
|
431
|
-
|
432
|
-
rel.each_endobj do |eo|
|
433
|
-
assoend = { 'Type' => "#{@xnamespace}.#{eo}",
|
434
|
-
'Role' => eo,
|
435
|
-
'Multiplicity' => rel.multiplicity[eo] }
|
429
|
+
# 1. all EntityType
|
430
|
+
add_metadata_xml_entity_type(schema)
|
436
431
|
|
437
|
-
|
438
|
-
|
439
|
-
end
|
432
|
+
# 2. Associations
|
433
|
+
add_metadata_xml_associations(schema)
|
440
434
|
|
441
435
|
# 3. Enty container
|
442
|
-
|
443
|
-
'Name' => @xname,
|
444
|
-
'm:IsDefaultEntityContainer' => 'true')
|
445
|
-
@collections.each do |klass|
|
446
|
-
# 3.a Entity set's
|
447
|
-
ec.add_element('EntitySet',
|
448
|
-
'Name' => klass.entity_set_name,
|
449
|
-
'EntityType' => "#{@xnamespace}.#{klass.type_name}")
|
450
|
-
end
|
451
|
-
# 3.b Association set's
|
452
|
-
@relman.each_rel do |rel|
|
453
|
-
assoc = ec.add_element('AssociationSet',
|
454
|
-
'Name' => rel.name,
|
455
|
-
'Association' => "#{@xnamespace}.#{rel.name}")
|
436
|
+
add_metadata_xml_entity_container(schema)
|
456
437
|
|
457
|
-
rel.each_endobj do |eo|
|
458
|
-
clazz = Object.const_get(eo)
|
459
|
-
assoend = { 'EntitySet' => clazz.entity_set_name.to_s, 'Role' => eo }
|
460
|
-
assoc.add_element('End', assoend)
|
461
|
-
end
|
462
|
-
end
|
463
438
|
XML_PREAMBLE + doc.to_pretty_xml
|
464
439
|
end
|
465
440
|
|
466
441
|
# methods related to transitions to next state (cf. walker)
|
467
442
|
module Transitions
|
443
|
+
DOLLAR_ID_REGEXP = Regexp.new('\A\/\$').freeze
|
468
444
|
def allowed_transitions
|
469
445
|
@allowed_transitions = [
|
470
446
|
Safrano::TransitionEnd,
|
@@ -485,8 +461,7 @@ module OData
|
|
485
461
|
end
|
486
462
|
|
487
463
|
def transition_content_id(match_result)
|
488
|
-
|
489
|
-
[match_result[1].sub!(Regexp.new('\A\/\$'), ''), :run_with_content_id]
|
464
|
+
[match_result[2], :run_with_content_id]
|
490
465
|
end
|
491
466
|
|
492
467
|
def transition_metadata(_match_result)
|
@@ -501,16 +476,14 @@ module OData
|
|
501
476
|
include Transitions
|
502
477
|
|
503
478
|
def odata_get(req)
|
504
|
-
if req.accept?(
|
479
|
+
if req.accept?(APPXML)
|
505
480
|
# app.headers 'Content-Type' => 'application/xml;charset=utf-8'
|
506
481
|
# Doc: 2.2.3.7.1 Service Document As per [RFC5023], AtomPub Service
|
507
482
|
# Documents MUST be
|
508
483
|
# identified with the "application/atomsvc+xml" media type (see
|
509
484
|
# [RFC5023] section 8).
|
510
|
-
[200,
|
511
|
-
service_xml(req)]
|
485
|
+
[200, CT_ATOMXML, service_xml(req)]
|
512
486
|
else
|
513
|
-
# TODO: other formats ?
|
514
487
|
# this is returned by http://services.odata.org/V2/OData/OData.svc
|
515
488
|
415
|
516
489
|
end
|
@@ -583,10 +556,9 @@ module OData
|
|
583
556
|
end
|
584
557
|
|
585
558
|
def odata_get(req)
|
586
|
-
if req.accept?(
|
587
|
-
[200,
|
588
|
-
|
589
|
-
else # TODO: other formats
|
559
|
+
if req.accept?(APPXML)
|
560
|
+
[200, CT_APPXML, @service.metadata_xml(req)]
|
561
|
+
else
|
590
562
|
415
|
591
563
|
end
|
592
564
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safrano
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- D.M.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -108,7 +108,13 @@ files:
|
|
108
108
|
- lib/odata/collection_order.rb
|
109
109
|
- lib/odata/entity.rb
|
110
110
|
- lib/odata/error.rb
|
111
|
+
- lib/odata/filter/error.rb
|
112
|
+
- lib/odata/filter/parse.rb
|
113
|
+
- lib/odata/filter/sequel.rb
|
114
|
+
- lib/odata/filter/token.rb
|
115
|
+
- lib/odata/filter/tree.rb
|
111
116
|
- lib/odata/relations.rb
|
117
|
+
- lib/odata/url_parameters.rb
|
112
118
|
- lib/odata/walker.rb
|
113
119
|
- lib/odata_rack_builder.rb
|
114
120
|
- lib/rack_app.rb
|
@@ -116,6 +122,8 @@ files:
|
|
116
122
|
- lib/response.rb
|
117
123
|
- lib/safrano.rb
|
118
124
|
- lib/safrano_core.rb
|
125
|
+
- lib/sequel/plugins/join_by_paths.rb
|
126
|
+
- lib/sequel_join_by_paths.rb
|
119
127
|
- lib/service.rb
|
120
128
|
homepage: https://gitlab.com/dm0da/safrano
|
121
129
|
licenses:
|
@@ -133,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
141
|
requirements:
|
134
142
|
- - ">="
|
135
143
|
- !ruby/object:Gem::Version
|
136
|
-
version: 2.
|
144
|
+
version: 2.4.0
|
137
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
146
|
requirements:
|
139
147
|
- - ">="
|