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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dda061616194242041f7308c791bc5689fabaf0627f6f5090df3141fe5a34d8e
4
- data.tar.gz: bec6791bb225330e1e149bf4166012397f9ffce9df2dbe0be3d7b224c5172212
3
+ metadata.gz: 3c19f9f9e0a4700de60c1a177c1555663f50ab02e2e5fb0449e21f3c43751dbc
4
+ data.tar.gz: 0a56ca92c60b1e1a943f7710f7993c325b8e1e9d20b9df745b87dea954170722
5
5
  SHA512:
6
- metadata.gz: 7aef08bdf67609d7e836b955e478f4b0a8cd67a08fc6caf0c9e9b263bac3d59086a6da59967cdacf5b3bb328060cbb20b9cdbe70e28fcacb5ce464e63eeb7e59
7
- data.tar.gz: 2f65e93d591fc18ca7836a17c61250bc181c1d9aff14bd875e2d2f4803406ca76d577c6cd4c0b5f7e468240ac6d63e9389f7367a5b6cba6bfa298c4c5efd5fcd
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
- data.delete('__metadata')
188
-
189
- if req.in_changeset
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
@@ -142,40 +142,39 @@ module Safrano
142
142
  @attribute_path_list = attribute_path_list
143
143
  end
144
144
 
145
- MAX_DEPTH = 4
146
- def attribute_path_list(depth = 0)
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 depth >= MAX_DEPTH
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(depth).map { |kc| "#{a}/#{kc}" })
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(depth).map { |kc| "#{a}/#{kc}" })
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(depth = 0)
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 depth >= MAX_DEPTH
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(depth).map { |kc| "#{a}/#{kc}" })
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(depth).map { |kc| "#{a}/#{kc}" })
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
- @pk_castfunc = @casted_cols[primary_key]
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)
@@ -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 on service per App-class because publishing is
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
@@ -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
- use ::Rack::Cors
15
+ @middlewares = []
16
+
14
17
  instance_eval(&block) if block_given?
15
- use ::Rack::ContentLength
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
@@ -7,14 +7,16 @@ module Safrano
7
7
  # handle GET PUT etc
8
8
  module MethodHandlers
9
9
  def odata_options
10
- @walker.finalize.tap_error { |err| return err.odata_get(self) }
11
- .if_valid do |_context|
12
- # cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
13
- headers.delete('Content-Type')
14
- @response.headers.delete('Content-Type')
15
- @response.headers['Content-Type'] = ''
16
- [200, EMPTY_HASH, '']
17
- end
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
- .if_valid { |context| context.odata_post(self) }
44
+ .if_valid { |context| context.odata_post(self) }
43
45
  end
44
46
 
45
47
  def odata_head
@@ -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(TRAILING_SLASH, '')
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(TRAILING_SLASH, '')
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Safrano
4
- VERSION = '0.7.0'
4
+ VERSION = '0.8.0'
5
5
  end
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.7.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-09-02 00:00:00.000000000 Z
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://gitlab.com/dm0da/safrano
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
- wiki_uri: https://gitlab.com/dm0da/safrano/wikis/home
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.2.5
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: []