safrano 0.4.4 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,7 @@ require_relative 'error'
6
6
  module Safrano
7
7
  # represents a state transition when navigating/parsing the url path
8
8
  # from left to right
9
- class Transition < Regexp
9
+ class Transition
10
10
  attr_accessor :trans
11
11
  attr_accessor :match_result
12
12
  attr_accessor :rgx
@@ -52,8 +52,28 @@ module Safrano
52
52
  ctx.method(@trans).call(@match_result)
53
53
  end
54
54
  end
55
-
55
+
56
+ #Transition that does not move/change the input
57
+ class InplaceTransition < Transition
58
+ def initialize(trans: )
59
+ @trans = trans
60
+ end
61
+ def do_match(str)
62
+ @str = str
63
+ end
64
+ def path_remain
65
+ @str
66
+ end
67
+ def path_done
68
+ EMPTYSTR
69
+ end
70
+ def do_transition(ctx)
71
+ ctx.method(@trans).call(@str)
72
+ end
73
+ end
74
+
56
75
  TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
76
+ TransitionExecuteFunc = InplaceTransition.new(trans: 'transition_execute_func')
57
77
  TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
58
78
  trans: 'transition_metadata')
59
79
  TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
data/lib/odata/walker.rb CHANGED
@@ -29,18 +29,24 @@ module Safrano
29
29
 
30
30
  # are $links requested ?
31
31
  attr_reader :do_links
32
+
33
+ attr_reader :request
32
34
 
33
- NIL_SERVICE_FATAL = 'Walker is called with a nil service'.freeze
34
- EMPTYSTR = ''.freeze
35
- SLASH = '/'.freeze
35
+ NIL_SERVICE_FATAL = 'Walker is called with a nil service'
36
+ EMPTYSTR = ''
37
+ SLASH = '/'
36
38
 
37
- def initialize(service, path, content_id_refs = nil)
39
+ def initialize(service, path, request, content_id_refs = nil )
38
40
  raise NIL_SERVICE_FATAL unless service
39
41
 
40
42
  path = URI.decode_www_form_component(path)
41
43
  @context = service
42
44
  @content_id_refs = content_id_refs
43
-
45
+
46
+ # needed because for function import we need access to the url parameters (req.params)
47
+ # who contains the functions params
48
+ @request = request
49
+
44
50
  @contexts = [@context]
45
51
 
46
52
  @path_start = @path_remain = if service
@@ -109,6 +115,16 @@ module Safrano
109
115
  end
110
116
  end
111
117
 
118
+ # execute function import with request parameters
119
+ # input: @context containt the function to exectute,
120
+ # @request.params should normally contain the params
121
+ # result: validate the params for the given function, execute the function and
122
+ # return it's result back into @context,
123
+ # and finaly set status :end (or error if anyting went wrong )
124
+ def do_run_with_execute_func
125
+ @context, @status, @error = @context.do_execute_func(@request)
126
+ end
127
+
112
128
  # little hacks... depending on returned state, set some attributes
113
129
  def state_mappings
114
130
  case @status
@@ -137,6 +153,8 @@ module Safrano
137
153
  # entity reference here and place it in @context
138
154
  when :run_with_content_id
139
155
  do_run_with_content_id
156
+ when :run_with_execute_func
157
+ do_run_with_execute_func
140
158
  end
141
159
 
142
160
  @contexts << @context
@@ -62,6 +62,8 @@ module Safrano
62
62
  # All tap_valid* handlers are executed
63
63
  # tap_error* handlers are not executed
64
64
  class Valid
65
+ attr_reader :result
66
+
65
67
  def initialize(result)
66
68
  @result = result
67
69
  end
@@ -100,17 +102,13 @@ module Safrano
100
102
  def error
101
103
  nil
102
104
  end
103
-
104
- def result
105
- @result
106
- end
107
105
  end # class Valid
108
106
 
109
- def Contract.valid(result)
107
+ def self.valid(result)
110
108
  Contract::Valid.new(result)
111
109
  end
112
110
 
113
- def Contract.and(*contracts)
111
+ def self.and(*contracts)
114
112
  # pick the first error if any
115
113
  if (ff = contracts.find(&:error))
