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.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative './sequel/plugins/join_by_paths.rb'
4
+
5
+ Sequel::Model.plugin Sequel::Plugins::JoinByPaths
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
- def handle_entity_expand_one(entity:, exp_one:, nav_values_h:, nav_coll_h:,
17
- uribase:)
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 = %r{\A(\w+)\/?(.*)\z}.match(exp_one)
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
- if (enval = entity.nav_values[k])
32
- nav_values_h[k.to_s] = get_entity_odata_h(entity: enval,
33
- expand: rest_exp,
34
- uribase: uribase)
35
- elsif (encoll = entity.nav_coll[k])
36
- # nav attributes that are a collection (x..n)
37
- nav_coll_h[k.to_s] = encoll.map do |xe|
38
- if xe
39
- get_entity_odata_h(entity: xe, expand: rest_exp,
40
- uribase: uribase)
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
- hres.merge!(entity.values)
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(%r{/\z}, '')
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
- @collections.each do |klass|
374
- # 1. all EntityType
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
- assoc.add_element('End', assoend)
438
- end
439
- end
432
+ # 2. Associations
433
+ add_metadata_xml_associations(schema)
440
434
 
441
435
  # 3. Enty container
442
- ec = schema.add_element('EntityContainer',
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
- # TODO: a nicer way of extracting the content-id number?
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?('application/xml')
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, { 'Content-Type' => 'application/atomsvc+xml;charset=utf-8' },
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?('application/xml')
587
- [200, { 'Content-Type' => 'application/xml;charset=utf-8' },
588
- @service.metadata_xml(req)]
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.2.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-09-15 00:00:00.000000000 Z
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.3.0
144
+ version: 2.4.0
137
145
  required_rubygems_version: !ruby/object:Gem::Requirement
138
146
  requirements:
139
147
  - - ">="