safrano 0.7.0 → 0.8.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/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: []
|