safrano 0.4.4 → 0.5.2

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