safrano 0.3.4 → 0.4.4
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/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +17 -15
- data/lib/odata/collection.rb +141 -500
- data/lib/odata/collection_filter.rb +44 -37
- data/lib/odata/collection_media.rb +193 -43
- data/lib/odata/collection_order.rb +50 -37
- data/lib/odata/common_logger.rb +39 -12
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +201 -176
- data/lib/odata/error.rb +186 -33
- data/lib/odata/expand.rb +126 -0
- data/lib/odata/filter/base.rb +69 -0
- data/lib/odata/filter/error.rb +55 -6
- data/lib/odata/filter/parse.rb +38 -36
- data/lib/odata/filter/sequel.rb +121 -67
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +15 -11
- data/lib/odata/filter/tree.rb +110 -60
- data/lib/odata/function_import.rb +166 -0
- data/lib/odata/model_ext.rb +618 -0
- data/lib/odata/navigation_attribute.rb +50 -32
- data/lib/odata/relations.rb +7 -7
- data/lib/odata/select.rb +54 -0
- data/lib/{safrano_core.rb → odata/transition.rb} +14 -60
- data/lib/odata/url_parameters.rb +128 -37
- data/lib/odata/walker.rb +19 -11
- data/lib/safrano.rb +18 -28
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +43 -0
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/{multipart.rb → safrano/multipart.rb} +37 -41
- data/lib/safrano/rack_app.rb +175 -0
- data/lib/{odata_rack_builder.rb → safrano/rack_builder.rb} +18 -2
- data/lib/{request.rb → safrano/request.rb} +102 -50
- data/lib/{response.rb → safrano/response.rb} +5 -4
- data/lib/safrano/sequel_join_by_paths.rb +5 -0
- data/lib/{service.rb → safrano/service.rb} +257 -188
- data/lib/safrano/version.rb +5 -0
- data/lib/sequel/plugins/join_by_paths.rb +17 -29
- metadata +53 -17
- data/lib/rack_app.rb +0 -174
- data/lib/sequel_join_by_paths.rb +0 -5
- data/lib/version.rb +0 -4
data/lib/odata/walker.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'rexml/document'
|
5
|
-
require 'safrano
|
5
|
+
require 'safrano'
|
6
6
|
|
7
|
-
module
|
7
|
+
module Safrano
|
8
8
|
# handle navigation in the Datamodel tree of entities/attributes
|
9
9
|
# input is the url path. Url parameters ($filter etc...) are NOT handled here
|
10
10
|
# This uses a state transition algorithm
|
@@ -30,8 +30,12 @@ module OData
|
|
30
30
|
# are $links requested ?
|
31
31
|
attr_reader :do_links
|
32
32
|
|
33
|
+
NIL_SERVICE_FATAL = 'Walker is called with a nil service'.freeze
|
34
|
+
EMPTYSTR = ''.freeze
|
35
|
+
SLASH = '/'.freeze
|
36
|
+
|
33
37
|
def initialize(service, path, content_id_refs = nil)
|
34
|
-
raise
|
38
|
+
raise NIL_SERVICE_FATAL unless service
|
35
39
|
|
36
40
|
path = URI.decode_www_form_component(path)
|
37
41
|
@context = service
|
@@ -42,10 +46,9 @@ module OData
|
|
42
46
|
@path_start = @path_remain = if service
|
43
47
|
unprefixed(service.xpath_prefix, path)
|
44
48
|
else # This is for batch function
|
45
|
-
|
46
49
|
path
|
47
50
|
end
|
48
|
-
@path_done =
|
51
|
+
@path_done = String.new
|
49
52
|
@status = :start
|
50
53
|
@end_context = nil
|
51
54
|
@do_count = nil
|
@@ -53,12 +56,12 @@ module OData
|
|
53
56
|
end
|
54
57
|
|
55
58
|
def unprefixed(prefix, path)
|
56
|
-
if (prefix ==
|
59
|
+
if (prefix == EMPTYSTR) || (prefix == SLASH)
|
57
60
|
path
|
58
61
|
else
|
59
62
|
# path.sub!(/\A#{prefix}/, '')
|
60
63
|
# TODO check
|
61
|
-
path.sub(/\A#{prefix}/,
|
64
|
+
path.sub(/\A#{prefix}/, EMPTYSTR)
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
@@ -74,6 +77,7 @@ module OData
|
|
74
77
|
valid_tr = @context.allowed_transitions.select do |t|
|
75
78
|
t.do_match(@path_remain)
|
76
79
|
end
|
80
|
+
|
77
81
|
# this is a very fragile and obscure but required hack (wanted: a
|
78
82
|
# better one) to make attributes that are substrings of each other
|
79
83
|
# work well
|
@@ -95,13 +99,13 @@ module OData
|
|
95
99
|
@context = nil
|
96
100
|
@status = :error
|
97
101
|
# TODO: more appropriate error handling
|
98
|
-
@error =
|
102
|
+
@error = Safrano::ErrorNotFound
|
99
103
|
end
|
100
104
|
else
|
101
105
|
@context = nil
|
102
106
|
@status = :error
|
103
107
|
# TODO: more appropriate error handling
|
104
|
-
@error =
|
108
|
+
@error = Safrano::ErrorNotFound
|
105
109
|
end
|
106
110
|
end
|
107
111
|
|
@@ -151,7 +155,7 @@ module OData
|
|
151
155
|
else
|
152
156
|
@context = nil
|
153
157
|
@status = :error
|
154
|
-
@error =
|
158
|
+
@error = Safrano::ErrorNotFoundSegment.new(@path_remain)
|
155
159
|
end
|
156
160
|
end
|
157
161
|
# TODO: shouldnt we raise an error here if @status != :end ?
|
@@ -159,5 +163,9 @@ module OData
|
|
159
163
|
|
160
164
|
@end_context = @contexts.size >= 2 ? @contexts[-2] : @contexts[1]
|
161
165
|
end
|
166
|
+
|
167
|
+
def finalize
|
168
|
+
(@status == :end) ? Contract.valid(@end_context) : @error
|
169
|
+
end
|
162
170
|
end
|
163
171
|
end
|
data/lib/safrano.rb
CHANGED
@@ -1,32 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'rexml/document'
|
5
|
-
require_relative '
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
require_relative 'safrano/version'
|
6
|
+
require_relative 'safrano/deprecation'
|
7
|
+
require_relative 'safrano/core_ext'
|
8
|
+
require_relative 'safrano/contract'
|
9
|
+
require_relative 'safrano/multipart'
|
10
|
+
require_relative 'safrano/core'
|
11
|
+
require_relative 'odata/transition'
|
12
|
+
require_relative 'odata/edm/primitive_types'
|
13
|
+
require_relative 'odata/entity'
|
14
|
+
require_relative 'odata/attribute'
|
15
|
+
require_relative 'odata/navigation_attribute'
|
16
|
+
require_relative 'odata/model_ext'
|
17
|
+
require_relative 'safrano/service'
|
18
|
+
require_relative 'odata/walker'
|
13
19
|
require 'sequel'
|
14
|
-
require_relative '
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# picked from activsupport; needed for ruby < 2.5
|
19
|
-
# Destructively converts all keys using the +block+ operations.
|
20
|
-
# Same as +transform_keys+ but modifies +self+.
|
21
|
-
class Hash
|
22
|
-
def transform_keys!
|
23
|
-
keys.each do |key|
|
24
|
-
self[yield(key)] = delete(key)
|
25
|
-
end
|
26
|
-
self
|
27
|
-
end unless method_defined? :transform_keys!
|
28
|
-
|
29
|
-
def symbolize_keys!
|
30
|
-
transform_keys! { |key| key.to_sym rescue key }
|
31
|
-
end
|
32
|
-
end
|
20
|
+
require_relative 'safrano/sequel_join_by_paths'
|
21
|
+
require_relative 'safrano/rack_app'
|
22
|
+
require_relative 'safrano/rack_builder'
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Safrano
|
4
|
+
# Design: the Invalid module and the Valid class share exactly the same
|
5
|
+
# methods (ie. interface) so methods returning such objects can be
|
6
|
+
# post-processed in a declarative way
|
7
|
+
# Example:
|
8
|
+
# something.do_stuff(param).tap_valid{|valid_result| ... }
|
9
|
+
# .tap_error{|error| ... }
|
10
|
+
|
11
|
+
module Contract
|
12
|
+
# represents a invalid result, ie. an error
|
13
|
+
# this shall be included/extended to our Error classes thus
|
14
|
+
# automagically making them contract/flow enabled
|
15
|
+
|
16
|
+
# All tap_valid* handlers are not executed
|
17
|
+
# tap_error* handlers are executed
|
18
|
+
module Invalid
|
19
|
+
def tap_error
|
20
|
+
yield self
|
21
|
+
self # allow chaining
|
22
|
+
end
|
23
|
+
|
24
|
+
def tap_valid
|
25
|
+
self # allow chaining
|
26
|
+
end
|
27
|
+
|
28
|
+
def if_valid
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def if_error
|
33
|
+
yield self ## return this
|
34
|
+
end
|
35
|
+
|
36
|
+
def if_valid_collect
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def map_result!
|
41
|
+
self # allow chaining
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect_result!
|
45
|
+
self # allow chaining
|
46
|
+
end
|
47
|
+
|
48
|
+
def error
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def result
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Error
|
58
|
+
include Invalid
|
59
|
+
end
|
60
|
+
|
61
|
+
# represents a valid result.
|
62
|
+
# All tap_valid* handlers are executed
|
63
|
+
# tap_error* handlers are not executed
|
64
|
+
class Valid
|
65
|
+
def initialize(result)
|
66
|
+
@result = result
|
67
|
+
end
|
68
|
+
|
69
|
+
def tap_error
|
70
|
+
self # allow chaining
|
71
|
+
end
|
72
|
+
|
73
|
+
def tap_valid
|
74
|
+
yield @result
|
75
|
+
self # allow chaining
|
76
|
+
end
|
77
|
+
|
78
|
+
def if_valid
|
79
|
+
yield @result ## return this
|
80
|
+
end
|
81
|
+
|
82
|
+
def if_error
|
83
|
+
self # allow chaining
|
84
|
+
end
|
85
|
+
|
86
|
+
def if_valid_collect
|
87
|
+
yield(*@result) ## return this
|
88
|
+
end
|
89
|
+
|
90
|
+
def map_result!
|
91
|
+
@result = yield @result
|
92
|
+
self # allow chaining
|
93
|
+
end
|
94
|
+
|
95
|
+
def collect_result!
|
96
|
+
@result = yield(*@result)
|
97
|
+
self # allow chaining
|
98
|
+
end
|
99
|
+
|
100
|
+
def error
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def result
|
105
|
+
@result
|
106
|
+
end
|
107
|
+
end # class Valid
|
108
|
+
|
109
|
+
def Contract.valid(result)
|
110
|
+
Contract::Valid.new(result)
|
111
|
+
end
|
112
|
+
|
113
|
+
def Contract.and(*contracts)
|
114
|
+
# pick the first error if any
|
115
|
+
if (ff = contracts.find(&:error))
|
116
|
+
return ff
|
117
|
+
end
|
118
|
+
|
119
|
+
# return a new one with @result = list of the contracts's results
|
120
|
+
# usually this then be reduced again with #collect_result! or # #if_valid_collect methods
|
121
|
+
Contract.valid(contracts.map(&:result))
|
122
|
+
end
|
123
|
+
|
124
|
+
# shortcut for Contract.and(*contracts).collect_result!
|
125
|
+
def Contract.collect_result!(*contracts)
|
126
|
+
# pick the first error if any
|
127
|
+
if (ff = contracts.find(&:error))
|
128
|
+
return ff
|
129
|
+
end
|
130
|
+
|
131
|
+
# return a new one with @result = yield(*list of the contracts's results)
|
132
|
+
Contract.valid(yield(*contracts.map(&:result)))
|
133
|
+
end
|
134
|
+
|
135
|
+
# generic success return, when return value does not matter
|
136
|
+
# but usefull for control-flow
|
137
|
+
OK = Contract.valid(nil).freeze
|
138
|
+
# generic error return, when error value does not matter
|
139
|
+
# but usefull for control-flow
|
140
|
+
NOT_OK = Error.new.freeze
|
141
|
+
NOK = NOT_OK # it's shorter
|
142
|
+
end # Contract
|
143
|
+
end
|
data/lib/safrano/core.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# our main namespace
|
4
|
+
module Safrano
|
5
|
+
# frozen empty Array/Hash to reduce unncecessary object creation
|
6
|
+
EMPTY_ARRAY = [].freeze
|
7
|
+
EMPTY_HASH = {}.freeze
|
8
|
+
EMPTY_HASH_IN_ARY = [EMPTY_HASH].freeze
|
9
|
+
EMPTY_STRING = ''.freeze
|
10
|
+
ARY_204_EMPTY_HASH_ARY = [204, EMPTY_HASH, EMPTY_ARRAY].freeze
|
11
|
+
SPACE = ' '.freeze
|
12
|
+
COMMA = ','.freeze
|
13
|
+
|
14
|
+
# some prominent constants... probably already defined elsewhere eg in Rack
|
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
|
25
|
+
|
26
|
+
CT_JSON = { CONTENT_TYPE => APPJSON_UTF8 }.freeze
|
27
|
+
CT_TEXT = { CONTENT_TYPE => TEXTPLAIN_UTF8 }.freeze
|
28
|
+
CT_ATOMXML = { CONTENT_TYPE => APPATOMXML_UTF8 }.freeze
|
29
|
+
CT_APPXML = { CONTENT_TYPE => APPXML_UTF8 }.freeze
|
30
|
+
|
31
|
+
module NavigationInfo
|
32
|
+
attr_reader :nav_parent
|
33
|
+
attr_reader :navattr_reflection
|
34
|
+
attr_reader :nav_name
|
35
|
+
|
36
|
+
def set_relation_info(parent, name)
|
37
|
+
@nav_parent = parent
|
38
|
+
@nav_name = name
|
39
|
+
@navattr_reflection = parent.class.association_reflections[name.to_sym]
|
40
|
+
@nav_klass = @navattr_reflection[:class_name].constantize
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir.glob(File.expand_path('../core_ext/*.rb', __dir__)).sort.each do |path|
|
4
|
+
require path
|
5
|
+
end
|
6
|
+
|
7
|
+
# small helper method
|
8
|
+
# http://stackoverflow.com/
|
9
|
+
# questions/24980295/strictly-convert-string-to-integer-or-nil
|
10
|
+
def number_or_nil(str)
|
11
|
+
num = str.to_i
|
12
|
+
num if num.to_s == str
|
13
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
# shamelessly copied from Sequel...
|
4
|
+
|
5
|
+
module Safrano
|
6
|
+
# This module makes it easy to print deprecation warnings with optional backtraces to a given stream.
|
7
|
+
# There are a two accessors you can use to change how/where the deprecation methods are printed
|
8
|
+
# and whether/how backtraces should be included:
|
9
|
+
#
|
10
|
+
# Safrano::Deprecation.output = $stderr # print deprecation messages to standard error (default)
|
11
|
+
# Safrano::Deprecation.output = File.open('deprecated_calls.txt', 'wb') # use a file instead
|
12
|
+
# Safrano::Deprecation.output = false # do not output deprecation messages
|
13
|
+
#
|
14
|
+
# Safrano::Deprecation.prefix = "SAFRANO DEPRECATION WARNING: " # prefix deprecation messages with a given string (default)
|
15
|
+
# Safrano::Deprecation.prefix = false # do not prefix deprecation messages
|
16
|
+
#
|
17
|
+
# Safrano::Deprecation.backtrace_filter = false # don't include backtraces
|
18
|
+
# Safrano::Deprecation.backtrace_filter = true # include full backtraces
|
19
|
+
# Safrano::Deprecation.backtrace_filter = 2 # include 2 backtrace lines (default)
|
20
|
+
# Safrano::Deprecation.backtrace_filter = 1 # include 1 backtrace line
|
21
|
+
# Safrano::Deprecation.backtrace_filter = lambda{|line, line_no| line_no < 3 || line =~ /my_app/} # select backtrace lines to output
|
22
|
+
module Deprecation
|
23
|
+
@backtrace_filter = false
|
24
|
+
@output = $stderr
|
25
|
+
@prefix = "SAFRANO DEPRECATION WARNING: ".freeze
|
26
|
+
|
27
|
+
class << self
|
28
|
+
# How to filter backtraces. +false+ does not include backtraces, +true+ includes
|
29
|
+
# full backtraces, an Integer includes that number of backtrace lines, and
|
30
|
+
# a proc is called with the backtrace line and line number to select the backtrace
|
31
|
+
# lines to include. The default is no backtrace .
|
32
|
+
attr_accessor :backtrace_filter
|
33
|
+
|
34
|
+
# Where deprecation messages should be output, must respond to puts. $stderr by default.
|
35
|
+
attr_accessor :output
|
36
|
+
|
37
|
+
# Where deprecation messages should be prefixed with ("SEQUEL DEPRECATION WARNING: " by default).
|
38
|
+
attr_accessor :prefix
|
39
|
+
end
|
40
|
+
|
41
|
+
# Print the message and possibly backtrace to the output.
|
42
|
+
def self.deprecate(method, instead = nil)
|
43
|
+
return unless output
|
44
|
+
|
45
|
+
message = instead ? "#{method} is deprecated and will be removed in Safrano 0.6. #{instead}." : method
|
46
|
+
message = "#{prefix}#{message}" if prefix
|
47
|
+
output.puts(message)
|
48
|
+
case b = backtrace_filter
|
49
|
+
when Integer
|
50
|
+
caller.each do |c|
|
51
|
+
b -= 1
|
52
|
+
output.puts(c)
|
53
|
+
break if b <= 0
|
54
|
+
end
|
55
|
+
when true
|
56
|
+
caller.each { |c| output.puts(c) }
|
57
|
+
when Proc
|
58
|
+
caller.each_with_index { |line, line_no| output.puts(line) if b.call(line, line_no) }
|
59
|
+
end
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# If using ruby 2.3+, use Module#deprecate_constant to deprecate the constant,
|
64
|
+
# otherwise do nothing as the ruby implementation does not support constant deprecation.
|
65
|
+
def self.deprecate_constant(mod, constant)
|
66
|
+
# :nocov:
|
67
|
+
if RUBY_VERSION > '2.3'
|
68
|
+
# :nocov:
|
69
|
+
mod.deprecate_constant(constant)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -1,21 +1,25 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
CRLF = "\r\n"
|
4
|
+
LF = "\n"
|
3
5
|
|
4
6
|
require 'securerandom'
|
5
7
|
require 'webrick/httpstatus'
|
6
8
|
|
7
9
|
# Simple multipart support for OData $batch purpose
|
8
10
|
module MIME
|
11
|
+
CTT_TYPE_LC = 'content-type'
|
12
|
+
TEXT_PLAIN = 'text/plain'
|
13
|
+
|
9
14
|
# a mime object has a header(with content-type etc) and a content(aka body)
|
10
15
|
class Media
|
11
16
|
# Parser for MIME::Media
|
12
17
|
class Parser
|
13
18
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
14
19
|
|
15
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
16
|
-
|
17
20
|
attr_accessor :lines
|
18
21
|
attr_accessor :target
|
22
|
+
|
19
23
|
def initialize
|
20
24
|
@state = :h
|
21
25
|
@lines = []
|
@@ -54,9 +58,8 @@ module MIME
|
|
54
58
|
if (hmd = HMD_RGX.match(line))
|
55
59
|
@target_hd[hmd[1].downcase] = hmd[2].strip
|
56
60
|
|
57
|
-
# elsif CRLF_LINE_RGX =~ line
|
58
61
|
elsif CRLF == line
|
59
|
-
@target_ct = @target_hd[
|
62
|
+
@target_ct = @target_hd[CTT_TYPE_LC] || TEXT_PLAIN
|
60
63
|
@state = new_content
|
61
64
|
|
62
65
|
end
|
@@ -100,24 +103,22 @@ module MIME
|
|
100
103
|
end
|
101
104
|
|
102
105
|
def hook_multipart(content_type, boundary)
|
103
|
-
@target_hd[
|
104
|
-
@target_ct = @target_hd[
|
106
|
+
@target_hd[CTT_TYPE_LC] = content_type
|
107
|
+
@target_ct = @target_hd[CTT_TYPE_LC]
|
105
108
|
@target = multipart_content(boundary)
|
106
109
|
@target.hd = @target_hd
|
107
110
|
@target.ct = @target_ct
|
108
111
|
@state = :bmp
|
109
112
|
end
|
110
113
|
MPS = 'multipart/'.freeze
|
111
|
-
MP_RGX1 = %r{^(digest|mixed);\s*boundary
|
114
|
+
MP_RGX1 = %r{^(digest|mixed);\s*boundary="(.*)"}.freeze
|
112
115
|
MP_RGX2 = %r{^(digest|mixed);\s*boundary=(.*)}.freeze
|
113
116
|
# APP_HTTP_RGX = %r{^application/http}.freeze
|
114
117
|
APP_HTTP = 'application/http'.freeze
|
115
118
|
def new_content
|
116
119
|
@target =
|
117
|
-
if @target_ct.start_with?(MPS)
|
118
|
-
(md = (
|
119
|
-
(MP_RGX2.match(@target_ct[10..-1])))
|
120
|
-
)
|
120
|
+
if @target_ct.start_with?(MPS) &&
|
121
|
+
(md = (MP_RGX1.match(@target_ct[10..-1]) || MP_RGX2.match(@target_ct[10..-1])))
|
121
122
|
multipart_content(md[2].strip)
|
122
123
|
elsif @target_ct.start_with?(APP_HTTP)
|
123
124
|
MIME::Content::Application::Http.new
|
@@ -198,7 +199,6 @@ module MIME
|
|
198
199
|
# Parser for Text::Plain
|
199
200
|
class Parser
|
200
201
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
201
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
202
202
|
def initialize(target)
|
203
203
|
@state = :h
|
204
204
|
@lines = []
|
@@ -212,7 +212,6 @@ module MIME
|
|
212
212
|
def parse_head(line)
|
213
213
|
if (hmd = HMD_RGX.match(line))
|
214
214
|
@target.hd[hmd[1].downcase] = hmd[2].strip
|
215
|
-
# elsif CRLF_LINE_RGX =~ line
|
216
215
|
elsif CRLF == line
|
217
216
|
@state = :b
|
218
217
|
else
|
@@ -241,10 +240,11 @@ module MIME
|
|
241
240
|
|
242
241
|
def initialize
|
243
242
|
@hd = {}
|
244
|
-
|
243
|
+
# we need it unfrozen --> +
|
244
|
+
@content = +''
|
245
245
|
# set default values. Can be overwritten by parser
|
246
|
-
@hd[
|
247
|
-
@ct =
|
246
|
+
@hd[CTT_TYPE_LC] = TEXT_PLAIN
|
247
|
+
@ct = TEXT_PLAIN
|
248
248
|
@parser = Parser.new(self)
|
249
249
|
end
|
250
250
|
|
@@ -309,8 +309,10 @@ module MIME
|
|
309
309
|
# to remove it from the end of the last body line
|
310
310
|
return unless @body_lines
|
311
311
|
|
312
|
-
#
|
313
|
-
@body_lines.last.chomp!(CRLF)
|
312
|
+
# the last line ends up frozen --> chomp! fails
|
313
|
+
# @body_lines.last.chomp!(CRLF)
|
314
|
+
last_line = @body_lines.pop.chomp(CRLF)
|
315
|
+
@body_lines.push last_line
|
314
316
|
@parts << @body_lines
|
315
317
|
end
|
316
318
|
|
@@ -364,7 +366,7 @@ module MIME
|
|
364
366
|
end
|
365
367
|
|
366
368
|
def set_multipart_header
|
367
|
-
@hd[
|
369
|
+
@hd[CTT_TYPE_LC] = "#{Safrano::MP_MIXED}; boundary=#{@boundary}"
|
368
370
|
end
|
369
371
|
|
370
372
|
def get_http_resp(batcha)
|
@@ -382,9 +384,7 @@ module MIME
|
|
382
384
|
# of the changes
|
383
385
|
batcha.db.transaction do
|
384
386
|
begin
|
385
|
-
@response.content = @content.map { |part|
|
386
|
-
part.get_response(batcha)
|
387
|
-
}
|
387
|
+
@response.content = @content.map { |part| part.get_response(batcha) }
|
388
388
|
rescue Sequel::Rollback => e
|
389
389
|
# one of the changes of the changeset has failed
|
390
390
|
# --> provide a dummy empty response for the change-parts
|
@@ -405,18 +405,18 @@ module MIME
|
|
405
405
|
@response.content = [{ 'odata.error' =>
|
406
406
|
{ 'message' =>
|
407
407
|
'Bad Request: Failed changeset ' } }.to_json]
|
408
|
-
@response.hd =
|
408
|
+
@response.hd = Safrano::CT_JSON
|
409
409
|
@response
|
410
410
|
end
|
411
411
|
|
412
412
|
def unparse(bodyonly = false)
|
413
|
-
b =
|
413
|
+
b = +String.new
|
414
414
|
unless bodyonly
|
415
|
-
|
416
|
-
b << "#{OData::CONTENT_TYPE}: #{@hd[OData::CTT_TYPE_LC]}#{CRLF}"
|
415
|
+
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
417
416
|
end
|
418
|
-
|
419
|
-
b <<
|
417
|
+
|
418
|
+
b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
|
419
|
+
b << @content.map(&:unparse).join(crbdcr)
|
420
420
|
b << "#{CRLF}--#{@boundary}--#{CRLF}"
|
421
421
|
b
|
422
422
|
end
|
@@ -436,15 +436,14 @@ module MIME
|
|
436
436
|
|
437
437
|
def initialize
|
438
438
|
@hd = {}
|
439
|
-
@content =
|
439
|
+
@content = +String.new
|
440
440
|
end
|
441
441
|
|
442
442
|
def unparse
|
443
|
-
b = "#{@http_method} #{@uri} HTTP/1.1#{CRLF}"
|
443
|
+
b = +"#{@http_method} #{@uri} HTTP/1.1#{CRLF}"
|
444
444
|
@hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
|
445
|
-
# @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
|
446
445
|
b << CRLF
|
447
|
-
b << @content
|
446
|
+
b << @content unless content.empty?
|
448
447
|
b
|
449
448
|
end
|
450
449
|
end
|
@@ -470,10 +469,9 @@ module MIME
|
|
470
469
|
end
|
471
470
|
|
472
471
|
def unparse
|
473
|
-
b = String.new(APPLICATION_HTTP_11)
|
472
|
+
b = +String.new(APPLICATION_HTTP_11)
|
474
473
|
b << "#{@status} #{StatusMessage[@status]} #{CRLF}"
|
475
474
|
@hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
|
476
|
-
# @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
|
477
475
|
b << CRLF
|
478
476
|
b << @content.join if @content
|
479
477
|
b
|
@@ -482,15 +480,14 @@ module MIME
|
|
482
480
|
|
483
481
|
# For application/http . Content is either a Request or a Response
|
484
482
|
class Http < Media
|
485
|
-
HTTP_MTHS_RGX = 'POST|GET|PUT|MERGE|PATCH|DELETE'
|
483
|
+
HTTP_MTHS_RGX = 'POST|GET|PUT|MERGE|PATCH|DELETE'
|
486
484
|
HTTP_R_RGX = %r{^(#{HTTP_MTHS_RGX})\s+(\S*)\s?(HTTP/1\.1)\s*$}.freeze
|
487
|
-
HEADER_RGX = %r{^([a-zA-Z\-]+):\s*([0-9a-zA-Z
|
485
|
+
HEADER_RGX = %r{^([a-zA-Z\-]+):\s*([0-9a-zA-Z\-/,\s]+;?\S*)\s*$}.freeze
|
488
486
|
HTTP_RESP_RGX = %r{^HTTP/1\.1\s(\d+)\s}.freeze
|
489
487
|
|
490
488
|
# Parser for Http Media
|
491
489
|
class Parser
|
492
490
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
493
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
494
491
|
|
495
492
|
def initialize(target)
|
496
493
|
@state = :http
|
@@ -523,7 +520,6 @@ module MIME
|
|
523
520
|
if (hmd = HMD_RGX.match(line))
|
524
521
|
@target.content.hd[hmd[1].downcase] = hmd[2].strip
|
525
522
|
elsif CRLF == line
|
526
|
-
# elsif CRLF_LINE_RGX =~ line
|
527
523
|
@state = :b
|
528
524
|
else
|
529
525
|
@body_lines << line
|
@@ -575,7 +571,7 @@ module MIME
|
|
575
571
|
end
|
576
572
|
|
577
573
|
def unparse
|
578
|
-
b = "Content-Type: #{@ct}#{CRLF}"
|
574
|
+
b = +"Content-Type: #{@ct}#{CRLF}"
|
579
575
|
b << "Content-Transfer-Encoding: binary#{CRLF}#{CRLF}"
|
580
576
|
b << @content.unparse
|
581
577
|
b
|