safrano 0.8.0 → 0.8.2
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 +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
|