safrano 0.2.0 → 0.3.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.
- 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
|
- - ">="
|