116
114
  return ff
@@ -118,18 +116,18 @@ module Safrano
118
116
 
119
117
  # return a new one with @result = list of the contracts's results
120
118
  # usually this then be reduced again with #collect_result! or # #if_valid_collect methods
121
- Contract.valid(contracts.map(&:result))
119
+ valid(contracts.map(&:result))
122
120
  end
123
121
 
124
122
  # shortcut for Contract.and(*contracts).collect_result!
125
- def Contract.collect_result!(*contracts)
123
+ def self.collect_result!(*contracts)
126
124
  # pick the first error if any
127
125
  if (ff = contracts.find(&:error))
128
126
  return ff
129
127
  end
130
128
 
131
129
  # return a new one with @result = yield(*list of the contracts's results)
132
- Contract.valid(yield(*contracts.map(&:result)))
130
+ valid(yield(*contracts.map(&:result)))
133
131
  end
134
132
 
135
133
  # generic success return, when return value does not matter
data/lib/safrano/core.rb CHANGED
@@ -6,22 +6,22 @@ module Safrano
6
6
  EMPTY_ARRAY = [].freeze
7
7
  EMPTY_HASH = {}.freeze
8
8
  EMPTY_HASH_IN_ARY = [EMPTY_HASH].freeze
9
- EMPTY_STRING = ''.freeze
9
+ EMPTY_STRING = ''
10
10
  ARY_204_EMPTY_HASH_ARY = [204, EMPTY_HASH, EMPTY_ARRAY].freeze
11
- SPACE = ' '.freeze
12
- COMMA = ','.freeze
11
+ SPACE = ' '
12
+ COMMA = ','
13
13
 
14
14
  # some prominent constants... probably already defined elsewhere eg in Rack
15
15
  # but lets KISS
16
- CONTENT_TYPE = 'Content-Type'.freeze
17
- CTT_TYPE_LC = 'content-type'.freeze
18
- TEXTPLAIN_UTF8 = 'text/plain;charset=utf-8'.freeze
19
- APPJSON = 'application/json'.freeze
20
- APPXML = 'application/xml'.freeze
21
- MP_MIXED = 'multipart/mixed'.freeze
22
- APPXML_UTF8 = 'application/xml;charset=utf-8'.freeze
23
- APPATOMXML_UTF8 = 'application/atomsvc+xml;charset=utf-8'.freeze
24
- APPJSON_UTF8 = 'application/json;charset=utf-8'.freeze
16
+ CONTENT_TYPE = 'Content-Type'
17
+ CTT_TYPE_LC = 'content-type'
18
+ TEXTPLAIN_UTF8 = 'text/plain;charset=utf-8'
19
+ APPJSON = 'application/json'
20
+ APPXML = 'application/xml'
21
+ MP_MIXED = 'multipart/mixed'
22
+ APPXML_UTF8 = 'application/xml;charset=utf-8'
23
+ APPATOMXML_UTF8 = 'application/atomsvc+xml;charset=utf-8'
24
+ APPJSON_UTF8 = 'application/json;charset=utf-8'
25
25
 
26
26
  CT_JSON = { CONTENT_TYPE => APPJSON_UTF8 }.freeze
27
27
  CT_TEXT = { CONTENT_TYPE => TEXTPLAIN_UTF8 }.freeze
@@ -22,7 +22,7 @@ module Safrano
22
22
  module Deprecation
23
23
  @backtrace_filter = false
24
24
  @output = $stderr
25
- @prefix = "SAFRANO DEPRECATION WARNING: ".freeze
25
+ @prefix = 'SAFRANO DEPRECATION WARNING: '
26
26
 
27
27
  class << self
28
28
  # How to filter backtraces. +false+ does not include backtraces, +true+ includes
@@ -64,10 +64,10 @@ module Safrano
64
64
  # otherwise do nothing as the ruby implementation does not support constant deprecation.
65
65
  def self.deprecate_constant(mod, constant)
66
66
  # :nocov:
67
- if RUBY_VERSION > '2.3'
68
- # :nocov:
69
- mod.deprecate_constant(constant)
70
- end
67
+ return unless RUBY_VERSION > '2.3'
68
+
69
+ # :nocov:
70
+ mod.deprecate_constant(constant)
71
71
  end
