safrano 0.8.0 → 0.8.2
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 +1 -0
- data/lib/core_ext/DateTime/format.rb +1 -0
- data/lib/core_ext/Hash/transform.rb +2 -2
- data/lib/core_ext/MatchData/matchlen.rb +14 -0
- data/lib/core_ext/Time/format.rb +1 -1
- data/lib/core_ext/matchdata.rb +3 -0
- data/lib/odata/attribute.rb +4 -5
- data/lib/odata/batch.rb +3 -1
- data/lib/odata/collection.rb +3 -0
- data/lib/odata/collection_media.rb +5 -4
- data/lib/odata/complex_type.rb +4 -3
- data/lib/odata/entity.rb +7 -4
- data/lib/odata/error.rb +21 -5
- data/lib/odata/filter/base.rb +1 -0
- data/lib/odata/filter/error.rb +3 -0
- data/lib/odata/filter/sequel.rb +6 -10
- data/lib/odata/filter/sequel_datetime_adapter.rb +1 -0
- data/lib/odata/filter/sequel_function_adapter.rb +1 -0
- data/lib/odata/filter/tree.rb +28 -26
- data/lib/odata/function_import.rb +7 -0
- data/lib/odata/model_ext.rb +20 -17
- data/lib/odata/navigation_attribute.rb +6 -0
- data/lib/odata/request/json.rb +1 -0
- data/lib/odata/transition.rb +134 -27
- data/lib/odata/walker.rb +5 -25
- data/lib/safrano/rack_app.rb +16 -3
- data/lib/safrano/rack_builder.rb +37 -37
- data/lib/safrano/request.rb +5 -4
- data/lib/safrano/service.rb +61 -7
- data/lib/safrano/type_mapping.rb +6 -3
- data/lib/safrano/version.rb +1 -1
- data/lib/sequel/plugins/join_by_paths.rb +2 -2
- metadata +4 -2
@@ -78,6 +78,10 @@ module Safrano
|
|
78
78
|
# Represents a named but nil-valued navigation-attribute of an Entity
|
79
79
|
# (usually resulting from a NULL FK db value)
|
80
80
|
class NilNavigationAttribute
|
81
|
+
def initialize
|
82
|
+
@allowed_transitions = ALLOWED_TRANSITIONS
|
83
|
+
end
|
84
|
+
|
81
85
|
include Safrano::NavigationInfo
|
82
86
|
def odata_get(req)
|
83
87
|
if req.walker.media_value
|
@@ -131,6 +135,8 @@ module Safrano
|
|
131
135
|
def allowed_transitions
|
132
136
|
ALLOWED_TRANSITIONS
|
133
137
|
end
|
138
|
+
|
139
|
+
include Safrano::Transitions::GetNextTrans::BySimpleDetect
|
134
140
|
end
|
135
141
|
include Transitions
|
136
142
|
end
|
data/lib/odata/request/json.rb
CHANGED
data/lib/odata/transition.rb
CHANGED
@@ -4,16 +4,78 @@ require_relative 'error'
|
|
4
4
|
|
5
5
|
# our main namespace
|
6
6
|
module Safrano
|
7
|
+
module Transitions
|
8
|
+
module GetNextTrans
|
9
|
+
module BySimpleDetect
|
10
|
+
def get_next_transresult(path_remain)
|
11
|
+
# current url-parsing context
|
12
|
+
# has no ambiguous next match and we dont need to find longest match
|
13
|
+
# but it's sufficient to find the one matching (or nil)
|
14
|
+
tres_next = nil
|
15
|
+
@allowed_transitions.detect { |t| tres_next = t.result(path_remain) }
|
16
|
+
tres_next
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ForJustTransitionEnd
|
21
|
+
def get_next_transresult(path_remain)
|
22
|
+
Safrano::TransitionEnd.result(path_remain)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Transitions::GetNextTrans::ByLongestMatch
|
27
|
+
module ByLongestMatch
|
28
|
+
def get_next_transresult(path_remain)
|
29
|
+
# current url-parsing context
|
30
|
+
# has ambiguous next match and we need to find longest match
|
31
|
+
# example: current context is "the top level service" and we have
|
32
|
+
# entity types Race and RaceType
|
33
|
+
|
34
|
+
match_len = -1
|
35
|
+
tres_next = nil
|
36
|
+
|
37
|
+
@allowed_transitions.each { |t|
|
38
|
+
if (res = t.longer_match(path_remain, match_len))
|
39
|
+
tres_next = res
|
40
|
+
match_len = tres_next.match_length
|
41
|
+
end
|
42
|
+
}
|
43
|
+
tres_next
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# same as ByLongestMatch but use the getter method allowed_transitions instead of
|
48
|
+
# directly @allowed_transitions
|
49
|
+
module ByLongestMatchDyn
|
50
|
+
def get_next_transresult(path_remain)
|
51
|
+
# current url-parsing context
|
52
|
+
# has ambiguous next match and we need to find longest match
|
53
|
+
# example: current context is "the top level service" and we have
|
54
|
+
# entity types Race and RaceType
|
55
|
+
|
56
|
+
match_len = -1
|
57
|
+
tres_next = nil
|
58
|
+
|
59
|
+
allowed_transitions.each { |t|
|
60
|
+
if (res = t.longer_match(path_remain, match_len))
|
61
|
+
tres_next = res
|
62
|
+
match_len = tres_next.match_length
|
63
|
+
end
|
64
|
+
}
|
65
|
+
tres_next
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
7
71
|
# represents a state transition when navigating/parsing the url path
|
8
72
|
# from left to right
|
9
73
|
class Transition
|
10
|
-
attr_accessor :trans
|
11
|
-
attr_accessor :match_result
|
12
|
-
attr_accessor :
|
13
|
-
|
14
|
-
|
15
|
-
EMPTYSTR = ''
|
16
|
-
SLASH = '/'
|
74
|
+
# attr_accessor :trans
|
75
|
+
# attr_accessor :match_result
|
76
|
+
# attr_accessor :trans_result
|
77
|
+
# attr_accessor :rgx
|
78
|
+
# attr_reader :remain_idx
|
17
79
|
|
18
80
|
RESULT_BAD_REQ_ERR = [nil, :error, ::Safrano::BadRequestError].freeze
|
19
81
|
RESULT_NOT_FOUND_ERR = [nil, :error, ::Safrano::ErrorNotFound].freeze
|
@@ -25,31 +87,58 @@ module Safrano
|
|
25
87
|
Regexp.new(arg)
|
26
88
|
else
|
27
89
|
arg
|
28
|
-
end
|
90
|
+
end.freeze
|
91
|
+
@trans = trans.freeze
|
92
|
+
@remain_idx = remain_idx.freeze
|
93
|
+
end
|
94
|
+
|
95
|
+
def result(str)
|
96
|
+
return unless (mres = @rgx.match(str))
|
97
|
+
|
98
|
+
TransitionResult.new(trans: @trans, match_result: mres, remain_idx: @remain_idx)
|
99
|
+
end
|
100
|
+
|
101
|
+
# return match-result for str, if longer as min_match_length
|
102
|
+
def longer_match(str, min_match_length)
|
103
|
+
return unless (mres = @rgx.match(str))
|
104
|
+
return unless (mres.match_length(1) > min_match_length)
|
105
|
+
|
106
|
+
TransitionResult.new(trans: @trans, match_result: mres, remain_idx: @remain_idx)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class TransitionResult
|
111
|
+
attr_reader :match
|
112
|
+
|
113
|
+
EMPTYSTR = ''
|
114
|
+
SLASH = '/'
|
115
|
+
|
116
|
+
def initialize(trans:, match_result:, remain_idx:)
|
29
117
|
@trans = trans
|
118
|
+
@match = match_result
|
30
119
|
@remain_idx = remain_idx
|
31
120
|
end
|
32
121
|
|
33
|
-
def
|
34
|
-
@
|
122
|
+
def match_length
|
123
|
+
@match.match_length(1)
|
35
124
|
end
|
36
125
|
|
37
126
|
# remain_idx is the index of the last match-data. ususally its 2
|
38
127
|
# but can be overidden
|
39
128
|
def path_remain
|
40
|
-
@
|
129
|
+
@match[@remain_idx] if @match && @match[@remain_idx]
|
41
130
|
end
|
42
131
|
|
43
132
|
def path_done
|
44
|
-
if @
|
45
|
-
@
|
133
|
+
if @match
|
134
|
+
@match[1] || EMPTYSTR
|
46
135
|
else
|
47
136
|
EMPTYSTR
|
48
137
|
end
|
49
138
|
end
|
50
139
|
|
51
140
|
def do_transition(ctx)
|
52
|
-
ctx.
|
141
|
+
ctx.__send__(@trans, @match)
|
53
142
|
end
|
54
143
|
end
|
55
144
|
|
@@ -59,8 +148,30 @@ module Safrano
|
|
59
148
|
@trans = trans
|
60
149
|
end
|
61
150
|
|
62
|
-
def
|
151
|
+
def result(str)
|
152
|
+
@str = str
|
153
|
+
InplaceTransitionResult.new(trans: @trans, match_result: @str)
|
154
|
+
end
|
155
|
+
|
156
|
+
# return match-result for str, if longer as min_match_length
|
157
|
+
def longer_match(str, min_match_length)
|
63
158
|
@str = str
|
159
|
+
return unless (@str.size > min_match_length)
|
160
|
+
|
161
|
+
InplaceTransitionResult.new(trans: @trans, match_result: @str)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Transition that does not move/change the input
|
166
|
+
class InplaceTransitionResult < TransitionResult
|
167
|
+
def initialize(trans:, match_result:)
|
168
|
+
@trans = trans
|
169
|
+
@match = match_result
|
170
|
+
@str = match_result
|
171
|
+
end
|
172
|
+
|
173
|
+
def match_length
|
174
|
+
@str.size
|
64
175
|
end
|
65
176
|
|
66
177
|
def path_remain
|
@@ -70,26 +181,22 @@ module Safrano
|
|
70
181
|
def path_done
|
71
182
|
EMPTYSTR
|
72
183
|
end
|
73
|
-
|
74
|
-
def do_transition(ctx)
|
75
|
-
ctx.method(@trans).call(@str)
|
76
|
-
end
|
77
184
|
end
|
78
185
|
|
79
|
-
TransitionEnd = Transition.new('\A(\/?)\z', trans:
|
80
|
-
TransitionExecuteFunc = InplaceTransition.new(trans:
|
186
|
+
TransitionEnd = Transition.new('\A(\/?)\z', trans: :transition_end)
|
187
|
+
TransitionExecuteFunc = InplaceTransition.new(trans: :transition_execute_func)
|
81
188
|
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
82
|
-
trans:
|
189
|
+
trans: :transition_metadata)
|
83
190
|
TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
|
84
|
-
trans:
|
191
|
+
trans: :transition_batch)
|
85
192
|
TransitionContentId = Transition.new('\A(\/\$(\d+))(.*)',
|
86
|
-
trans:
|
193
|
+
trans: :transition_content_id,
|
87
194
|
remain_idx: 3)
|
88
195
|
TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
|
89
|
-
trans:
|
196
|
+
trans: :transition_count)
|
90
197
|
TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
|
91
|
-
trans:
|
198
|
+
trans: :transition_value)
|
92
199
|
TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
|
93
|
-
trans:
|
200
|
+
trans: :transition_links)
|
94
201
|
attr_accessor :allowed_transitions
|
95
202
|
end
|
data/lib/odata/walker.rb
CHANGED
@@ -65,30 +65,10 @@ module Safrano
|
|
65
65
|
if (prefix == EMPTYSTR) || (prefix == SLASH)
|
66
66
|
path
|
67
67
|
else
|
68
|
-
# path.sub!(/\A#{prefix}/, '')
|
69
|
-
# TODO: check
|
70
68
|
path.sub(/\A#{prefix}/, EMPTYSTR)
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
74
|
-
def get_next_transition
|
75
|
-
# handle multiple valid transitions
|
76
|
-
# like when we have attributes that are substring of each other
|
77
|
-
# --> instead of using detect (ie take first transition)
|
78
|
-
# we need to use select and then find the longest match
|
79
|
-
|
80
|
-
valid_tr = @context.allowed_transitions.map(&:dup).select do |t|
|
81
|
-
t.do_match(@path_remain)
|
82
|
-
end
|
83
|
-
|
84
|
-
# HACK: (wanted: a better one) to make attributes that are substrings of each other
|
85
|
-
# work well
|
86
|
-
return unless valid_tr
|
87
|
-
return (@tr_next = nil) if valid_tr.empty?
|
88
|
-
|
89
|
-
@tr_next = valid_tr.size == 1 ? valid_tr.first : valid_tr.max_by { |t| t.match_result[1].size }
|
90
|
-
end
|
91
|
-
|
92
72
|
# perform a content-id ($batch changeset ref) transition
|
93
73
|
def do_run_with_content_id
|
94
74
|
if @content_id_refs.is_a? Hash
|
@@ -137,7 +117,7 @@ module Safrano
|
|
137
117
|
end
|
138
118
|
|
139
119
|
def do_next_transition
|
140
|
-
@context, @status, @error = @
|
120
|
+
@context, @status, @error = @tres_next.do_transition(@context)
|
141
121
|
# little hack's
|
142
122
|
case @status
|
143
123
|
# we dont have the content-id references data on service level
|
@@ -151,8 +131,8 @@ module Safrano
|
|
151
131
|
end
|
152
132
|
|
153
133
|
@contexts << @context
|
154
|
-
@path_remain = @
|
155
|
-
@path_done << @
|
134
|
+
@path_remain = @tres_next.path_remain
|
135
|
+
@path_done << @tres_next.path_done
|
156
136
|
|
157
137
|
# little hack's
|
158
138
|
state_mappings
|
@@ -160,8 +140,8 @@ module Safrano
|
|
160
140
|
|
161
141
|
def eo
|
162
142
|
while @context
|
163
|
-
|
164
|
-
if @
|
143
|
+
@tres_next = @context.get_next_transresult(@path_remain)
|
144
|
+
if @tres_next
|
165
145
|
do_next_transition
|
166
146
|
else
|
167
147
|
@context = nil
|
data/lib/safrano/rack_app.rb
CHANGED
@@ -22,6 +22,13 @@ module Safrano
|
|
22
22
|
Safrano::Request.new(env, @service_base).process
|
23
23
|
end
|
24
24
|
|
25
|
+
# needed for testing only ? try to remove this
|
26
|
+
def self.copy(other)
|
27
|
+
copy = Class.new(Safrano::ServerApp) # <---- !!!
|
28
|
+
copy.set_servicebase(other.get_service_base.dup)
|
29
|
+
copy
|
30
|
+
end
|
31
|
+
|
25
32
|
# needed for testing only ? try to remove this
|
26
33
|
def self.enable_batch
|
27
34
|
@service_base.enable_batch
|
@@ -32,16 +39,22 @@ module Safrano
|
|
32
39
|
@service_base.path_prefix path_pr
|
33
40
|
end
|
34
41
|
|
42
|
+
# needed for testing only ? try to remove this
|
43
|
+
|
44
|
+
def self.response_format_options(*args)
|
45
|
+
@service_base.response_format_options(*args)
|
46
|
+
end
|
47
|
+
|
35
48
|
# needed for testing only ? try to remove this
|
36
49
|
def self.get_service_base
|
37
50
|
@service_base
|
38
51
|
end
|
39
|
-
|
40
|
-
# needed for safrano-rack_builder
|
52
|
+
|
53
|
+
# needed for safrano-rack_builder
|
41
54
|
def get_path_prefix
|
42
55
|
self.class.get_service_base.xpath_prefix
|
43
56
|
end
|
44
|
-
|
57
|
+
|
45
58
|
def self.set_servicebase(sbase)
|
46
59
|
@service_base = sbase
|
47
60
|
@service_base.enable_v1_service
|
data/lib/safrano/rack_builder.rb
CHANGED
@@ -7,9 +7,11 @@ module Rack
|
|
7
7
|
module Safrano
|
8
8
|
# just a Wrapper to ensure (force?) that mandatory middlewares are acutally
|
9
9
|
# used
|
10
|
-
LOCALHOST_ANY_PORT_RGX = /\A(?:https?:\/\/)?localhost(?::\d+)?\z
|
10
|
+
LOCALHOST_ANY_PORT_RGX = /\A(?:https?:\/\/)?localhost(?::\d+)?\z/.freeze
|
11
|
+
CORS_RO_METHODS = %i[get head options].freeze
|
12
|
+
CORS_BATCH_METHODS = %i[post head options].freeze
|
13
|
+
CORS_RW_METHODS = %i[get post put patch delete head options].freeze
|
11
14
|
class Builder < ::Rack::Builder
|
12
|
-
|
13
15
|
def initialize(default_app = nil, &block)
|
14
16
|
super(default_app) {}
|
15
17
|
@middlewares = []
|
@@ -17,63 +19,61 @@ module Rack
|
|
17
19
|
instance_eval(&block) if block_given?
|
18
20
|
prepend_rackcors_ifneeded
|
19
21
|
end
|
20
|
-
|
22
|
+
|
21
23
|
def use(middleware, *args, &block)
|
22
24
|
@middlewares << middleware
|
23
25
|
super(middleware, *args, &block)
|
24
26
|
end
|
25
|
-
|
27
|
+
|
26
28
|
def prepend_rackcors_ifneeded
|
27
29
|
return if stack_has_rackcors
|
28
|
-
|
30
|
+
|
29
31
|
# get the safrano app path prefix
|
30
32
|
# normally @run is a Safrano Server app
|
31
33
|
return unless @run.is_a? ::Safrano::ServerApp
|
32
|
-
|
33
|
-
|
34
|
+
|
34
35
|
service_path_prefix = @run.get_path_prefix.dup
|
35
|
-
|
36
|
-
# due to a bug in rack-cors
|
36
|
+
|
37
|
+
# due to a bug in rack-cors
|
37
38
|
# we cant use the batch ressource path as
|
38
39
|
# a string but need to pass it as a regexp
|
39
|
-
# ( bug fixed in rack-cors git / new release? but still need to workaround it for
|
40
|
-
# current/old releases ),
|
41
|
-
batch_path_regexp = if service_path_prefix.empty?
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
40
|
+
# ( bug fixed in rack-cors git / new release? but still need to workaround it for
|
41
|
+
# current/old releases ),
|
42
|
+
batch_path_regexp = if service_path_prefix.empty?
|
43
|
+
# Ressource /$batch
|
44
|
+
/\A\/\$batch\z/
|
45
|
+
else
|
46
|
+
# Ressource like /foo/bar/baz/$batch
|
47
|
+
service_path_prefix.sub!(::Safrano::TRAILING_SLASH_RGX, '')
|
48
|
+
service_path_prefix.sub!(::Safrano::LEADING_SLASH_RGX, '')
|
49
|
+
# now is foo/bar/baz
|
50
|
+
path_prefix_rgx = Regexp.escape("/#{service_path_prefix}/$batch")
|
51
|
+
# now escaped path regexp /foo/bar/baz/$batch
|
52
|
+
# finaly just add start / end anchors
|
53
|
+
/\A#{path_prefix_rgx}\z/
|
54
|
+
end
|
54
55
|
# this will append rack-cors mw
|
55
|
-
# per default allow GET * from everywhere
|
56
|
-
# allow POST $batch from localhost only
|
56
|
+
# per default allow GET * from everywhere
|
57
|
+
# allow POST $batch from localhost only
|
57
58
|
use ::Rack::Cors do
|
58
59
|
allow do
|
59
60
|
origins LOCALHOST_ANY_PORT_RGX
|
60
|
-
resource
|
61
|
-
resource '*', headers: :any, methods:
|
62
|
-
end
|
61
|
+
resource batch_path_regexp, headers: :any, methods: CORS_BATCH_METHODS
|
62
|
+
resource '*', headers: :any, methods: CORS_RW_METHODS
|
63
|
+
end
|
63
64
|
allow do
|
64
65
|
origins '*'
|
65
|
-
resource '*', headers: :any, methods:
|
66
|
-
end
|
67
|
-
|
66
|
+
resource '*', headers: :any, methods: CORS_RO_METHODS
|
67
|
+
end
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
# we need it in first place... move last element to beginin of mw stack
|
71
|
-
rackcors = @use.delete_at(-1)
|
72
|
-
@use.insert(0, rackcors)
|
71
|
+
rackcors = @use.delete_at(-1)
|
72
|
+
@use.insert(0, rackcors)
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
def stack_has_rackcors
|
76
|
-
@middlewares.find{|mw| mw == Rack::Cors }
|
76
|
+
@middlewares.find { |mw| mw == Rack::Cors }
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
data/lib/safrano/request.rb
CHANGED
@@ -11,12 +11,12 @@ module Safrano
|
|
11
11
|
headers.delete('Content-Type')
|
12
12
|
@response.headers.delete('Content-Type')
|
13
13
|
@response.headers['Content-Type'] = ''
|
14
|
-
|
14
|
+
|
15
15
|
# we only let rack-cors handle Cors OPTIONS .
|
16
16
|
# otherwise dont do it...
|
17
|
-
# see https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
|
17
|
+
# see https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
|
18
18
|
# 501 not implemented
|
19
|
-
[501, EMPTY_HASH, '']
|
19
|
+
[501, EMPTY_HASH, '']
|
20
20
|
end
|
21
21
|
|
22
22
|
def odata_delete
|
@@ -41,7 +41,7 @@ module Safrano
|
|
41
41
|
|
42
42
|
def odata_post
|
43
43
|
@walker.finalize.tap_error { |err| return err.odata_get(self) }
|
44
|
-
|
44
|
+
.if_valid { |context| context.odata_post(self) }
|
45
45
|
end
|
46
46
|
|
47
47
|
def odata_head
|
@@ -99,6 +99,7 @@ module Safrano
|
|
99
99
|
@type
|
100
100
|
end
|
101
101
|
end
|
102
|
+
|
102
103
|
## original coding:
|
103
104
|
# # The set of media-types. Requests that do not indicate
|
104
105
|
# # one of the media types presents in this list will not be eligible
|
data/lib/safrano/service.rb
CHANGED
@@ -12,8 +12,12 @@ require 'set'
|
|
12
12
|
require 'odata/collection'
|
13
13
|
|
14
14
|
module Safrano
|
15
|
-
#
|
15
|
+
# these modules have all methods related to expand/defered output preparation
|
16
16
|
# and will be included in Service class
|
17
|
+
|
18
|
+
METADATA_K = '__metadata'
|
19
|
+
EMPTYH = {}.freeze
|
20
|
+
|
17
21
|
module ExpandHandler
|
18
22
|
PATH_SPLITTER = %r{\A(\w+)/?(.*)\z}.freeze
|
19
23
|
DEFERRED = '__deferred'
|
@@ -40,14 +44,17 @@ module Safrano
|
|
40
44
|
{ uri: entity.uri }
|
41
45
|
end
|
42
46
|
|
43
|
-
EMPTYH = {}.freeze
|
44
|
-
METADATA_K = '__metadata'
|
45
47
|
def get_entity_odata_h(entity:, template:)
|
46
|
-
|
47
|
-
|
48
|
+
hres = {}
|
49
|
+
# finalise the template according to options
|
50
|
+
# (eg. skip metadata and or deferred...)
|
51
|
+
@final_template_func.call(template)
|
48
52
|
|
49
53
|
template.each do |elmt, arg|
|
50
54
|
case elmt
|
55
|
+
when :meta
|
56
|
+
hres[METADATA_K] = entity.metadata_h
|
57
|
+
|
51
58
|
when :all_values
|
52
59
|
hres.merge! entity.casted_values
|
53
60
|
|
@@ -110,6 +117,7 @@ module Safrano
|
|
110
117
|
TRAILING_SLASH_RGX = %r{/\z}.freeze
|
111
118
|
LEADING_SLASH_RGX = %r{\A/}.freeze
|
112
119
|
include XMLNS
|
120
|
+
GENERIC_415_RESP = [415, {}, ['']].freeze
|
113
121
|
# Base class for service. Subclass will be for V1, V2 etc...
|
114
122
|
class ServiceBase
|
115
123
|
include Safrano
|
@@ -142,6 +150,8 @@ module Safrano
|
|
142
150
|
attr_accessor :function_imports
|
143
151
|
attr_accessor :function_import_keys
|
144
152
|
attr_accessor :type_mappings
|
153
|
+
attr_accessor :final_template_func
|
154
|
+
attr_accessor :response_format_options
|
145
155
|
|
146
156
|
# Instance attributes for specialized Version specific Instances
|
147
157
|
attr_accessor :v1
|
@@ -150,6 +160,12 @@ module Safrano
|
|
150
160
|
# TODO: more elegant design
|
151
161
|
attr_reader :data_service_version
|
152
162
|
|
163
|
+
# for response format options
|
164
|
+
FINAL_TEMPLATE_FUNC_DEFAULT = ->(_template) {}
|
165
|
+
FINAL_TEMPLATE_FUNC_SKIP_META = ->(template) { template.delete(:meta) }
|
166
|
+
FINAL_TEMPLATE_FUNC_SKIP_DEFERR = ->(template) { template.delete(:deferr) }
|
167
|
+
FINAL_TEMPLATE_FUNC_SKIP_META_DEFERR = ->(template) { template.delete(:meta); template.delete(:deferr) }
|
168
|
+
|
153
169
|
def initialize(&block)
|
154
170
|
# Warning: if you add attributes here, you shall need add them
|
155
171
|
# in copy_attribs_to as well
|
@@ -163,6 +179,8 @@ module Safrano
|
|
163
179
|
@function_import_keys = []
|
164
180
|
@cmap = {}
|
165
181
|
@type_mappings = {}
|
182
|
+
@response_format_options = []
|
183
|
+
@final_template_func = FINAL_TEMPLATE_FUNC_DEFAULT
|
166
184
|
# enabled per default starting from 0.6
|
167
185
|
@bugfix_create_response = true
|
168
186
|
instance_eval(&block) if block_given?
|
@@ -213,12 +231,42 @@ module Safrano
|
|
213
231
|
(@v2.xserver_url = @xserver_url) if @v2
|
214
232
|
end
|
215
233
|
|
234
|
+
VALID_RESP_FORMAT_OPTS = [:skip_deferred, :skip_metadata]
|
235
|
+
def valid_resp_format_options(*args)
|
236
|
+
args.map!(&:to_sym)
|
237
|
+
args.each { |arg|
|
238
|
+
raise API::InvalidRespFormatOption.new(arg) unless VALID_RESP_FORMAT_OPTS.include?(arg)
|
239
|
+
}
|
240
|
+
yield(args)
|
241
|
+
end
|
242
|
+
|
243
|
+
def response_format_options(*args)
|
244
|
+
valid_resp_format_options(*args) do |vargs|
|
245
|
+
@response_format_options = vargs.dup
|
246
|
+
process_response_format_options
|
247
|
+
@v1.response_format_options(*vargs) if @v1
|
248
|
+
@v2.response_format_options(*vargs) if @v2
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
216
252
|
# keep the bug active for now, but allow to de-activate the fix
|
217
253
|
def bugfix_create_response(bool)
|
218
254
|
@bugfix_create_response = bool
|
219
255
|
end
|
220
256
|
|
221
257
|
# end public API
|
258
|
+
def process_response_format_options
|
259
|
+
@final_template_func = if (@response_format_options.include?(:skip_metadata) &&
|
260
|
+
@response_format_options.include?(:skip_deferred))
|
261
|
+
FINAL_TEMPLATE_FUNC_SKIP_META_DEFERR
|
262
|
+
elsif @response_format_options.include?(:skip_metadata)
|
263
|
+
FINAL_TEMPLATE_FUNC_SKIP_META
|
264
|
+
elsif @response_format_options.include?(:skip_deferred)
|
265
|
+
FINAL_TEMPLATE_FUNC_SKIP_DEFERR
|
266
|
+
else
|
267
|
+
FINAL_TEMPLATE_FUNC_DEFAULT
|
268
|
+
end
|
269
|
+
end
|
222
270
|
|
223
271
|
def set_uribase
|
224
272
|
@uribase = if @xpath_prefix.empty?
|
@@ -249,6 +297,8 @@ module Safrano
|
|
249
297
|
other.function_imports = @function_imports
|
250
298
|
other.function_import_keys = @function_import_keys
|
251
299
|
other.type_mappings = @type_mappings
|
300
|
+
other.response_format_options = @response_format_options
|
301
|
+
other.final_template_func = @final_template_func
|
252
302
|
other.bugfix_create_response(@bugfix_create_response)
|
253
303
|
other
|
254
304
|
end
|
@@ -620,6 +670,8 @@ module Safrano
|
|
620
670
|
Safrano::TransitionContentId
|
621
671
|
].freeze
|
622
672
|
|
673
|
+
include Safrano::Transitions::GetNextTrans::ByLongestMatch
|
674
|
+
|
623
675
|
def build_allowed_transitions
|
624
676
|
@allowed_transitions = if @function_imports.empty?
|
625
677
|
(ALLOWED_TRANSITIONS_FIXED + [
|
@@ -679,7 +731,7 @@ module Safrano
|
|
679
731
|
[200, CT_APPXML, [service_xml(req)]]
|
680
732
|
else
|
681
733
|
# this is returned by http://services.odata.org/V2/OData/Safrano.svc
|
682
|
-
|
734
|
+
GENERIC_415_RESP
|
683
735
|
end
|
684
736
|
end
|
685
737
|
end
|
@@ -748,11 +800,13 @@ module Safrano
|
|
748
800
|
Safrano::Transition::RESULT_END
|
749
801
|
end
|
750
802
|
|
803
|
+
include Safrano::Transitions::GetNextTrans::ForJustTransitionEnd
|
804
|
+
|
751
805
|
def odata_get(req)
|
752
806
|
if req.accept?(APPXML)
|
753
807
|
[200, CT_APPXML, [@service.metadata_xml(req)]]
|
754
808
|
else
|
755
|
-
|
809
|
+
GENERIC_415_RESP
|
756
810
|
end
|
757
811
|
end
|
758
812
|
end
|
data/lib/safrano/type_mapping.rb
CHANGED
@@ -12,6 +12,7 @@ module Safrano
|
|
12
12
|
attr_reader :castfunc
|
13
13
|
attr_reader :edm_type
|
14
14
|
end
|
15
|
+
|
15
16
|
# Model attribute (column) specific mapping
|
16
17
|
class AttributeTypeMapping < TypeMapping
|
17
18
|
attr_reader :attr_name
|
@@ -20,6 +21,7 @@ module Safrano
|
|
20
21
|
@edm_type = builder.xedm_type
|
21
22
|
@castfunc = builder.castfunc
|
22
23
|
end
|
24
|
+
|
23
25
|
# wrapper to handle API
|
24
26
|
class Builder
|
25
27
|
attr_reader :xedm_type
|
@@ -82,9 +84,8 @@ module Safrano
|
|
82
84
|
end
|
83
85
|
|
84
86
|
def match(curtyp)
|
85
|
-
if @bui2 && (m = @bui2.match(curtyp))
|
86
|
-
|
87
|
-
elsif @bui1 && (m = @bui1.match(curtyp))
|
87
|
+
if (@bui2 && (m = @bui2.match(curtyp))) ||
|
88
|
+
(@bui1 && (m = @bui1.match(curtyp)))
|
88
89
|
m
|
89
90
|
elsif @rgx.match(curtyp)
|
90
91
|
type_mapping
|
@@ -134,6 +135,7 @@ module Safrano
|
|
134
135
|
RgxTypeMapping1Par.new(self)
|
135
136
|
end
|
136
137
|
end
|
138
|
+
|
137
139
|
class Builder2Par < Builder
|
138
140
|
def initialize(db_ty_rgx, proc)
|
139
141
|
@db_types_rgx = db_ty_rgx
|
@@ -177,6 +179,7 @@ module Safrano
|
|
177
179
|
@castfunc = builder.castfunc
|
178
180
|
end
|
179
181
|
end
|
182
|
+
|
180
183
|
class RgxTypeMapping2Par < RgxTypeMapping
|
181
184
|
def initialize(builder)
|
182
185
|
@edm_type = builder.xedm_type
|