safrano 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/odata/entity.rb +3 -10
- data/lib/odata/error.rb +5 -1
- data/lib/odata/model_ext.rb +26 -15
- data/lib/safrano/rack_app.rb +7 -2
- data/lib/safrano/rack_builder.rb +63 -2
- data/lib/safrano/request.rb +11 -9
- data/lib/safrano/service.rb +5 -3
- data/lib/safrano/version.rb +1 -1
- metadata +22 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c19f9f9e0a4700de60c1a177c1555663f50ab02e2e5fb0449e21f3c43751dbc
|
4
|
+
data.tar.gz: 0a56ca92c60b1e1a943f7710f7993c325b8e1e9d20b9df745b87dea954170722
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f273cc9008c3bdab427268c5bf139aeb30093c515611714110da1b3f48d90b86ab40c62e6c96aea6dacc8eb7381793d126850fc8bccab991d89fcf010075647
|
7
|
+
data.tar.gz: 3d1e65f46c4e3fe1765288fb1f06e3f488b708216ce708103945fbf2aa12dd088e7fe6da8e06ebc8c6fa18b6bbd65b67506a5c8a8dde2097f405b57ceaa9d2b4
|
data/lib/odata/entity.rb
CHANGED
@@ -184,16 +184,9 @@ module Safrano
|
|
184
184
|
if req.walker.media_value
|
185
185
|
odata_media_value_put(req)
|
186
186
|
elsif req.accept?(APPJSON)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
set_fields(data, self.class.data_fields, missing: :skip)
|
191
|
-
save(transaction: false)
|
192
|
-
else
|
193
|
-
update_fields(data, self.class.data_fields, missing: :skip)
|
194
|
-
end
|
195
|
-
|
196
|
-
[202, EMPTY_HASH, to_odata_post_json(service: req.service)]
|
187
|
+
|
188
|
+
AlreadyExistsUnprocessableError.odata_get(req)
|
189
|
+
|
197
190
|
else # TODO: other formats
|
198
191
|
415
|
199
192
|
end
|
data/lib/odata/error.rb
CHANGED
@@ -142,7 +142,11 @@ module Safrano
|
|
142
142
|
@msg = reason
|
143
143
|
end
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
|
+
class AlreadyExistsUnprocessableError < UnprocessableEntityError
|
147
|
+
@msg = 'The ressource you are trying to create already exists'
|
148
|
+
end
|
149
|
+
|
146
150
|
# http Bad Req.
|
147
151
|
class BadRequestError
|
148
152
|
extend ErrorClass
|
data/lib/odata/model_ext.rb
CHANGED
@@ -142,40 +142,39 @@ module Safrano
|
|
142
142
|
@attribute_path_list = attribute_path_list
|
143
143
|
end
|
144
144
|
|
145
|
-
MAX_DEPTH =
|
146
|
-
|
145
|
+
MAX_DEPTH = 6
|
146
|
+
MAX_TYPE_REPETITION = 2
|
147
|
+
def attribute_path_list(depth_path = [])
|
147
148
|
ret = @columns_str.dup
|
148
149
|
# break circles
|
149
|
-
return ret if
|
150
|
-
|
151
|
-
depth += 1
|
150
|
+
return ret if depth_path.size > MAX_DEPTH
|
151
|
+
return ret if depth_path.count(self) > MAX_TYPE_REPETITION
|
152
152
|
|
153
153
|
@nav_entity_attribs&.each do |a, k|
|
154
|
-
ret.concat(k.attribute_path_list(
|
154
|
+
ret.concat(k.attribute_path_list(depth_path + [self]).map { |kc| "#{a}/#{kc}" })
|
155
155
|
end
|
156
156
|
|
157
157
|
@nav_collection_attribs&.each do |a, k|
|
158
|
-
ret.concat(k.attribute_path_list(
|
158
|
+
ret.concat(k.attribute_path_list(depth_path + [self]).map { |kc| "#{a}/#{kc}" })
|
159
159
|
end
|
160
160
|
ret
|
161
161
|
end
|
162
162
|
|
163
|
-
def expand_path_list(
|
163
|
+
def expand_path_list(depth_path = [])
|
164
164
|
ret = []
|
165
165
|
ret.concat(@nav_entity_attribs_keys) if @nav_entity_attribs
|
166
166
|
ret.concat(@nav_collection_attribs_keys) if @nav_collection_attribs
|
167
167
|
|
168
168
|
# break circles
|
169
|
-
return ret if
|
170
|
-
|
171
|
-
depth += 1
|
169
|
+
return ret if depth_path.size > MAX_DEPTH
|
170
|
+
return ret if depth_path.count(self) > MAX_TYPE_REPETITION
|
172
171
|
|
173
172
|
@nav_entity_attribs&.each do |a, k|
|
174
|
-
ret.concat(k.expand_path_list(
|
173
|
+
ret.concat(k.expand_path_list(depth_path + [self]).map { |kc| "#{a}/#{kc}" })
|
175
174
|
end
|
176
175
|
|
177
176
|
@nav_collection_attribs&.each do |a, k|
|
178
|
-
ret.concat(k.expand_path_list(
|
177
|
+
ret.concat(k.expand_path_list(depth_path + [self]).map { |kc| "#{a}/#{kc}" })
|
179
178
|
end
|
180
179
|
ret
|
181
180
|
end
|
@@ -446,8 +445,20 @@ module Safrano
|
|
446
445
|
|
447
446
|
# check if key needs casting. Important for later entity-uri generation !
|
448
447
|
return unless primary_key.is_a? Symbol # single key field
|
449
|
-
|
450
|
-
|
448
|
+
|
449
|
+
# guid key as guid'xxx-yyy-zzz'
|
450
|
+
metadata = @cols_metadata[primary_key]
|
451
|
+
|
452
|
+
if metadata[:edm_type] == 'Edm.Guid'
|
453
|
+
props = db_schema[primary_key]
|
454
|
+
@pk_castfunc = if props[:type] == :blob # Edm.Guid but as 16 byte binary Blob on DB level, eg in Sqlite
|
455
|
+
lambda { |x| "guid'#{UUIDTools::UUID.parse_raw(x)}'" unless x.nil? }
|
456
|
+
else
|
457
|
+
lambda { |x| "guid'#{x}'" unless x.nil? }
|
458
|
+
end
|
459
|
+
else
|
460
|
+
@pk_castfunc = @casted_cols[primary_key]
|
461
|
+
end
|
451
462
|
end # build_casted_cols(service)
|
452
463
|
|
453
464
|
def finalize_publishing(service)
|
data/lib/safrano/rack_app.rb
CHANGED
@@ -8,7 +8,7 @@ require_relative 'response'
|
|
8
8
|
module Safrano
|
9
9
|
# Note there is a strong 1 to 1 relation between an app instance
|
10
10
|
# and a published service. --> actually means also
|
11
|
-
# we only support
|
11
|
+
# we only support one service per App-class because publishing is
|
12
12
|
# made on app class level
|
13
13
|
class ServerApp
|
14
14
|
def initialize
|
@@ -36,7 +36,12 @@ module Safrano
|
|
36
36
|
def self.get_service_base
|
37
37
|
@service_base
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
|
+
# needed for safrano-rack_builder
|
41
|
+
def get_path_prefix
|
42
|
+
self.class.get_service_base.xpath_prefix
|
43
|
+
end
|
44
|
+
|
40
45
|
def self.set_servicebase(sbase)
|
41
46
|
@service_base = sbase
|
42
47
|
@service_base.enable_v1_service
|
data/lib/safrano/rack_builder.rb
CHANGED
@@ -7,12 +7,73 @@ 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
11
|
class Builder < ::Rack::Builder
|
12
|
+
|
11
13
|
def initialize(default_app = nil, &block)
|
12
14
|
super(default_app) {}
|
13
|
-
|
15
|
+
@middlewares = []
|
16
|
+
|
14
17
|
instance_eval(&block) if block_given?
|
15
|
-
|
18
|
+
prepend_rackcors_ifneeded
|
19
|
+
end
|
20
|
+
|
21
|
+
def use(middleware, *args, &block)
|
22
|
+
@middlewares << middleware
|
23
|
+
super(middleware, *args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def prepend_rackcors_ifneeded
|
27
|
+
return if stack_has_rackcors
|
28
|
+
|
29
|
+
# get the safrano app path prefix
|
30
|
+
# normally @run is a Safrano Server app
|
31
|
+
return unless @run.is_a? ::Safrano::ServerApp
|
32
|
+
|
33
|
+
|
34
|
+
service_path_prefix = @run.get_path_prefix.dup
|
35
|
+
|
36
|
+
# due to a bug in rack-cors
|
37
|
+
# we cant use the batch ressource path as
|
38
|
+
# 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
|
+
# Ressource /$batch
|
43
|
+
/\A\/\$batch\z/
|
44
|
+
else
|
45
|
+
# Ressource like /foo/bar/baz/$batch
|
46
|
+
service_path_prefix.sub!(::Safrano::TRAILING_SLASH_RGX, '')
|
47
|
+
service_path_prefix.sub!(::Safrano::LEADING_SLASH_RGX, '')
|
48
|
+
# now is foo/bar/baz
|
49
|
+
path_prefix_rgx = Regexp.escape("/#{service_path_prefix}/$batch")
|
50
|
+
# now escaped path regexp /foo/bar/baz/$batch
|
51
|
+
# finaly just add start / end anchors
|
52
|
+
/\A#{path_prefix_rgx}\z/
|
53
|
+
end
|
54
|
+
# this will append rack-cors mw
|
55
|
+
# per default allow GET * from everywhere
|
56
|
+
# allow POST $batch from localhost only
|
57
|
+
use ::Rack::Cors do
|
58
|
+
allow do
|
59
|
+
origins LOCALHOST_ANY_PORT_RGX
|
60
|
+
resource (batch_path_regexp), headers: :any, methods: [:post, :head, :options]
|
61
|
+
resource '*', headers: :any, methods: [:get, :post, :put, :patch, :head, :options]
|
62
|
+
end
|
63
|
+
allow do
|
64
|
+
origins '*'
|
65
|
+
resource '*', headers: :any, methods: [:get, :head, :options]
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
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)
|
73
|
+
end
|
74
|
+
|
75
|
+
def stack_has_rackcors
|
76
|
+
@middlewares.find{|mw| mw == Rack::Cors }
|
16
77
|
end
|
17
78
|
end
|
18
79
|
end
|
data/lib/safrano/request.rb
CHANGED
@@ -7,14 +7,16 @@ module Safrano
|
|
7
7
|
# handle GET PUT etc
|
8
8
|
module MethodHandlers
|
9
9
|
def odata_options
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
# cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
|
11
|
+
headers.delete('Content-Type')
|
12
|
+
@response.headers.delete('Content-Type')
|
13
|
+
@response.headers['Content-Type'] = ''
|
14
|
+
|
15
|
+
# we only let rack-cors handle Cors OPTIONS .
|
16
|
+
# otherwise dont do it...
|
17
|
+
# see https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
|
18
|
+
# 501 not implemented
|
19
|
+
[501, EMPTY_HASH, '']
|
18
20
|
end
|
19
21
|
|
20
22
|
def odata_delete
|
@@ -39,7 +41,7 @@ module Safrano
|
|
39
41
|
|
40
42
|
def odata_post
|
41
43
|
@walker.finalize.tap_error { |err| return err.odata_get(self) }
|
42
|
-
|
44
|
+
.if_valid { |context| context.odata_post(self) }
|
43
45
|
end
|
44
46
|
|
45
47
|
def odata_head
|
data/lib/safrano/service.rb
CHANGED
@@ -107,6 +107,8 @@ module Safrano
|
|
107
107
|
MIN_DATASERVICE_VERSION = '1'
|
108
108
|
CV_MAX_DATASERVICE_VERSION = Contract.valid(MAX_DATASERVICE_VERSION).freeze
|
109
109
|
CV_MIN_DATASERVICE_VERSION = Contract.valid(MIN_DATASERVICE_VERSION).freeze
|
110
|
+
TRAILING_SLASH_RGX = %r{/\z}.freeze
|
111
|
+
LEADING_SLASH_RGX = %r{\A/}.freeze
|
110
112
|
include XMLNS
|
111
113
|
# Base class for service. Subclass will be for V1, V2 etc...
|
112
114
|
class ServiceBase
|
@@ -166,7 +168,6 @@ module Safrano
|
|
166
168
|
instance_eval(&block) if block_given?
|
167
169
|
end
|
168
170
|
|
169
|
-
TRAILING_SLASH = %r{/\z}.freeze
|
170
171
|
DEFAULT_PATH_PREFIX = '/'
|
171
172
|
DEFAULT_SERVER_URL = 'http://localhost:9494'
|
172
173
|
|
@@ -200,13 +201,14 @@ module Safrano
|
|
200
201
|
end
|
201
202
|
|
202
203
|
def path_prefix(path_pr)
|
203
|
-
@xpath_prefix = path_pr.sub(
|
204
|
+
@xpath_prefix = path_pr.sub(TRAILING_SLASH_RGX, '')
|
205
|
+
@xpath_prefix.freeze
|
204
206
|
(@v1.xpath_prefix = @xpath_prefix) if @v1
|
205
207
|
(@v2.xpath_prefix = @xpath_prefix) if @v2
|
206
208
|
end
|
207
209
|
|
208
210
|
def server_url(surl)
|
209
|
-
@xserver_url = surl.sub(
|
211
|
+
@xserver_url = surl.sub(TRAILING_SLASH_RGX, '')
|
210
212
|
(@v1.xserver_url = @xserver_url) if @v1
|
211
213
|
(@v2.xserver_url = @xserver_url) if @v2
|
212
214
|
end
|
data/lib/safrano/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safrano
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- oz
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -120,6 +120,20 @@ dependencies:
|
|
120
120
|
- - "~>"
|
121
121
|
- !ruby/object:Gem::Version
|
122
122
|
version: '2.2'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: rexml
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '3.0'
|
130
|
+
type: :runtime
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '3.0'
|
123
137
|
- !ruby/object:Gem::Dependency
|
124
138
|
name: rack-test
|
125
139
|
requirement: !ruby/object:Gem::Requirement
|
@@ -231,15 +245,15 @@ files:
|
|
231
245
|
- lib/safrano/type_mapping.rb
|
232
246
|
- lib/safrano/version.rb
|
233
247
|
- lib/sequel/plugins/join_by_paths.rb
|
234
|
-
homepage: https://
|
248
|
+
homepage: https://safrano.aithscel.eu
|
235
249
|
licenses:
|
236
250
|
- MIT
|
237
251
|
metadata:
|
238
252
|
bug_tracker_uri: https://gitlab.com/dm0da/safrano/issues
|
239
253
|
changelog_uri: https://gitlab.com/dm0da/safrano/blob/master/CHANGELOG
|
240
254
|
source_code_uri: https://gitlab.com/dm0da/safrano/tree/master
|
241
|
-
|
242
|
-
post_install_message:
|
255
|
+
documentation_uri: https://gitlab.com/dm0da/safrano/-/blob/master/README.md
|
256
|
+
post_install_message:
|
243
257
|
rdoc_options: []
|
244
258
|
require_paths:
|
245
259
|
- lib
|
@@ -254,8 +268,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
268
|
- !ruby/object:Gem::Version
|
255
269
|
version: '0'
|
256
270
|
requirements: []
|
257
|
-
rubygems_version: 3.
|
258
|
-
signing_key:
|
271
|
+
rubygems_version: 3.3.15
|
272
|
+
signing_key:
|
259
273
|
specification_version: 4
|
260
274
|
summary: Safrano is an OData server library
|
261
275
|
test_files: []
|