72
72
  end
73
73
  end
@@ -4,7 +4,7 @@ CRLF = "\r\n"
4
4
  LF = "\n"
5
5
 
6
6
  require 'securerandom'
7
- require 'webrick/httpstatus'
7
+ require 'rack/utils'
8
8
 
9
9
  # Simple multipart support for OData $batch purpose
10
10
  module MIME
@@ -110,11 +110,11 @@ module MIME
110
110
  @target.ct = @target_ct
111
111
  @state = :bmp
112
112
  end
113
- MPS = 'multipart/'.freeze
113
+ MPS = 'multipart/'
114
114
  MP_RGX1 = %r{^(digest|mixed);\s*boundary="(.*)"}.freeze
115
115
  MP_RGX2 = %r{^(digest|mixed);\s*boundary=(.*)}.freeze
116
116
  # APP_HTTP_RGX = %r{^application/http}.freeze
117
- APP_HTTP = 'application/http'.freeze
117
+ APP_HTTP = 'application/http'
118
118
  def new_content
119
119
  @target =
120
120
  if @target_ct.start_with?(MPS) &&
@@ -457,7 +457,7 @@ module MIME
457
457
  "Content-Transfer-Encoding: binary#{CRLF}",
458
458
  'HTTP/1.1 '].join(CRLF).freeze
459
459
 
460
- StatusMessage = ::WEBrick::HTTPStatus::StatusMessage.freeze
460
+ StatusMessage = ::Rack::Utils::HTTP_STATUS_CODES.freeze
461
461
 
462
462
  def initialize
463
463
  @hd = {}
@@ -56,7 +56,7 @@ module Safrano
56
56
  NOCACHE_HDRS = { 'Cache-Control' => 'no-cache',
57
57
  'Expires' => '-1',
58
58
  'Pragma' => 'no-cache' }.freeze
59
- DATASERVICEVERSION = 'DataServiceVersion'.freeze
59
+ DATASERVICEVERSION = 'DataServiceVersion'
60
60
  include MethodHandlers
61
61
 
62
62
  def before
@@ -9,7 +9,7 @@ module Safrano
9
9
  # is not passed
10
10
  class Request < Rack::Request
11
11
  HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
