safrano 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  - - ">="