safrano 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|