12
- HEADER_VAL_RAW = '(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*'.freeze
12
+ HEADER_VAL_RAW = '(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*'
13
13
  HEADER_VAL_WITH_PAR = /(?:#{HEADER_VAL_RAW})\s*(?:;#{HEADER_PARAM})*/.freeze
14
14
  ON_CGST_ERROR = (proc { |r| raise(Sequel::Rollback) if r.in_changeset })
15
15
 
@@ -99,6 +99,7 @@ module Safrano
99
99
  def create_odata_walker
100
100
  @env['safrano.walker'] = @walker = Walker.new(@service,
101
101
  path_info,
102
+ self,
102
103
  @content_id_references)
103
104
  end
104
105
 
@@ -194,7 +195,7 @@ module Safrano
194
195
  if (rqv = env['HTTP_DATASERVICEVERSION'])
195
196
  if (m = DATASERVICEVERSION_RGX.match(rqv))
196
197
  # client request an too new version --> 501
197
- if ((v = m[1]) > Safrano::MAX_DATASERVICE_VERSION) or
198
+ if ((v = m[1]) > Safrano::MAX_DATASERVICE_VERSION) ||
198
199
  (v < Safrano::MIN_DATASERVICE_VERSION)
199
200
  Safrano::VersionNotImplementedError
200
201
  else
@@ -15,8 +15,8 @@ module Safrano
15
15
  # and will be included in Service class
16
16
  module ExpandHandler
17
17
  PATH_SPLITTER = %r{\A(\w+)/?(.*)\z}.freeze
18
- DEFERRED = '__deferred'.freeze
19
- URI = 'uri'.freeze
18
+ DEFERRED = '__deferred'
19
+ URI = 'uri'
20
20
 
21
21
  def get_deferred_odata_h(entity_uri:, attrib:)
22
22
  { DEFERRED => { URI => "#{entity_uri}/#{attrib}" } }
@@ -24,8 +24,8 @@ module Safrano
24
24
 
25
25
  # default v2
26
26
  # overriden in ServiceV1
27
- RESULTS_K = 'results'.freeze
28
- COUNT_K = '__count'.freeze
27
+ RESULTS_K = 'results'
28
+ COUNT_K = '__count'
29
29
  def get_coll_odata_h(array:, template:, icount: nil)
30
30
  array.map! do |w|
31
31
  get_entity_odata_h(entity: w, template: template)
@@ -40,7 +40,7 @@ module Safrano
40
40
  end
41
41
 
42
42
  EMPTYH = {}.freeze
43
- METADATA_K = '__metadata'.freeze
43
+ METADATA_K = '__metadata'
44
44
  def get_entity_odata_h(entity:, template:)
45
45
  # start with metadata
46
46
  hres = { METADATA_K => entity.metadata_h }
@@ -90,21 +90,20 @@ module Safrano
90
90
  # xml namespace constants needed for the output of XML service and
91
91
  # and metadata
92
92
  module XMLNS
93
- MSFT_ADO = 'http://schemas.microsoft.com/ado'.freeze
94
- MSFT_ADO_2009_EDM = "#{MSFT_ADO}/2009/11/edm".freeze
95
- MSFT_ADO_2007_EDMX = "#{MSFT_ADO}/2007/06/edmx".freeze
96
- MSFT_ADO_2007_META = MSFT_ADO + \
97
- '/2007/08/dataservices/metadata'.freeze
98
-
99
- W3_2005_ATOM = 'http://www.w3.org/2005/Atom'.freeze
100
- W3_2007_APP = 'http://www.w3.org/2007/app'.freeze
93
+ MSFT_ADO = 'http://schemas.microsoft.com/ado'
94
+ MSFT_ADO_2009_EDM = "#{MSFT_ADO}/2009/11/edm"
95
+ MSFT_ADO_2007_EDMX = "#{MSFT_ADO}/2007/06/edmx"
96
+ MSFT_ADO_2007_META = "#{MSFT_ADO}/2007/08/dataservices/metadata"
97
+
98
+ W3_2005_ATOM = 'http://www.w3.org/2005/Atom'
99
+ W3_2007_APP = 'http://www.w3.org/2007/app'
101
100
  end
102
101
  end
103
102
 
104
103
  # Link to Model
105
104
  module Safrano
106
- MAX_DATASERVICE_VERSION = '2'.freeze
107
- MIN_DATASERVICE_VERSION = '1'.freeze
105
+ MAX_DATASERVICE_VERSION = '2'
106
+ MIN_DATASERVICE_VERSION = '1'
108
107
  CV_MAX_DATASERVICE_VERSION = Contract.valid(MAX_DATASERVICE_VERSION).freeze
109
108
  CV_MIN_DATASERVICE_VERSION = Contract.valid(MIN_DATASERVICE_VERSION).freeze
110
109
  include XMLNS
@@ -113,7 +112,7 @@ module Safrano
113
112
  include Safrano
114
113
  include ExpandHandler
115
114
 
116
- XML_PREAMBLE = %Q(<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n).freeze
115
+ XML_PREAMBLE = %(<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n)
117
116
 
118
117
  # This is just a hash of entity Set Names to the corresponding Class
119
118
  # Example
@@ -138,6 +137,7 @@ module Safrano
138
137
  attr_accessor :relman
139
138
  attr_accessor :complex_types
140
139
  attr_accessor :function_imports
140
+ attr_accessor :function_import_keys
141
141
 
142
142
  # Instance attributes for specialized Version specific Instances
143
143
  attr_accessor :v1
@@ -156,13 +156,14 @@ module Safrano
156
156
  @relman = Safrano::RelationManager.new
157
157
  @complex_types = Set.new
158
158
  @function_imports = {}
159
+ @function_import_keys = []
159
160
  @cmap = {}
160
161
  instance_eval(&block) if block_given?
161
162
  end
162
163
 
163
164
  TRAILING_SLASH = %r{/\z}.freeze
164
- DEFAULT_PATH_PREFIX = '/'.freeze
165
- DEFAULT_SERVER_URL = 'http://localhost:9494'.freeze
165
+ DEFAULT_PATH_PREFIX = '/'
166
+ DEFAULT_SERVER_URL = 'http://localhost:9494'
166
167
 
167
168
  def enable_batch
168
169
  @batch_handler = Safrano::Batch::EnabledHandler.new
@@ -205,6 +206,12 @@ module Safrano
205
206
  (@v2.xserver_url = @xserver_url) if @v2
206
207
  end
207
208
 
209
+ # keep the bug active for now, but allow to activate the fix,
210
+ # later we will change the default to be fixed
211
+ def bugfix_create_response(bool = false)
212
+ @bugfix_create_response = bool
213
+ end
214
+
208
215
  # end public API
209
216
 
210
217
  def set_uribase
@@ -234,6 +241,7 @@ module Safrano
234
241
  other.batch_handler = @batch_handler
235
242
  other.complex_types = @complex_types
236
243
  other.function_imports = @function_imports
244
+ other.function_import_keys = @function_import_keys
237
245
  other
238
246
  end
239
247
 
@@ -317,6 +325,8 @@ module Safrano
317
325
  def function_import(name)
318
326
  funcimp = Safrano::FunctionImport(name)
319
327
  @function_imports[name] = funcimp
328
+ @function_import_keys << name
329
+ set_funcimports_sorted
320
330
  funcimp
321
331
  end
322
332
 
@@ -333,7 +343,11 @@ module Safrano
333
343
  @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse! if @collections
334
344
  @collections
335
345
  end
336
-
346
+
347
+ # need to be sorted by size too
348
+ def set_funcimports_sorted
349
+ @function_import_keys.sort_by!{|k| k.size}.reverse!
350
+ end
337
351
  # to be called at end of publishing block to ensure we get the right names
338
352
  # and additionally build the list of valid attribute path's used
339
353
  # for validation of $orderby or $filter params
@@ -362,6 +376,9 @@ module Safrano
362
376
  @collections.each do |klass|
363
377
  klass.build_uri(@uribase)
364
378
  klass.include(klass.time_cols.empty? ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
379
+
380
+ # Output create (POST) as single entity (Standard) or as array (non-standard buggy)
381
+ klass.include ( @bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
365
382
  end
366
383
 
367
384
  # build allowed transitions (requires that @collections are filled and sorted for having a
@@ -393,7 +410,7 @@ module Safrano
393
410
  end
394
411
 
395
412
  def base_url_func_regexp
396
- @function_imports.keys.join('|')
413
+ @function_import_keys.join('|')
397
414
  end
398
415
 
399
416
  def service
@@ -530,7 +547,7 @@ module Safrano
530
547
 
531
548
  # methods related to transitions to next state (cf. walker)
532
549
  module Transitions
533
- DOLLAR_ID_REGEXP = Regexp.new('\A\/\$').freeze
550
+ DOLLAR_ID_REGEXP = Regexp.new('\A\/\$')
534
551
  ALLOWED_TRANSITIONS_FIXED = [
535
552
  Safrano::TransitionEnd,
536
553
  Safrano::TransitionMetadata,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Safrano
4
- VERSION = '0.4.4'
4
+ VERSION = '0.5.2'
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.4.4
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - oz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-30 00:00:00.000000000 Z
11
+ date: 2021-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rfc2047
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '0.3'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '0.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +108,7 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.51'
111
- description: Safrano is an OData server library based on Ruby, Rack and Sequel.
111
+ description: Safrano is an OData server library based on Ruby Sequel and Rack.
112
112
  email: dev@aithscel.eu
113
113
  executables: []
114
114
  extensions: []
@@ -189,8 +189,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
189
  - !ruby/object:Gem::Version
190
190
  version: '0'
191
191
  requirements: []
192
- rubygems_version: 3.1.4
192
+ rubygems_version: 3.2.5
193
193
  signing_key:
194
194
  specification_version: 4
195
- summary: Safrano is a Ruby OData server library based on Sequel and Rack
195
+ summary: Safrano is an OData server library based on Ruby Sequel and Rack
196
196
  test_files: []