safrano 0.4.3 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Dir/iter.rb +18 -0
  3. data/lib/core_ext/Hash/transform.rb +21 -0
  4. data/lib/core_ext/Integer/edm.rb +13 -0
  5. data/lib/core_ext/REXML/Document/output.rb +16 -0
  6. data/lib/core_ext/String/convert.rb +25 -0
  7. data/lib/core_ext/String/edm.rb +13 -0
  8. data/lib/core_ext/dir.rb +3 -0
  9. data/lib/core_ext/hash.rb +3 -0
  10. data/lib/core_ext/integer.rb +3 -0
  11. data/lib/core_ext/rexml.rb +3 -0
  12. data/lib/core_ext/string.rb +5 -0
  13. data/lib/odata/attribute.rb +8 -4
  14. data/lib/odata/batch.rb +9 -7
  15. data/lib/odata/collection.rb +139 -642
  16. data/lib/odata/collection_filter.rb +18 -42
  17. data/lib/odata/collection_media.rb +111 -54
  18. data/lib/odata/collection_order.rb +5 -2
  19. data/lib/odata/common_logger.rb +2 -0
  20. data/lib/odata/complex_type.rb +196 -0
  21. data/lib/odata/edm/primitive_types.rb +184 -0
  22. data/lib/odata/entity.rb +78 -123
  23. data/lib/odata/error.rb +170 -37
  24. data/lib/odata/expand.rb +20 -17
  25. data/lib/odata/filter/base.rb +9 -1
  26. data/lib/odata/filter/error.rb +43 -27
  27. data/lib/odata/filter/parse.rb +39 -25
  28. data/lib/odata/filter/sequel.rb +112 -56
  29. data/lib/odata/filter/sequel_function_adapter.rb +50 -49
  30. data/lib/odata/filter/token.rb +21 -18
  31. data/lib/odata/filter/tree.rb +78 -44
  32. data/lib/odata/function_import.rb +168 -0
  33. data/lib/odata/model_ext.rb +641 -0
  34. data/lib/odata/navigation_attribute.rb +9 -24
  35. data/lib/odata/relations.rb +5 -5
  36. data/lib/odata/select.rb +17 -5
  37. data/lib/odata/transition.rb +71 -0
  38. data/lib/odata/url_parameters.rb +100 -24
  39. data/lib/odata/walker.rb +18 -10
  40. data/lib/safrano.rb +18 -38
  41. data/lib/safrano/contract.rb +141 -0
  42. data/lib/safrano/core.rb +24 -106
  43. data/lib/safrano/core_ext.rb +13 -0
  44. data/lib/safrano/deprecation.rb +73 -0
  45. data/lib/safrano/multipart.rb +29 -24
  46. data/lib/safrano/rack_app.rb +62 -63
  47. data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -1
  48. data/lib/safrano/request.rb +96 -38
  49. data/lib/safrano/response.rb +4 -2
  50. data/lib/safrano/sequel_join_by_paths.rb +2 -2
  51. data/lib/safrano/service.rb +156 -110
  52. data/lib/safrano/version.rb +3 -1
  53. data/lib/sequel/plugins/join_by_paths.rb +6 -19
  54. 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/multipart.rb'
4
- require_relative 'safrano/core.rb'
5
- require_relative 'odata/entity.rb'
6
- require_relative 'odata/attribute.rb'
7
- require_relative 'odata/navigation_attribute.rb'
8
- require_relative 'odata/collection.rb'
9
- require_relative 'safrano/service.rb'
10
- require_relative 'odata/walker.rb'
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.rb'
20
+ require_relative 'safrano/sequel_join_by_paths'
13
21
  require_relative 'safrano/rack_app'
14
- require_relative 'safrano/odata_rack_builder'
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 OData
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 = ''.freeze
9
+ EMPTY_STRING = ''
8
10
  ARY_204_EMPTY_HASH_ARY = [204, EMPTY_HASH, EMPTY_ARRAY].freeze
9
- SPACE = ' '.freeze
10
- COMMA = ','.freeze
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'.freeze
15
- CTT_TYPE_LC = 'content-type'.freeze
16
- TEXTPLAIN_UTF8 = 'text/plain;charset=utf-8'.freeze
17
- APPJSON = 'application/json'.freeze
18
- APPXML = 'application/xml'.freeze
19
- MP_MIXED = 'multipart/mixed'.freeze
20
- APPXML_UTF8 = 'application/xml;charset=utf-8'.freeze
21
- APPATOMXML_UTF8 = 'application/atomsvc+xml;charset=utf-8'.freeze
22
- 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'
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
- # Type mapping DB --> Edm
30
- # TypeMap = {"INTEGER" => "Edm.Int32" , "TEXT" => "Edm.String",
31
- # "STRING" => "Edm.String"}
32
- # Todo: complete mapping... this is just for the most common ones
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
- # TODO... complete; used in $metadata
43
- def self.get_edm_type(db_type:)
44
- case db_type.upcase
45
- when 'INTEGER'
46
- 'Edm.Int32'
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
@@ -1,13 +1,15 @@
1
- CRLF = "\r\n".freeze
2
- LF = "\n".freeze
1
+ # frozen_string_literal: true
2
+
3
+ CRLF = "\r\n"
4
+ LF = "\n"
3
5
 
4
6
  require 'securerandom'
5
- require 'webrick/httpstatus'
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'.freeze
10
- TEXT_PLAIN = 'text/plain'.freeze
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/'.freeze
111
- MP_RGX1 = %r{^(digest|mixed);\s*boundary=\"(.*)\"}.freeze
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'.freeze
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
- @content = ''
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
- # @body_lines.last.sub!(CRLF_ENDING_RGX, '')
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] = "#{OData::MP_MIXED}; boundary=#{@boundary}"
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 = OData::CT_JSON
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
- # b << OData::CONTENT_TYPE << ': ' << @hd[OData::CTT_TYPE_LC] << CRLF
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 if @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 = ::WEBrick::HTTPStatus::StatusMessage.freeze
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'.freeze
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\-\/,\s]+;?\S*)\s*$}.freeze
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