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 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: []