safrano 0.4.2 → 0.5.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 +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 +9 -7
- data/lib/odata/collection.rb +140 -591
- 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 +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +123 -172
- data/lib/odata/error.rb +183 -32
- data/lib/odata/expand.rb +20 -17
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +41 -25
- data/lib/odata/filter/sequel.rb +133 -62
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +106 -52
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +639 -0
- data/lib/odata/navigation_attribute.rb +13 -26
- 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 +20 -10
- data/lib/safrano.rb +18 -38
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +23 -107
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +29 -33
- data/lib/safrano/rack_app.rb +66 -65
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +96 -45
- data/lib/safrano/response.rb +4 -2
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +240 -130
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +6 -19
- metadata +32 -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,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 self.valid(result)
|
110
|
+
Contract::Valid.new(result)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.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
|
+
valid(contracts.map(&:result))
|
122
|
+
end
|
123
|
+
|
124
|
+
# shortcut for Contract.and(*contracts).collect_result!
|
125
|
+
def self.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
|
+
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
CHANGED
@@ -1,127 +1,43 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# our main namespace
|
4
|
-
module
|
4
|
+
module Safrano
|
5
5
|
# frozen empty Array/Hash to reduce unncecessary object creation
|
6
6
|
EMPTY_ARRAY = [].freeze
|
7
7
|
EMPTY_HASH = {}.freeze
|
8
8
|
EMPTY_HASH_IN_ARY = [EMPTY_HASH].freeze
|
9
|
-
EMPTY_STRING = ''
|
9
|
+
EMPTY_STRING = ''
|
10
10
|
ARY_204_EMPTY_HASH_ARY = [204, EMPTY_HASH, EMPTY_ARRAY].freeze
|
11
|
-
SPACE = ' '
|
12
|
-
COMMA = ','
|
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'
|
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'
|
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
|
28
28
|
CT_ATOMXML = { CONTENT_TYPE => APPATOMXML_UTF8 }.freeze
|
29
29
|
CT_APPXML = { CONTENT_TYPE => APPXML_UTF8 }.freeze
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# TODO: use Sequel GENERIC_TYPES: -->
|
37
|
-
# Constants
|
38
|
-
# GENERIC_TYPES = %w'String Integer Float Numeric BigDecimal Date DateTime
|
39
|
-
# Time File TrueClass FalseClass'.freeze
|
40
|
-
# Classes specifying generic types that Sequel will convert to
|
41
|
-
# database-specific types.
|
42
|
-
DB_TYPE_STRING_RGX = /\ACHAR\s*\(\d+\)\z/.freeze
|
43
|
-
|
44
|
-
# TODO... complete; used in $metadata
|
45
|
-
def self.get_edm_type(db_type:)
|
46
|
-
case db_type.upcase
|
47
|
-
when 'INTEGER'
|
48
|
-
'Edm.Int32'
|
49
|
-
when 'TEXT', 'STRING'
|
50
|
-
'Edm.String'
|
51
|
-
else
|
52
|
-
'Edm.String' if DB_TYPE_STRING_RGX =~ db_type.upcase
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
module REXML
|
58
|
-
# some small extensions
|
59
|
-
class Document
|
60
|
-
def to_pretty_xml
|
61
|
-
formatter = REXML::Formatters::Pretty.new(2)
|
62
|
-
formatter.compact = true
|
63
|
-
strio = ''
|
64
|
-
formatter.write(root, strio)
|
65
|
-
strio
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Core
|
71
|
-
module Safrano
|
72
|
-
# represents a state transition when navigating/parsing the url path
|
73
|
-
# from left to right
|
74
|
-
class Transition < Regexp
|
75
|
-
attr_accessor :trans
|
76
|
-
attr_accessor :match_result
|
77
|
-
attr_accessor :rgx
|
78
|
-
attr_reader :remain_idx
|
79
|
-
def initialize(arg, trans: nil, remain_idx: 2)
|
80
|
-
@rgx = if arg.respond_to? :each_char
|
81
|
-
Regexp.new(arg)
|
82
|
-
else
|
83
|
-
arg
|
84
|
-
end
|
85
|
-
@trans = trans
|
86
|
-
@remain_idx = remain_idx
|
87
|
-
end
|
88
|
-
|
89
|
-
def do_match(str)
|
90
|
-
@match_result = @rgx.match(str)
|
91
|
-
end
|
92
|
-
|
93
|
-
# remain_idx is the index of the last match-data. ususally its 2
|
94
|
-
# but can be overidden
|
95
|
-
def path_remain
|
96
|
-
@match_result[@remain_idx] if @match_result && @match_result[@remain_idx]
|
97
|
-
end
|
31
|
+
module NavigationInfo
|
32
|
+
attr_reader :nav_parent
|
33
|
+
attr_reader :navattr_reflection
|
34
|
+
attr_reader :nav_name
|
98
35
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def do_transition(ctx)
|
108
|
-
ctx.method(@trans).call(@match_result)
|
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
|
109
41
|
end
|
110
42
|
end
|
111
|
-
|
112
|
-
TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
|
113
|
-
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
114
|
-
trans: 'transition_metadata')
|
115
|
-
TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
|
116
|
-
trans: 'transition_batch')
|
117
|
-
TransitionContentId = Transition.new('\A(\/\$(\d+))(.*)',
|
118
|
-
trans: 'transition_content_id',
|
119
|
-
remain_idx: 3)
|
120
|
-
TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
|
121
|
-
trans: 'transition_count')
|
122
|
-
TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
|
123
|
-
trans: 'transition_value')
|
124
|
-
TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
|
125
|
-
trans: 'transition_links')
|
126
|
-
attr_accessor :allowed_transitions
|
127
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: '
|
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
|
@@ -15,10 +17,9 @@ module MIME
|
|
15
17
|
class Parser
|
16
18
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
17
19
|
|
18
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
19
|
-
|
20
20
|
attr_accessor :lines
|
21
21
|
attr_accessor :target
|
22
|
+
|
22
23
|
def initialize
|
23
24
|
@state = :h
|
24
25
|
@lines = []
|
@@ -57,7 +58,6 @@ module MIME
|
|
57
58
|
if (hmd = HMD_RGX.match(line))
|
58
59
|
@target_hd[hmd[1].downcase] = hmd[2].strip
|
59
60
|
|
60
|
-
# elsif CRLF_LINE_RGX =~ line
|
61
61
|
elsif CRLF == line
|
62
62
|
@target_ct = @target_hd[CTT_TYPE_LC] || TEXT_PLAIN
|
63
63
|
@state = new_content
|
@@ -110,11 +110,11 @@ module MIME
|
|
110
110
|
@target.ct = @target_ct
|
111
111
|
@state = :bmp
|
112
112
|
end
|
113
|
-
MPS = 'multipart/'
|
114
|
-
MP_RGX1 = %r{^(digest|mixed);\s*boundary
|
113
|
+
MPS = 'multipart/'
|
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'
|
117
|
+
APP_HTTP = 'application/http'
|
118
118
|
def new_content
|
119
119
|
@target =
|
120
120
|
if @target_ct.start_with?(MPS) &&
|
@@ -199,7 +199,6 @@ module MIME
|
|
199
199
|
# Parser for Text::Plain
|
200
200
|
class Parser
|
201
201
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
202
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
203
202
|
def initialize(target)
|
204
203
|
@state = :h
|
205
204
|
@lines = []
|
@@ -213,7 +212,6 @@ module MIME
|
|
213
212
|
def parse_head(line)
|
214
213
|
if (hmd = HMD_RGX.match(line))
|
215
214
|
@target.hd[hmd[1].downcase] = hmd[2].strip
|
216
|
-
# elsif CRLF_LINE_RGX =~ line
|
217
215
|
elsif CRLF == line
|
218
216
|
@state = :b
|
219
217
|
else
|
@@ -242,7 +240,8 @@ module MIME
|
|
242
240
|
|
243
241
|
def initialize
|
244
242
|
@hd = {}
|
245
|
-
|
243
|
+
# we need it unfrozen --> +
|
244
|
+
@content = +''
|
246
245
|
# set default values. Can be overwritten by parser
|
247
246
|
@hd[CTT_TYPE_LC] = TEXT_PLAIN
|
248
247
|
@ct = TEXT_PLAIN
|
@@ -310,8 +309,10 @@ module MIME
|
|
310
309
|
# to remove it from the end of the last body line
|
311
310
|
return unless @body_lines
|
312
311
|
|
313
|
-
#
|
314
|
-
@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
|
315
316
|
@parts << @body_lines
|
316
317
|
end
|
317
318
|
|
@@ -365,7 +366,7 @@ module MIME
|
|
365
366
|
end
|
366
367
|
|
367
368
|
def set_multipart_header
|
368
|
-
@hd[CTT_TYPE_LC] = "#{
|
369
|
+
@hd[CTT_TYPE_LC] = "#{Safrano::MP_MIXED}; boundary=#{@boundary}"
|
369
370
|
end
|
370
371
|
|
371
372
|
def get_http_resp(batcha)
|
@@ -404,15 +405,14 @@ module MIME
|
|
404
405
|
@response.content = [{ 'odata.error' =>
|
405
406
|
{ 'message' =>
|
406
407
|
'Bad Request: Failed changeset ' } }.to_json]
|
407
|
-
@response.hd =
|
408
|
+
@response.hd = Safrano::CT_JSON
|
408
409
|
@response
|
409
410
|
end
|
410
411
|
|
411
412
|
def unparse(bodyonly = false)
|
412
|
-
b =
|
413
|
+
b = +String.new
|
413
414
|
unless bodyonly
|
414
|
-
|
415
|
-
b << "#{OData::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
415
|
+
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
416
416
|
end
|
417
417
|
|
418
418
|
b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
|
@@ -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
|
@@ -458,7 +457,7 @@ module MIME
|
|
458
457
|
"Content-Transfer-Encoding: binary#{CRLF}",
|
459
458
|
'HTTP/1.1 '].join(CRLF).freeze
|
460
459
|
|
461
|
-
StatusMessage = ::
|
460
|
+
StatusMessage = ::Rack::Utils::HTTP_STATUS_CODES.freeze
|
462
461
|
|
463
462
|
def initialize
|
464
463
|
@hd = {}
|
@@ -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
|