safrano 0.4.3 → 0.5.1
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 +8 -4
- data/lib/odata/batch.rb +9 -7
- data/lib/odata/collection.rb +139 -642
- data/lib/odata/collection_filter.rb +18 -42
- data/lib/odata/collection_media.rb +111 -54
- data/lib/odata/collection_order.rb +5 -2
- data/lib/odata/common_logger.rb +2 -0
- data/lib/odata/complex_type.rb +196 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +78 -123
- data/lib/odata/error.rb +170 -37
- data/lib/odata/expand.rb +20 -17
- data/lib/odata/filter/base.rb +9 -1
- data/lib/odata/filter/error.rb +43 -27
- data/lib/odata/filter/parse.rb +39 -25
- data/lib/odata/filter/sequel.rb +112 -56
- data/lib/odata/filter/sequel_function_adapter.rb +50 -49
- data/lib/odata/filter/token.rb +21 -18
- data/lib/odata/filter/tree.rb +78 -44
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +641 -0
- data/lib/odata/navigation_attribute.rb +9 -24
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +17 -5
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +100 -24
- data/lib/odata/walker.rb +18 -10
- data/lib/safrano.rb +18 -38
- data/lib/safrano/contract.rb +141 -0
- data/lib/safrano/core.rb +24 -106
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +29 -24
- data/lib/safrano/rack_app.rb +62 -63
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -1
- data/lib/safrano/request.rb +96 -38
- data/lib/safrano/response.rb +4 -2
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +156 -110
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +6 -19
- metadata +30 -11
data/lib/safrano.rb
CHANGED
@@ -1,42 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'rexml/document'
|
3
|
-
require_relative 'safrano/
|
4
|
-
require_relative 'safrano/
|
5
|
-
require_relative '
|
6
|
-
require_relative '
|
7
|
-
require_relative '
|
8
|
-
require_relative '
|
9
|
-
require_relative '
|
10
|
-
require_relative 'odata/
|
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'
|
11
19
|
require 'sequel'
|
12
|
-
require_relative 'safrano/sequel_join_by_paths
|
20
|
+
require_relative 'safrano/sequel_join_by_paths'
|
13
21
|
require_relative 'safrano/rack_app'
|
14
|
-
require_relative 'safrano/
|
15
|
-
require_relative 'safrano/version'
|
16
|
-
|
17
|
-
# picked from activsupport; needed for ruby < 2.5
|
18
|
-
# Destructively converts all keys using the +block+ operations.
|
19
|
-
# Same as +transform_keys+ but modifies +self+.
|
20
|
-
class Hash
|
21
|
-
def transform_keys!
|
22
|
-
keys.each do |key|
|
23
|
-
self[yield(key)] = delete(key)
|
24
|
-
end
|
25
|
-
self
|
26
|
-
end unless method_defined? :transform_keys!
|
27
|
-
|
28
|
-
def symbolize_keys!
|
29
|
-
transform_keys! { |key| key.to_sym rescue key }
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# needed for ruby < 2.5
|
34
|
-
class Dir
|
35
|
-
def self.each_child(dir)
|
36
|
-
Dir.foreach(dir) do |x|
|
37
|
-
next if (x == '.') || (x == '..')
|
38
|
-
|
39
|
-
yield x
|
40
|
-
end
|
41
|
-
end unless respond_to? :each_child
|
42
|
-
end
|
22
|
+
require_relative 'safrano/rack_builder'
|
@@ -0,0 +1,141 @@
|
|
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
|
+
attr_reader :result
|
66
|
+
|
67
|
+
def initialize(result)
|
68
|
+
@result = result
|
69
|
+
end
|
70
|
+
|
71
|
+
def tap_error
|
72
|
+
self # allow chaining
|
73
|
+
end
|
74
|
+
|
75
|
+
def tap_valid
|
76
|
+
yield @result
|
77
|
+
self # allow chaining
|
78
|
+
end
|
79
|
+
|
80
|
+
def if_valid
|
81
|
+
yield @result ## return this
|
82
|
+
end
|
83
|
+
|
84
|
+
def if_error
|
85
|
+
self # allow chaining
|
86
|
+
end
|
87
|
+
|
88
|
+
def if_valid_collect
|
89
|
+
yield(*@result) ## return this
|
90
|
+
end
|
91
|
+
|
92
|
+
def map_result!
|
93
|
+
@result = yield @result
|
94
|
+
self # allow chaining
|
95
|
+
end
|
96
|
+
|
97
|
+
def collect_result!
|
98
|
+
@result = yield(*@result)
|
99
|
+
self # allow chaining
|
100
|
+
end
|
101
|
+
|
102
|
+
def error
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
end # class Valid
|
106
|
+
|
107
|
+
def self.valid(result)
|
108
|
+
Contract::Valid.new(result)
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.and(*contracts)
|
112
|
+
# pick the first error if any
|
113
|
+
if (ff = contracts.find(&:error))
|
114
|
+
return ff
|
115
|
+
end
|
116
|
+
|
117
|
+
# return a new one with @result = list of the contracts's results
|
118
|
+
# usually this then be reduced again with #collect_result! or # #if_valid_collect methods
|
119
|
+
valid(contracts.map(&:result))
|
120
|
+
end
|
121
|
+
|
122
|
+
# shortcut for Contract.and(*contracts).collect_result!
|
123
|
+
def self.collect_result!(*contracts)
|
124
|
+
# pick the first error if any
|
125
|
+
if (ff = contracts.find(&:error))
|
126
|
+
return ff
|
127
|
+
end
|
128
|
+
|
129
|
+
# return a new one with @result = yield(*list of the contracts's results)
|
130
|
+
valid(yield(*contracts.map(&:result)))
|
131
|
+
end
|
132
|
+
|
133
|
+
# generic success return, when return value does not matter
|
134
|
+
# but usefull for control-flow
|
135
|
+
OK = Contract.valid(nil).freeze
|
136
|
+
# generic error return, when error value does not matter
|
137
|
+
# but usefull for control-flow
|
138
|
+
NOT_OK = Error.new.freeze
|
139
|
+
NOK = NOT_OK # it's shorter
|
140
|
+
end # Contract
|
141
|
+
end
|
data/lib/safrano/core.rb
CHANGED
@@ -1,125 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# our main namespace
|
2
|
-
module
|
4
|
+
module Safrano
|
3
5
|
# frozen empty Array/Hash to reduce unncecessary object creation
|
4
6
|
EMPTY_ARRAY = [].freeze
|
5
7
|
EMPTY_HASH = {}.freeze
|
6
8
|
EMPTY_HASH_IN_ARY = [EMPTY_HASH].freeze
|
7
|
-
EMPTY_STRING = ''
|
9
|
+
EMPTY_STRING = ''
|
8
10
|
ARY_204_EMPTY_HASH_ARY = [204, EMPTY_HASH, EMPTY_ARRAY].freeze
|
9
|
-
SPACE = ' '
|
10
|
-
COMMA = ','
|
11
|
+
SPACE = ' '
|
12
|
+
COMMA = ','
|
11
13
|
|
12
14
|
# some prominent constants... probably already defined elsewhere eg in Rack
|
13
15
|
# but lets KISS
|
14
|
-
CONTENT_TYPE = 'Content-Type'
|
15
|
-
CTT_TYPE_LC = 'content-type'
|
16
|
-
TEXTPLAIN_UTF8 = 'text/plain;charset=utf-8'
|
17
|
-
APPJSON = 'application/json'
|
18
|
-
APPXML = 'application/xml'
|
19
|
-
MP_MIXED = 'multipart/mixed'
|
20
|
-
APPXML_UTF8 = 'application/xml;charset=utf-8'
|
21
|
-
APPATOMXML_UTF8 = 'application/atomsvc+xml;charset=utf-8'
|
22
|
-
APPJSON_UTF8 = 'application/json;charset=utf-8'
|
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'
|
23
25
|
|
24
26
|
CT_JSON = { CONTENT_TYPE => APPJSON_UTF8 }.freeze
|
25
27
|
CT_TEXT = { CONTENT_TYPE => TEXTPLAIN_UTF8 }.freeze
|
26
28
|
CT_ATOMXML = { CONTENT_TYPE => APPATOMXML_UTF8 }.freeze
|
27
29
|
CT_APPXML = { CONTENT_TYPE => APPXML_UTF8 }.freeze
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# TODO: use Sequel GENERIC_TYPES: -->
|
35
|
-
# Constants
|
36
|
-
# GENERIC_TYPES = %w'String Integer Float Numeric BigDecimal Date DateTime
|
37
|
-
# Time File TrueClass FalseClass'.freeze
|
38
|
-
# Classes specifying generic types that Sequel will convert to
|
39
|
-
# database-specific types.
|
40
|
-
DB_TYPE_STRING_RGX = /\ACHAR\s*\(\d+\)\z/.freeze
|
31
|
+
module NavigationInfo
|
32
|
+
attr_reader :nav_parent
|
33
|
+
attr_reader :navattr_reflection
|
34
|
+
attr_reader :nav_name
|
41
35
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
when 'TEXT', 'STRING'
|
48
|
-
'Edm.String'
|
49
|
-
else
|
50
|
-
'Edm.String' if DB_TYPE_STRING_RGX =~ db_type.upcase
|
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
|
51
41
|
end
|
52
42
|
end
|
53
43
|
end
|
54
|
-
|
55
|
-
module REXML
|
56
|
-
# some small extensions
|
57
|
-
class Document
|
58
|
-
def to_pretty_xml
|
59
|
-
formatter = REXML::Formatters::Pretty.new(2)
|
60
|
-
formatter.compact = true
|
61
|
-
strio = ''
|
62
|
-
formatter.write(root, strio)
|
63
|
-
strio
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Core
|
69
|
-
module Safrano
|
70
|
-
# represents a state transition when navigating/parsing the url path
|
71
|
-
# from left to right
|
72
|
-
class Transition < Regexp
|
73
|
-
attr_accessor :trans
|
74
|
-
attr_accessor :match_result
|
75
|
-
attr_accessor :rgx
|
76
|
-
attr_reader :remain_idx
|
77
|
-
def initialize(arg, trans: nil, remain_idx: 2)
|
78
|
-
@rgx = if arg.respond_to? :each_char
|
79
|
-
Regexp.new(arg)
|
80
|
-
else
|
81
|
-
arg
|
82
|
-
end
|
83
|
-
@trans = trans
|
84
|
-
@remain_idx = remain_idx
|
85
|
-
end
|
86
|
-
|
87
|
-
def do_match(str)
|
88
|
-
@match_result = @rgx.match(str)
|
89
|
-
end
|
90
|
-
|
91
|
-
# remain_idx is the index of the last match-data. ususally its 2
|
92
|
-
# but can be overidden
|
93
|
-
def path_remain
|
94
|
-
@match_result[@remain_idx] if @match_result && @match_result[@remain_idx]
|
95
|
-
end
|
96
|
-
|
97
|
-
def path_done
|
98
|
-
if @match_result
|
99
|
-
@match_result[1] || ''
|
100
|
-
else
|
101
|
-
''
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def do_transition(ctx)
|
106
|
-
ctx.method(@trans).call(@match_result)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
|
111
|
-
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
112
|
-
trans: 'transition_metadata')
|
113
|
-
TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
|
114
|
-
trans: 'transition_batch')
|
115
|
-
TransitionContentId = Transition.new('\A(\/\$(\d+))(.*)',
|
116
|
-
trans: 'transition_content_id',
|
117
|
-
remain_idx: 3)
|
118
|
-
TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
|
119
|
-
trans: 'transition_count')
|
120
|
-
TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
|
121
|
-
trans: 'transition_value')
|
122
|
-
TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
|
123
|
-
trans: 'transition_links')
|
124
|
-
attr_accessor :allowed_transitions
|
125
|
-
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: '
|
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
|
+
return unless RUBY_VERSION > '2.3'
|
68
|
+
|
69
|
+
# :nocov:
|
70
|
+
mod.deprecate_constant(constant)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/safrano/multipart.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
CRLF = "\r\n"
|
4
|
+
LF = "\n"
|
3
5
|
|
4
6
|
require 'securerandom'
|
5
|
-
require '
|
7
|
+
require 'rack/utils'
|
6
8
|
|
7
9
|
# Simple multipart support for OData $batch purpose
|
8
10
|
module MIME
|
9
|
-
CTT_TYPE_LC = 'content-type'
|
10
|
-
TEXT_PLAIN = 'text/plain'
|
11
|
+
CTT_TYPE_LC = 'content-type'
|
12
|
+
TEXT_PLAIN = 'text/plain'
|
11
13
|
|
12
14
|
# a mime object has a header(with content-type etc) and a content(aka body)
|
13
15
|
class Media
|
@@ -17,6 +19,7 @@ module MIME
|
|
17
19
|
|
18
20
|
attr_accessor :lines
|
19
21
|
attr_accessor :target
|
22
|
+
|
20
23
|
def initialize
|
21
24
|
@state = :h
|
22
25
|
@lines = []
|
@@ -107,11 +110,11 @@ module MIME
|
|
107
110
|
@target.ct = @target_ct
|
108
111
|
@state = :bmp
|
109
112
|
end
|
110
|
-
MPS = 'multipart/'
|
111
|
-
MP_RGX1 = %r{^(digest|mixed);\s*boundary
|
113
|
+
MPS = 'multipart/'
|
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
|
-
APP_HTTP = 'application/http'
|
117
|
+
APP_HTTP = 'application/http'
|
115
118
|
def new_content
|
116
119
|
@target =
|
117
120
|
if @target_ct.start_with?(MPS) &&
|
@@ -237,7 +240,8 @@ module MIME
|
|
237
240
|
|
238
241
|
def initialize
|
239
242
|
@hd = {}
|
240
|
-
|
243
|
+
# we need it unfrozen --> +
|
244
|
+
@content = +''
|
241
245
|
# set default values. Can be overwritten by parser
|
242
246
|
@hd[CTT_TYPE_LC] = TEXT_PLAIN
|
243
247
|
@ct = TEXT_PLAIN
|
@@ -305,8 +309,10 @@ module MIME
|
|
305
309
|
# to remove it from the end of the last body line
|
306
310
|
return unless @body_lines
|
307
311
|
|
308
|
-
#
|
309
|
-
@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
|
310
316
|
@parts << @body_lines
|
311
317
|
end
|
312
318
|
|
@@ -360,7 +366,7 @@ module MIME
|
|
360
366
|
end
|
361
367
|
|
362
368
|
def set_multipart_header
|
363
|
-
@hd[CTT_TYPE_LC] = "#{
|
369
|
+
@hd[CTT_TYPE_LC] = "#{Safrano::MP_MIXED}; boundary=#{@boundary}"
|
364
370
|
end
|
365
371
|
|
366
372
|
def get_http_resp(batcha)
|
@@ -399,15 +405,14 @@ module MIME
|
|
399
405
|
@response.content = [{ 'odata.error' =>
|
400
406
|
{ 'message' =>
|
401
407
|
'Bad Request: Failed changeset ' } }.to_json]
|
402
|
-
@response.hd =
|
408
|
+
@response.hd = Safrano::CT_JSON
|
403
409
|
@response
|
404
410
|
end
|
405
411
|
|
406
412
|
def unparse(bodyonly = false)
|
407
|
-
b =
|
413
|
+
b = +String.new
|
408
414
|
unless bodyonly
|
409
|
-
|
410
|
-
b << "#{OData::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
415
|
+
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
411
416
|
end
|
412
417
|
|
413
418
|
b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
|
@@ -431,14 +436,14 @@ module MIME
|
|
431
436
|
|
432
437
|
def initialize
|
433
438
|
@hd = {}
|
434
|
-
@content =
|
439
|
+
@content = +String.new
|
435
440
|
end
|
436
441
|
|
437
442
|
def unparse
|
438
|
-
b = "#{@http_method} #{@uri} HTTP/1.1#{CRLF}"
|
443
|
+
b = +"#{@http_method} #{@uri} HTTP/1.1#{CRLF}"
|
439
444
|
@hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
|
440
445
|
b << CRLF
|
441
|
-
b << @content
|
446
|
+
b << @content unless content.empty?
|
442
447
|
b
|
443
448
|
end
|
444
449
|
end
|
@@ -452,7 +457,7 @@ module MIME
|
|
452
457
|
"Content-Transfer-Encoding: binary#{CRLF}",
|
453
458
|
'HTTP/1.1 '].join(CRLF).freeze
|
454
459
|
|
455
|
-
StatusMessage = ::
|
460
|
+
StatusMessage = ::Rack::Utils::HTTP_STATUS_CODES.freeze
|
456
461
|
|
457
462
|
def initialize
|
458
463
|
@hd = {}
|
@@ -464,7 +469,7 @@ module MIME
|
|
464
469
|
end
|
465
470
|
|
466
471
|
def unparse
|
467
|
-
b = String.new(APPLICATION_HTTP_11)
|
472
|
+
b = +String.new(APPLICATION_HTTP_11)
|
468
473
|
b << "#{@status} #{StatusMessage[@status]} #{CRLF}"
|
469
474
|
@hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
|
470
475
|
b << CRLF
|
@@ -475,9 +480,9 @@ module MIME
|
|
475
480
|
|
476
481
|
# For application/http . Content is either a Request or a Response
|
477
482
|
class Http < Media
|
478
|
-
HTTP_MTHS_RGX = 'POST|GET|PUT|MERGE|PATCH|DELETE'
|
483
|
+
HTTP_MTHS_RGX = 'POST|GET|PUT|MERGE|PATCH|DELETE'
|
479
484
|
HTTP_R_RGX = %r{^(#{HTTP_MTHS_RGX})\s+(\S*)\s?(HTTP/1\.1)\s*$}.freeze
|
480
|
-
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
|
481
486
|
HTTP_RESP_RGX = %r{^HTTP/1\.1\s(\d+)\s}.freeze
|
482
487
|
|
483
488
|
# Parser for Http Media
|
@@ -566,7 +571,7 @@ module MIME
|
|
566
571
|
end
|
567
572
|
|
568
573
|
def unparse
|
569
|
-
b = "Content-Type: #{@ct}#{CRLF}"
|
574
|
+
b = +"Content-Type: #{@ct}#{CRLF}"
|
570
575
|
b << "Content-Transfer-Encoding: binary#{CRLF}#{CRLF}"
|
571
576
|
b << @content.unparse
|
572
577
|
b
|