safrano 0.5.3 → 0.6.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/core_ext/Date/format.rb +47 -0
- data/lib/core_ext/DateTime/format.rb +54 -0
- data/lib/core_ext/Dir/iter.rb +7 -5
- data/lib/core_ext/Hash/transform.rb +14 -6
- data/lib/core_ext/Numeric/convert.rb +25 -0
- data/lib/core_ext/Time/format.rb +71 -0
- data/lib/core_ext/date.rb +5 -0
- data/lib/core_ext/datetime.rb +5 -0
- data/lib/core_ext/numeric.rb +3 -0
- data/lib/core_ext/time.rb +5 -0
- data/lib/odata/attribute.rb +8 -6
- data/lib/odata/batch.rb +3 -3
- data/lib/odata/collection.rb +9 -9
- data/lib/odata/collection_filter.rb +3 -1
- data/lib/odata/collection_media.rb +4 -27
- data/lib/odata/collection_order.rb +1 -1
- data/lib/odata/common_logger.rb +5 -27
- data/lib/odata/complex_type.rb +61 -67
- data/lib/odata/edm/primitive_types.rb +110 -42
- data/lib/odata/entity.rb +14 -47
- data/lib/odata/error.rb +7 -7
- data/lib/odata/expand.rb +2 -2
- data/lib/odata/filter/base.rb +10 -1
- data/lib/odata/filter/error.rb +2 -2
- data/lib/odata/filter/parse.rb +16 -2
- data/lib/odata/filter/sequel.rb +31 -4
- data/lib/odata/filter/sequel_datetime_adapter.rb +21 -0
- data/lib/odata/filter/token.rb +18 -5
- data/lib/odata/filter/tree.rb +83 -9
- data/lib/odata/function_import.rb +19 -18
- data/lib/odata/model_ext.rb +96 -38
- data/lib/odata/request/json.rb +171 -0
- data/lib/odata/transition.rb +13 -9
- data/lib/odata/url_parameters.rb +3 -3
- data/lib/odata/walker.rb +9 -9
- data/lib/safrano/multipart.rb +1 -3
- data/lib/safrano/rack_app.rb +2 -14
- data/lib/safrano/rack_builder.rb +0 -15
- data/lib/safrano/request.rb +3 -3
- data/lib/safrano/response.rb +3 -3
- data/lib/safrano/service.rb +43 -12
- data/lib/safrano/type_mapping.rb +149 -0
- data/lib/safrano/version.rb +1 -2
- data/lib/safrano.rb +3 -0
- metadata +54 -15
data/lib/odata/model_ext.rb
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
# Thus Below we have called that "EntityClass". It's meant as "Collection"
|
6
6
|
|
7
7
|
require 'json'
|
8
|
+
require 'base64'
|
8
9
|
require 'rexml/document'
|
9
10
|
require 'bigdecimal'
|
10
11
|
require_relative '../safrano/core'
|
@@ -32,9 +33,14 @@ module Safrano
|
|
32
33
|
attr_reader :default_template
|
33
34
|
attr_reader :uri
|
34
35
|
attr_reader :odata_upk_parts
|
35
|
-
attr_reader :
|
36
|
-
attr_reader :decimal_cols
|
36
|
+
attr_reader :casted_cols
|
37
37
|
attr_reader :namespace
|
38
|
+
# store cols metata here in the model (sub)-class. Initially we stored this infos (eg. edm_types etc)
|
39
|
+
# directly into sequels db_schema hash. But this hash is on the upper Sequel::Model(table) class
|
40
|
+
# and is shared by all subclasses.
|
41
|
+
# By storing it separately here we are less dependant from Sequel, and have less issues with
|
42
|
+
# testing with multiples models class derived from same Sequel::Model(table)
|
43
|
+
attr_reader :cols_metadata
|
38
44
|
|
39
45
|
# initialising block of code to be executed at end of
|
40
46
|
# ServerApp.publish_service after all model classes have been registered
|
@@ -75,7 +81,7 @@ module Safrano
|
|
75
81
|
@uparms = nil
|
76
82
|
@params = nil
|
77
83
|
@cx = nil
|
78
|
-
|
84
|
+
@cols_metadata = {}
|
79
85
|
end
|
80
86
|
|
81
87
|
def build_uri(uribase)
|
@@ -96,11 +102,10 @@ module Safrano
|
|
96
102
|
|
97
103
|
# Factory json-> Model Object instance
|
98
104
|
def new_from_hson_h(hash)
|
99
|
-
#enty = new
|
100
|
-
#enty.set_fields(hash, data_fields, missing: :skip)
|
101
|
-
|
102
|
-
#enty.set(hash)
|
103
|
-
enty
|
105
|
+
# enty = new
|
106
|
+
# enty.set_fields(hash, data_fields, missing: :skip)
|
107
|
+
create(hash)
|
108
|
+
# enty.set(hash)
|
104
109
|
end
|
105
110
|
|
106
111
|
def attrib_path_valid?(path)
|
@@ -178,13 +183,14 @@ module Safrano
|
|
178
183
|
end
|
179
184
|
# with their properties
|
180
185
|
db_schema.each do |pnam, prop|
|
186
|
+
metadata = @cols_metadata[pnam]
|
181
187
|
if prop[:primary_key] == true
|
182
188
|
enty.add_element('Key').add_element('PropertyRef',
|
183
189
|
'Name' => pnam.to_s)
|
184
190
|
end
|
185
191
|
attrs = { 'Name' => pnam.to_s,
|
186
192
|
# 'Type' => Safrano.get_edm_type(db_type: prop[:db_type]) }
|
187
|
-
'Type' =>
|
193
|
+
'Type' => metadata[:edm_type] }
|
188
194
|
attrs['Nullable'] = 'false' if prop[:allow_null] == false
|
189
195
|
enty.add_element('Property', attrs)
|
190
196
|
end
|
@@ -346,30 +352,82 @@ module Safrano
|
|
346
352
|
|
347
353
|
def build_default_template
|
348
354
|
@default_template = { all_values: EMPTYH }
|
349
|
-
if @nav_entity_attribs || @nav_collection_attribs
|
350
|
-
|
355
|
+
@default_template[:deferr] = (@nav_entity_attribs&.keys || []) + (@nav_collection_attribs&.keys || EMPTY_ARRAY) if @nav_entity_attribs || @nav_collection_attribs
|
356
|
+
end
|
357
|
+
|
358
|
+
def build_casted_cols(service)
|
359
|
+
# cols needed catsting before final json output
|
360
|
+
@casted_cols = {}
|
361
|
+
db_schema.each do |col, props|
|
362
|
+
# first check if we have user-defined type mapping
|
363
|
+
usermap = nil
|
364
|
+
dbtyp = props[:db_type]
|
365
|
+
metadata = @cols_metadata[col]
|
366
|
+
if service.type_mappings.values.find { |map| usermap = map.match(dbtyp) }
|
367
|
+
|
368
|
+
metadata[:edm_type] = usermap.edm_type
|
369
|
+
|
370
|
+
@casted_cols[col] = usermap.castfunc
|
371
|
+
next # this will override our rules below !
|
372
|
+
end
|
373
|
+
|
374
|
+
if metadata[:edm_precision] && (metadata[:edm_type] =~ /\AEdm.Decimal\(/i)
|
375
|
+
# we save the precision and/or scale in the lambda (binding!)
|
376
|
+
|
377
|
+
@casted_cols[col] = if metadata[:edm_scale]
|
378
|
+
lambda { |x|
|
379
|
+
# not sure if these copies are really needed, but feels better that way
|
380
|
+
# output decimal with precision and scale
|
381
|
+
x&.toDecimalPrecisionScaleString(metadata[:edm_precision], metadata[:edm_scale])
|
382
|
+
}
|
383
|
+
else
|
384
|
+
lambda { |x|
|
385
|
+
# not sure if these copies are really needed, but feels better that way
|
386
|
+
# output decimal with precision only
|
387
|
+
x&.toDecimalPrecisionString(metadata[:edm_precision])
|
388
|
+
}
|
389
|
+
end
|
390
|
+
|
391
|
+
next
|
392
|
+
end
|
393
|
+
if metadata[:edm_type] == 'Edm.Decimal'
|
394
|
+
@casted_cols[col] = ->(x) { x&.toDecimalString }
|
395
|
+
next
|
396
|
+
end
|
397
|
+
# Odata V2 Spec:
|
398
|
+
# Edm.Binary Base64 encoded value of an EDM.Binary value represented as a JSON string
|
399
|
+
# See for example https://services.odata.org/V2/Northwind/Northwind.svc/Categories(1)?$format=json
|
400
|
+
if metadata[:edm_type] == 'Edm.Binary'
|
401
|
+
@casted_cols[col] = ->(x) { Base64.encode64(x) unless x.nil? } # Base64
|
402
|
+
next
|
403
|
+
end
|
404
|
+
# TODO: check this more in details
|
405
|
+
# NOTE: here we use :type which is the sequel defined ruby-type
|
406
|
+
if props[:type] == :datetime || props[:type] == :date
|
407
|
+
# @casted_cols[col] = ->(x) { x&.iso8601 }
|
408
|
+
@casted_cols[col] = ->(x) { x&.to_edm_json }
|
409
|
+
end
|
351
410
|
end
|
352
411
|
end
|
353
412
|
|
354
|
-
def finalize_publishing
|
413
|
+
def finalize_publishing(service)
|
355
414
|
build_type_name
|
356
415
|
|
357
416
|
# build default output template structure
|
358
417
|
build_default_template
|
359
418
|
|
360
|
-
#
|
361
|
-
@
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
# add edm_types into schema
|
366
|
-
db_schema.each do |_col, props|
|
367
|
-
props[:odata_edm_type] = Safrano.default_edm_type(ruby_type: props[:type],
|
368
|
-
db_type: props[:db_type])
|
419
|
+
# add edm_types into metadata store
|
420
|
+
@cols_metadata = {}
|
421
|
+
db_schema.each do |col, props|
|
422
|
+
metadata = @cols_metadata.key?(col) ? @cols_metadata[col] : (@cols_metadata[col] = {})
|
423
|
+
Safrano.add_edm_types(metadata, props)
|
369
424
|
end
|
370
|
-
|
371
|
-
|
372
|
-
|
425
|
+
|
426
|
+
build_casted_cols(service)
|
427
|
+
# unless @casted_cols.empty?
|
428
|
+
# require 'pry'
|
429
|
+
# binding.pry
|
430
|
+
# end
|
373
431
|
# and finally build the path lists and allowed tr's
|
374
432
|
build_attribute_path_list
|
375
433
|
build_expand_path_list
|
@@ -379,7 +437,7 @@ module Safrano
|
|
379
437
|
build_entity_allowed_transitions
|
380
438
|
|
381
439
|
# for media
|
382
|
-
finalize_media if
|
440
|
+
finalize_media if respond_to? :finalize_media
|
383
441
|
end
|
384
442
|
|
385
443
|
KEYPRED_URL_REGEXP = /\A\(\s*'?([\w=,'\s]+)'?\s*\)(.*)/.freeze
|
@@ -388,7 +446,7 @@ module Safrano
|
|
388
446
|
@pk_names = []
|
389
447
|
@pk_cast_from_string = {}
|
390
448
|
odata_upk_build = []
|
391
|
-
primary_key.each
|
449
|
+
primary_key.each do |pk|
|
392
450
|
@pk_names << pk.to_s
|
393
451
|
kvpredicate = case db_schema[pk][:type]
|
394
452
|
when :integer
|
@@ -398,19 +456,19 @@ module Safrano
|
|
398
456
|
"'?'"
|
399
457
|
end
|
400
458
|
odata_upk_build << "#{pk}=#{kvpredicate}"
|
401
|
-
|
459
|
+
end
|
402
460
|
@odata_upk_parts = odata_upk_build.join(',').split('?')
|
403
461
|
|
404
462
|
# regex parts for unordered matching
|
405
|
-
@iuk_rgx_parts = primary_key.map
|
463
|
+
@iuk_rgx_parts = primary_key.map do |pk|
|
406
464
|
kvpredicate = case db_schema[pk][:type]
|
407
465
|
when :integer
|
408
|
-
|
466
|
+
'(\\d+)'
|
409
467
|
else
|
410
468
|
"'(\\w+)'"
|
411
469
|
end
|
412
470
|
[pk, "#{pk}=#{kvpredicate}"]
|
413
|
-
|
471
|
+
end.to_h
|
414
472
|
|
415
473
|
# single regex assuming the key fields are ordered !
|
416
474
|
@iuk_rgx = /\A#{@iuk_rgx_parts.values.join(',\s*')}\z/
|
@@ -424,7 +482,7 @@ module Safrano
|
|
424
482
|
kvpredicate = case db_schema[primary_key][:type]
|
425
483
|
when :integer
|
426
484
|
@pk_cast_from_string = ->(str) { Integer(str) }
|
427
|
-
|
485
|
+
'(\\d+)'
|
428
486
|
else
|
429
487
|
"'(\\w+)'"
|
430
488
|
end
|
@@ -556,14 +614,14 @@ module Safrano
|
|
556
614
|
scan_rgx_parts = @iuk_rgx_parts.dup
|
557
615
|
mdch = {}
|
558
616
|
|
559
|
-
mid.split(/\s*,\s*/).each
|
617
|
+
mid.split(/\s*,\s*/).each do |midpart|
|
560
618
|
mval = nil
|
561
|
-
mpk, mrgx = scan_rgx_parts.find
|
619
|
+
mpk, mrgx = scan_rgx_parts.find do |_pk, rgx|
|
562
620
|
if (md = rgx.match(midpart))
|
563
621
|
mval = md[1]
|
564
622
|
end
|
565
|
-
|
566
|
-
if mpk
|
623
|
+
end
|
624
|
+
if mpk && mval
|
567
625
|
mdch[mpk] = if (pk_cast = @pk_cast_from_string[mpk])
|
568
626
|
pk_cast.call(mval)
|
569
627
|
else
|
@@ -573,7 +631,7 @@ module Safrano
|
|
573
631
|
else
|
574
632
|
return Contract::NOK
|
575
633
|
end
|
576
|
-
|
634
|
+
end
|
577
635
|
# normally arriving here we have mdch filled with key values pairs,
|
578
636
|
# but not in the model key ordering. lets just re-order the values
|
579
637
|
mdc = @iuk_rgx_parts.keys.map { |pk| mdch[pk] }
|
@@ -592,7 +650,7 @@ module Safrano
|
|
592
650
|
|
593
651
|
def parse_odata_key(rawid)
|
594
652
|
if (md = @iuk_rgx.match(rawid))
|
595
|
-
if
|
653
|
+
if @pk_cast_from_string
|
596
654
|
Contract.valid(@pk_cast_from_string.call(md[1]))
|
597
655
|
else
|
598
656
|
Contract.valid(md[1]) # no cast needed, eg for string
|
@@ -616,7 +674,7 @@ module Safrano
|
|
616
674
|
# 2. Create relationship if needed
|
617
675
|
def odata_create_entity_and_relation(req, assoc = nil, parent = nil)
|
618
676
|
# TODO: this is for v2 only...
|
619
|
-
req.with_parsed_data do |data|
|
677
|
+
req.with_parsed_data(self) do |data|
|
620
678
|
data.delete('__metadata')
|
621
679
|
|
622
680
|
# validate payload column names
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
# client parsing functionality to ease testing
|
5
|
+
|
6
|
+
module Safrano
|
7
|
+
module OData
|
8
|
+
# this is used to parse inbound json payload on POST / PUT & co
|
9
|
+
# it does not do symbolize but proper (hopefully) type casting when needed
|
10
|
+
module JSON
|
11
|
+
# def self.parse(*args)
|
12
|
+
# ::JSON.parse(*args)
|
13
|
+
# end
|
14
|
+
|
15
|
+
def self.cast_values_in(resd, typ)
|
16
|
+
typ.db_schema.each do |f, props|
|
17
|
+
metadata = typ.cols_metadata[f]
|
18
|
+
|
19
|
+
case props[:type]
|
20
|
+
when :datetime, :date
|
21
|
+
fstr = f.to_s
|
22
|
+
# resd[fstr] = Time.parse(resd[f]) if resd[fstr]
|
23
|
+
resd[fstr] = Sequel.datetime_class.from_edm_json(resd[fstr]) if resd[fstr]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
resd
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_one(*args, type)
|
31
|
+
cast_values_in(::JSON.parse(*args), type)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# This is used from the Test-suite code !
|
36
|
+
# it does recursive / deep symbolize additionally to inbound casting
|
37
|
+
module XJSON
|
38
|
+
def self.get_class_from_meta(meta)
|
39
|
+
# type is normally namespaced ! --> split.last is the class
|
40
|
+
meta[:type].split('.').last.constantize
|
41
|
+
end
|
42
|
+
|
43
|
+
# symbolise keys and cast/parse values back to the proper ruby type;
|
44
|
+
# proper type meaning the one that Sequel chooses when loading data from the DB
|
45
|
+
# apply recursively to nested navigation attributes sub-structures
|
46
|
+
def self.cast_values_in(resd)
|
47
|
+
resd.symbolize_keys!
|
48
|
+
|
49
|
+
if (defered = resd[:__deferred])
|
50
|
+
defered.symbolize_keys!
|
51
|
+
elsif meta = resd[:__metadata]
|
52
|
+
meta.symbolize_keys!
|
53
|
+
|
54
|
+
# type is normally namespaced ! --> split.last is the class
|
55
|
+
typ = get_class_from_meta(meta)
|
56
|
+
|
57
|
+
typ.db_schema.each do |f, props|
|
58
|
+
metadata = typ.cols_metadata[f]
|
59
|
+
case props[:type]
|
60
|
+
when :datetime
|
61
|
+
# resd[f] = Time.parse(resd[f]) if resd[f]
|
62
|
+
# resd[f] = DateTime.strptime(resd[f], '/Date(%Q)').to_time if resd[f]
|
63
|
+
resd[f] = Sequel.datetime_class.from_edm_json(resd[f]) if resd[f]
|
64
|
+
when :decimal
|
65
|
+
resd[f] = BigDecimal(resd[f])
|
66
|
+
when :float
|
67
|
+
resd[f] = Float(resd[f])
|
68
|
+
else
|
69
|
+
# TODO: better typ-system
|
70
|
+
# Currently Sequel loads Numeric(x,y) as Float
|
71
|
+
resd[f] = Float(resd[f]) if metadata[:edm_type] =~ /\A\s*Edm.Decimal/
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if typ.nav_entity_attribs
|
76
|
+
typ.nav_entity_attribs.each_key do |nattr|
|
77
|
+
cast_values_in(resd[nattr.to_sym]) if resd[nattr.to_sym]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if typ.nav_collection_attribs
|
82
|
+
typ.nav_collection_attribs.each_key do |ncattr|
|
83
|
+
if (resd_attr = resd[ncattr.to_sym])
|
84
|
+
if (defered = resd_attr['__deferred'])
|
85
|
+
defered.symbolize_keys! if defered
|
86
|
+
else
|
87
|
+
resd_attr.symbolize_keys! # 'results' --> :results
|
88
|
+
resd_attr[:results].each { |enty| cast_values_in(enty) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
resd
|
95
|
+
end
|
96
|
+
|
97
|
+
# symbolise attrib names (h-keys)
|
98
|
+
# apply recursively to nested navigation attributes sub-structures
|
99
|
+
def self.symbolize_attribs_in(resd)
|
100
|
+
resd.symbolize_keys!
|
101
|
+
|
102
|
+
if (defered = resd[:__deferred])
|
103
|
+
defered.symbolize_keys!
|
104
|
+
elsif meta = resd[:__metadata]
|
105
|
+
meta.symbolize_keys!
|
106
|
+
|
107
|
+
typ = get_class_from_meta(meta)
|
108
|
+
|
109
|
+
if typ.nav_entity_attribs
|
110
|
+
typ.nav_entity_attribs.each_key do |nattr|
|
111
|
+
symbolize_attribs_in(resd[nattr.to_sym])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if typ.nav_collection_attribs
|
116
|
+
typ.nav_collection_attribs.each_key do |ncattr|
|
117
|
+
if (defered = resd[ncattr.to_sym]['__deferred'])
|
118
|
+
defered.symbolize_keys!
|
119
|
+
else
|
120
|
+
resd[ncattr.to_sym].each { |enty| symbolize_attribs_in(enty) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
resd
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.parse_one_nocast(*args)
|
130
|
+
resh = ::JSON.parse(*args)
|
131
|
+
symbolize_attribs_in(resh['d'])
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.parse_one(*args)
|
135
|
+
resh = ::JSON.parse(*args)
|
136
|
+
cast_values_in(resh['d'])
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.v1_parse_coll(*args)
|
140
|
+
resh = ::JSON.parse(*args)
|
141
|
+
|
142
|
+
resh['d'].map! { |currd| cast_values_in(currd) }
|
143
|
+
|
144
|
+
resh['d']
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.parse_coll(*args)
|
148
|
+
resh = ::JSON.parse(*args)
|
149
|
+
|
150
|
+
resh['d']['results'].map! { |currd| cast_values_in(currd) }
|
151
|
+
|
152
|
+
resh['d']
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.v1_parse_coll_nocast(*args)
|
156
|
+
resh = ::JSON.parse(*args)
|
157
|
+
|
158
|
+
resh['d'].map! { |currd| symbolize_attribs_in(currd) }
|
159
|
+
|
160
|
+
resh['d']
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.parse_coll_nocast(*args)
|
164
|
+
resh = ::JSON.parse(*args)
|
165
|
+
|
166
|
+
resh['d']['results'].map! { |currd| symbolize_attribs_in(currd) }
|
167
|
+
|
168
|
+
resh['d']
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/odata/transition.rb
CHANGED
@@ -6,14 +6,14 @@ require_relative 'error'
|
|
6
6
|
module Safrano
|
7
7
|
# represents a state transition when navigating/parsing the url path
|
8
8
|
# from left to right
|
9
|
-
class Transition
|
9
|
+
class Transition
|
10
10
|
attr_accessor :trans
|
11
11
|
attr_accessor :match_result
|
12
12
|
attr_accessor :rgx
|
13
13
|
attr_reader :remain_idx
|
14
14
|
|
15
|
-
EMPTYSTR = ''
|
16
|
-
SLASH = '/'
|
15
|
+
EMPTYSTR = ''
|
16
|
+
SLASH = '/'
|
17
17
|
|
18
18
|
RESULT_BAD_REQ_ERR = [nil, :error, ::Safrano::BadRequestError].freeze
|
19
19
|
RESULT_NOT_FOUND_ERR = [nil, :error, ::Safrano::ErrorNotFound].freeze
|
@@ -52,26 +52,30 @@ module Safrano
|
|
52
52
|
ctx.method(@trans).call(@match_result)
|
53
53
|
end
|
54
54
|
end
|
55
|
-
|
56
|
-
#Transition that does not move/change the input
|
57
|
-
class InplaceTransition < Transition
|
58
|
-
def initialize(trans:
|
55
|
+
|
56
|
+
# Transition that does not move/change the input
|
57
|
+
class InplaceTransition < Transition
|
58
|
+
def initialize(trans:)
|
59
59
|
@trans = trans
|
60
60
|
end
|
61
|
+
|
61
62
|
def do_match(str)
|
62
63
|
@str = str
|
63
64
|
end
|
65
|
+
|
64
66
|
def path_remain
|
65
|
-
@str
|
67
|
+
@str
|
66
68
|
end
|
69
|
+
|
67
70
|
def path_done
|
68
71
|
EMPTYSTR
|
69
72
|
end
|
73
|
+
|
70
74
|
def do_transition(ctx)
|
71
75
|
ctx.method(@trans).call(@str)
|
72
76
|
end
|
73
77
|
end
|
74
|
-
|
78
|
+
|
75
79
|
TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
|
76
80
|
TransitionExecuteFunc = InplaceTransition.new(trans: 'transition_execute_func')
|
77
81
|
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
data/lib/odata/url_parameters.rb
CHANGED
@@ -68,20 +68,20 @@ module Safrano
|
|
68
68
|
return Contract::OK unless @params['$top']
|
69
69
|
|
70
70
|
itop = number_or_nil(@params['$top'])
|
71
|
-
|
71
|
+
itop.nil? || itop.zero? ? BadRequestError : Contract::OK
|
72
72
|
end
|
73
73
|
|
74
74
|
def check_skip
|
75
75
|
return Contract::OK unless @params['$skip']
|
76
76
|
|
77
77
|
iskip = number_or_nil(@params['$skip'])
|
78
|
-
|
78
|
+
iskip.nil? || iskip.negative? ? BadRequestError : Contract::OK
|
79
79
|
end
|
80
80
|
|
81
81
|
def check_inlinecount
|
82
82
|
return Contract::OK unless (icp = @params['$inlinecount'])
|
83
83
|
|
84
|
-
(
|
84
|
+
(icp == 'allpages') || (icp == 'none') ? Contract::OK : BadRequestInlineCountParamError
|
85
85
|
end
|
86
86
|
|
87
87
|
def check_filter
|
data/lib/odata/walker.rb
CHANGED
@@ -29,24 +29,24 @@ module Safrano
|
|
29
29
|
|
30
30
|
# are $links requested ?
|
31
31
|
attr_reader :do_links
|
32
|
-
|
33
|
-
attr_reader :request
|
32
|
+
|
33
|
+
attr_reader :request
|
34
34
|
|
35
35
|
NIL_SERVICE_FATAL = 'Walker is called with a nil service'
|
36
36
|
EMPTYSTR = ''
|
37
37
|
SLASH = '/'
|
38
38
|
|
39
|
-
def initialize(service, path, request, content_id_refs = nil
|
39
|
+
def initialize(service, path, request, content_id_refs = nil)
|
40
40
|
raise NIL_SERVICE_FATAL unless service
|
41
41
|
|
42
42
|
path = URI.decode_www_form_component(path)
|
43
43
|
@context = service
|
44
44
|
@content_id_refs = content_id_refs
|
45
|
-
|
45
|
+
|
46
46
|
# needed because for function import we need access to the url parameters (req.params)
|
47
47
|
# who contains the functions params
|
48
48
|
@request = request
|
49
|
-
|
49
|
+
|
50
50
|
@contexts = [@context]
|
51
51
|
|
52
52
|
@path_start = @path_remain = if service
|
@@ -115,16 +115,16 @@ module Safrano
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
-
# execute function import with request parameters
|
118
|
+
# execute function import with request parameters
|
119
119
|
# input: @context containt the function to exectute,
|
120
120
|
# @request.params should normally contain the params
|
121
|
-
# result: validate the params for the given function, execute the function and
|
121
|
+
# result: validate the params for the given function, execute the function and
|
122
122
|
# return it's result back into @context,
|
123
123
|
# and finaly set status :end (or error if anyting went wrong )
|
124
124
|
def do_run_with_execute_func
|
125
125
|
@context, @status, @error = @context.do_execute_func(@request)
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
# little hacks... depending on returned state, set some attributes
|
129
129
|
def state_mappings
|
130
130
|
case @status
|
@@ -183,7 +183,7 @@ module Safrano
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def finalize
|
186
|
-
|
186
|
+
@status == :end ? Contract.valid(@end_context) : @error
|
187
187
|
end
|
188
188
|
end
|
189
189
|
end
|
data/lib/safrano/multipart.rb
CHANGED
@@ -411,9 +411,7 @@ module MIME
|
|
411
411
|
|
412
412
|
def unparse(bodyonly = false)
|
413
413
|
b = +String.new
|
414
|
-
unless bodyonly
|
415
|
-
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
416
|
-
end
|
414
|
+
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}" unless bodyonly
|
417
415
|
|
418
416
|
b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
|
419
417
|
b << @content.map(&:unparse).join(crbdcr)
|
data/lib/safrano/rack_app.rb
CHANGED
@@ -10,7 +10,7 @@ module Safrano
|
|
10
10
|
module MethodHandlers
|
11
11
|
def odata_options
|
12
12
|
@walker.finalize.tap_error { |err| return err.odata_get(@request) }
|
13
|
-
.if_valid do |
|
13
|
+
.if_valid do |_context|
|
14
14
|
# cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
|
15
15
|
headers.delete('Content-Type')
|
16
16
|
@response.headers.delete('Content-Type')
|
@@ -119,7 +119,7 @@ module Safrano
|
|
119
119
|
@response = Safrano::Response.new
|
120
120
|
|
121
121
|
before.tap_error { |err| dispatch_error(err) }
|
122
|
-
.tap_valid { |
|
122
|
+
.tap_valid { |_res| dispatch }
|
123
123
|
|
124
124
|
# handle remaining Sequel errors that we couldnt prevent with our
|
125
125
|
# own pre-checks
|
@@ -161,15 +161,3 @@ module Safrano
|
|
161
161
|
end
|
162
162
|
end
|
163
163
|
end
|
164
|
-
|
165
|
-
# deprecated
|
166
|
-
# REMOVE 0.6
|
167
|
-
module OData
|
168
|
-
class ServerApp < Safrano::ServerApp
|
169
|
-
def self.publish_service(&block)
|
170
|
-
::Safrano::Deprecation.deprecate('OData::ServerApp',
|
171
|
-
'Use Safrano::ServerApp instead')
|
172
|
-
super
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
data/lib/safrano/rack_builder.rb
CHANGED
@@ -17,18 +17,3 @@ module Rack
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
21
|
-
# deprecated
|
22
|
-
# REMOVE 0.6
|
23
|
-
module Rack
|
24
|
-
module OData
|
25
|
-
class Builder < ::Rack::Safrano::Builder
|
26
|
-
def initialize(default_app = nil, &block)
|
27
|
-
::Safrano::Deprecation.deprecate('Rack::OData::Builder',
|
28
|
-
'Use Rack::Safrano::Builder instead')
|
29
|
-
|
30
|
-
super
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/safrano/request.rb
CHANGED
@@ -144,11 +144,11 @@ module Safrano
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
-
def with_parsed_data
|
147
|
+
def with_parsed_data(type)
|
148
148
|
if content_type == APPJSON
|
149
149
|
# Parse json payload
|
150
150
|
begin
|
151
|
-
data = JSON.
|
151
|
+
data = Safrano::OData::JSON.parse_one(body.read, type)
|
152
152
|
rescue JSON::ParserError => e
|
153
153
|
ON_CGST_ERROR.call(self)
|
154
154
|
return [400, EMPTY_HASH, ['JSON Parser Error while parsing payload : ',
|
@@ -239,7 +239,7 @@ module Safrano
|
|
239
239
|
get_minversion.if_valid do |minv|
|
240
240
|
return MAX_LT_MIN_DTSV_ERROR if minv > maxv
|
241
241
|
|
242
|
-
get_version.if_valid do |
|
242
|
+
get_version.if_valid do |_v|
|
243
243
|
@service = nil
|
244
244
|
@service = case maxv
|
245
245
|
when '1'
|
data/lib/safrano/response.rb
CHANGED
@@ -18,8 +18,8 @@ module Safrano
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def body=(value)
|
21
|
-
value = value.body while ::Rack::Response
|
22
|
-
@body = String
|
21
|
+
value = value.body while value.is_a?(::Rack::Response)
|
22
|
+
@body = value.is_a?(String) ? [value.to_str] : value
|
23
23
|
end
|
24
24
|
|
25
25
|
def each
|
@@ -51,7 +51,7 @@ module Safrano
|
|
51
51
|
private
|
52
52
|
|
53
53
|
def calculate_content_length?
|
54
|
-
headers['Content-Type'] && !headers['Content-Length'] && (Array
|
54
|
+
headers['Content-Type'] && !headers['Content-Length'] && body.is_a?(Array)
|
55
55
|
end
|
56
56
|
|
57
57
|
def calculated_content_length
|