protocol-http 0.54.0 → 0.56.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c76db95baeca082a769340ab2a29ee5948bb837894e55039c082653453aa4f4f
4
- data.tar.gz: 868cdcc4a21786c640c8c06e7892bfd6ef19f81670a90274692cfadef10a0c39
3
+ metadata.gz: 5409468cd5dfe153d3ab2ecc8fae839ff0a688e0d54a81aa402395e6b3de0f1e
4
+ data.tar.gz: 11d700efbe0ecc425438a605f09643bba5bdde1e258bb0508e34428285b209f3
5
5
  SHA512:
6
- metadata.gz: acc4afb3f7caba7ec2d2611ab72dce9c954e88e8dfe72645645e442d8116909c3f228b22c2d460633d429510ffff5f6f4713bfff751f18d4873db167e773fcbe
7
- data.tar.gz: ef0e92bac69dba33417d074c24ce6d2f2009e19e1d81d317eecaf945817f3c7fb0775052048ca24df32b3f39c15f8de83ca45054669e2d89b5bf3b5b445c7cf1
6
+ metadata.gz: 90bf1b95afe9e830249676ab82cba8306c2184dfac7b105dff9c068eb67484f5002b73ec5141a1f42177042a4016c83a118a875df36f30ab65c8aa4b9b1ed2fb
7
+ data.tar.gz: 43ce8ee9a1ace9ff2ae0be4d9ae90625415a504fad5162ebf7e8b1f487bab25bae0aa452260b7cd3fe00193c53835c80f730a1ca16ac142c90f827c852119497
checksums.yaml.gz.sig CHANGED
Binary file
data/context/index.yaml CHANGED
@@ -20,14 +20,6 @@ files:
20
20
  - path: middleware.md
21
21
  title: Middleware
22
22
  description: This guide explains how to build and use HTTP middleware with `Protocol::HTTP::Middleware`.
23
- - path: hypertext-references.md
24
- title: Hypertext References
25
- description: This guide explains how to use `Protocol::HTTP::Reference` for constructing
26
- and manipulating hypertext references (URLs with parameters).
27
- - path: url-parsing.md
28
- title: URL Parsing
29
- description: This guide explains how to use `Protocol::HTTP::URL` for parsing and
30
- manipulating URL components, particularly query strings and parameters.
31
23
  - path: streaming.md
32
24
  title: Streaming
33
25
  description: This guide gives an overview of how to implement streaming requests
@@ -3,13 +3,14 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2019-2025, by Samuel Williams.
5
5
  # Copyright, 2023, by Genki Takiuchi.
6
+ # Copyright, 2025, by William T. Nelson.
6
7
 
7
8
  require_relative "buffered"
8
9
 
9
10
  module Protocol
10
11
  module HTTP
11
12
  module Body
12
- # The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be ASCII-8BIT and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
13
+ # The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be "ASCII-8BIT" and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
13
14
  class Stream
14
15
  # The default line separator, used by {gets}.
15
16
  NEWLINE = "\n"
@@ -315,7 +316,7 @@ module Protocol
315
316
  end
316
317
 
317
318
  # Write data to the stream using {write}.
318
- def <<(buffer)
319
+ def << buffer
319
320
  write(buffer)
320
321
  end
321
322
 
@@ -4,59 +4,66 @@
4
4
  # Copyright, 2019-2025, by Samuel Williams.
5
5
  # Copyright, 2022, by Herrick Fang.
6
6
 
7
- require_relative "url"
7
+ require_relative "quoted_string"
8
8
 
9
9
  module Protocol
10
10
  module HTTP
11
11
  # Represents an individual cookie key-value pair.
12
12
  class Cookie
13
+ # Valid cookie name characters according to RFC 6265.
14
+ # cookie-name = token (RFC 2616 defines token)
15
+ VALID_COOKIE_KEY = /\A#{TOKEN}\z/.freeze
16
+
17
+ # Valid cookie value characters according to RFC 6265.
18
+ # cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
19
+ # cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
20
+ # Excludes control chars, whitespace, DQUOTE, comma, semicolon, and backslash
21
+ VALID_COOKIE_VALUE = /\A[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]*\z/.freeze
22
+
13
23
  # Initialize the cookie with the given name, value, and directives.
14
24
  #
15
- # @parameter name [String] The name of the cookiel, e.g. "session_id".
25
+ # @parameter name [String] The name of the cookie, e.g. "session_id".
16
26
  # @parameter value [String] The value of the cookie, e.g. "1234".
17
27
  # @parameter directives [Hash] The directives of the cookie, e.g. `{"path" => "/"}`.
18
- def initialize(name, value, directives)
28
+ # @raises [ArgumentError] If the name or value contains invalid characters.
29
+ def initialize(name, value, directives = nil)
30
+ unless VALID_COOKIE_KEY.match?(name)
31
+ raise ArgumentError, "Invalid cookie name: #{name.inspect}"
32
+ end
33
+
34
+ if value && !VALID_COOKIE_VALUE.match?(value)
35
+ raise ArgumentError, "Invalid cookie value: #{value.inspect}"
36
+ end
37
+
19
38
  @name = name
20
39
  @value = value
21
40
  @directives = directives
22
41
  end
23
42
 
24
43
  # @attribute [String] The name of the cookie.
25
- attr :name
44
+ attr_accessor :name
26
45
 
27
46
  # @attribute [String] The value of the cookie.
28
- attr :value
47
+ attr_accessor :value
29
48
 
30
49
  # @attribute [Hash] The directives of the cookie.
31
- attr :directives
32
-
33
- # Encode the name of the cookie.
34
- def encoded_name
35
- URL.escape(@name)
36
- end
37
-
38
- # Encode the value of the cookie.
39
- def encoded_value
40
- URL.escape(@value)
41
- end
50
+ attr_accessor :directives
42
51
 
43
52
  # Convert the cookie to a string.
44
53
  #
45
54
  # @returns [String] The string representation of the cookie.
46
55
  def to_s
47
- buffer = String.new.b
56
+ buffer = String.new
48
57
 
49
- buffer << encoded_name << "=" << encoded_value
58
+ buffer << @name << "=" << @value
50
59
 
51
60
  if @directives
52
- @directives.collect do |key, value|
61
+ @directives.each do |key, value|
53
62
  buffer << ";"
63
+ buffer << key
54
64
 
55
- case value
56
- when String
57
- buffer << key << "=" << value
58
- when TrueClass
59
- buffer << key
65
+ if value != true
66
+ buffer << "=" << value.to_s
60
67
  end
61
68
  end
62
69
  end
@@ -74,11 +81,7 @@ module Protocol
74
81
  key, value = head.split("=", 2)
75
82
  directives = self.parse_directives(directives)
76
83
 
77
- self.new(
78
- URL.unescape(key),
79
- URL.unescape(value),
80
- directives,
81
- )
84
+ self.new(key, value, directives)
82
85
  end
83
86
 
84
87
  # Parse a list of strings into a hash of directives.
@@ -2,16 +2,17 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025, by Samuel Williams.
5
+ # Copyright, 2025, by William T. Nelson.
5
6
 
6
7
  require_relative "split"
7
- require_relative "quoted_string"
8
+ require_relative "../quoted_string"
8
9
  require_relative "../error"
9
10
 
10
11
  module Protocol
11
12
  module HTTP
12
13
  module Header
13
14
  # The `accept-content-type` header represents a list of content-types that the client can accept.
14
- class Accept < Array
15
+ class Accept < Split
15
16
  # Regular expression used to split values on commas, with optional surrounding whitespace, taking into account quoted strings.
16
17
  SEPARATOR = /
17
18
  (?: # Start non-capturing group
@@ -67,27 +68,26 @@ module Protocol
67
68
  end
68
69
  end
69
70
 
70
- # Parse the `accept` header value into a list of content types.
71
+ # Parses a raw header value.
71
72
  #
72
- # @parameter value [String] the value of the header.
73
- def initialize(value = nil)
74
- if value
75
- super(value.scan(SEPARATOR).map(&:strip))
76
- end
73
+ # @parameter value [String] a raw header value containing comma-separated media types.
74
+ # @returns [Accept] a new instance containing the parsed media types.
75
+ def self.parse(value)
76
+ self.new(value.scan(SEPARATOR).map(&:strip))
77
77
  end
78
78
 
79
79
  # Adds one or more comma-separated values to the header.
80
80
  #
81
81
  # The input string is split into distinct entries and appended to the array.
82
82
  #
83
- # @parameter value [String] the value or values to add, separated by commas.
84
- def << (value)
83
+ # @parameter value [String] a raw header value containing one or more media types separated by commas.
84
+ def << value
85
85
  self.concat(value.scan(SEPARATOR).map(&:strip))
86
86
  end
87
87
 
88
- # Serializes the stored values into a comma-separated string.
88
+ # Converts the parsed header value into a raw header value.
89
89
  #
90
- # @returns [String] the serialized representation of the header values.
90
+ # @returns [String] a raw header value (comma-separated string).
91
91
  def to_s
92
92
  join(",")
93
93
  end
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "split"
7
- require_relative "quoted_string"
7
+ require_relative "../quoted_string"
8
8
  require_relative "../error"
9
9
 
10
10
  module Protocol
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "split"
7
- require_relative "quoted_string"
7
+ require_relative "../quoted_string"
8
8
  require_relative "../error"
9
9
 
10
10
  module Protocol
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "split"
7
- require_relative "quoted_string"
7
+ require_relative "../quoted_string"
8
8
  require_relative "../error"
9
9
 
10
10
  module Protocol
@@ -15,6 +15,22 @@ module Protocol
15
15
  #
16
16
  # TODO Support other authorization mechanisms, e.g. bearer token.
17
17
  class Authorization < String
18
+ # Parses a raw header value.
19
+ #
20
+ # @parameter value [String] a raw header value.
21
+ # @returns [Authorization] a new instance.
22
+ def self.parse(value)
23
+ self.new(value)
24
+ end
25
+
26
+ # Coerces a value into a parsed header object.
27
+ #
28
+ # @parameter value [String] the value to coerce.
29
+ # @returns [Authorization] a parsed header object.
30
+ def self.coerce(value)
31
+ self.new(value.to_s)
32
+ end
33
+
18
34
  # Splits the header into the credentials.
19
35
  #
20
36
  # @returns [Tuple(String, String)] The username and password.
@@ -31,8 +47,8 @@ module Protocol
31
47
  strict_base64_encoded = ["#{username}:#{password}"].pack("m0")
32
48
 
33
49
  self.new(
34
- "Basic #{strict_base64_encoded}"
35
- )
50
+ "Basic #{strict_base64_encoded}"
51
+ )
36
52
  end
37
53
 
38
54
  # Whether this header is acceptable in HTTP trailers.
@@ -44,16 +44,44 @@ module Protocol
44
44
  # The `proxy-revalidate` directive is similar to `must-revalidate` but applies only to shared caches.
45
45
  PROXY_REVALIDATE = "proxy-revalidate"
46
46
 
47
- # Initializes the cache control header with the given value. The value is expected to be a comma-separated string of cache directives.
47
+ # Parses a raw header value.
48
48
  #
49
- # @parameter value [String | Nil] the raw Cache-Control header value.
49
+ # @parameter value [String] a raw header value containing comma-separated directives.
50
+ # @returns [CacheControl] a new instance containing the parsed and normalized directives.
51
+ def self.parse(value)
52
+ self.new(value.downcase.split(COMMA))
53
+ end
54
+
55
+ # Coerces a value into a parsed header object.
56
+ #
57
+ # @parameter value [String | Array] the value to coerce.
58
+ # @returns [CacheControl] a parsed header object with normalized values.
59
+ def self.coerce(value)
60
+ case value
61
+ when Array
62
+ self.new(value.map(&:downcase))
63
+ else
64
+ self.parse(value.to_s)
65
+ end
66
+ end
67
+
68
+ # Initializes the cache control header with the given values.
69
+ #
70
+ # @parameter value [Array | String | Nil] an array of directives, a raw header value, or `nil` for an empty header.
50
71
  def initialize(value = nil)
51
- super(value&.downcase)
72
+ if value.is_a?(Array)
73
+ super(value)
74
+ elsif value.is_a?(String)
75
+ super()
76
+ self << value
77
+ elsif value
78
+ raise ArgumentError, "Invalid value: #{value.inspect}"
79
+ end
52
80
  end
53
81
 
54
82
  # Adds a directive to the Cache-Control header. The value will be normalized to lowercase before being added.
55
83
  #
56
- # @parameter value [String] the directive to add.
84
+ # @parameter value [String] a raw header value containing directives to add.
57
85
  def << value
58
86
  super(value.downcase)
59
87
  end
@@ -132,3 +160,4 @@ module Protocol
132
160
  end
133
161
  end
134
162
  end
163
+
@@ -22,16 +22,44 @@ module Protocol
22
22
  # The `upgrade` directive indicates that the connection should be upgraded to a different protocol, as specified in the `Upgrade` header.
23
23
  UPGRADE = "upgrade"
24
24
 
25
- # Initializes the connection header with the given value. The value is expected to be a comma-separated string of directives.
25
+ # Parses a raw header value.
26
26
  #
27
- # @parameter value [String | Nil] the raw `connection` header value.
27
+ # @parameter value [String] a raw header value containing comma-separated directives.
28
+ # @returns [Connection] a new instance with normalized (lowercase) directives.
29
+ def self.parse(value)
30
+ self.new(value.downcase.split(COMMA))
31
+ end
32
+
33
+ # Coerces a value into a parsed header object.
34
+ #
35
+ # @parameter value [String | Array] the value to coerce.
36
+ # @returns [Connection] a parsed header object with normalized values.
37
+ def self.coerce(value)
38
+ case value
39
+ when Array
40
+ self.new(value.map(&:downcase))
41
+ else
42
+ self.parse(value.to_s)
43
+ end
44
+ end
45
+
46
+ # Initializes the connection header with the given values.
47
+ #
48
+ # @parameter value [Array | String | Nil] an array of directives, a raw header value, or `nil` for an empty header.
28
49
  def initialize(value = nil)
29
- super(value&.downcase)
50
+ if value.is_a?(Array)
51
+ super(value)
52
+ elsif value.is_a?(String)
53
+ super()
54
+ self << value
55
+ elsif value
56
+ raise ArgumentError, "Invalid value: #{value.inspect}"
57
+ end
30
58
  end
31
59
 
32
60
  # Adds a directive to the `connection` header. The value will be normalized to lowercase before being added.
33
61
  #
34
- # @parameter value [String] the directive to add.
62
+ # @parameter value [String] a raw header value containing directives to add.
35
63
  def << value
36
64
  super(value.downcase)
37
65
  end
@@ -61,3 +89,4 @@ module Protocol
61
89
  end
62
90
  end
63
91
  end
92
+
@@ -12,9 +12,25 @@ module Protocol
12
12
  #
13
13
  # This header is typically included in HTTP responses and follows the format defined in RFC 9110.
14
14
  class Date < String
15
- # Replaces the current value of the `date` header with the specified value.
15
+ # Parses a raw header value.
16
16
  #
17
- # @parameter value [String] the new value for the `date` header.
17
+ # @parameter value [String] a raw header value.
18
+ # @returns [Date] a new instance.
19
+ def self.parse(value)
20
+ self.new(value)
21
+ end
22
+
23
+ # Coerces a value into a parsed header object.
24
+ #
25
+ # @parameter value [String] the value to coerce.
26
+ # @returns [Date] a parsed header object.
27
+ def self.coerce(value)
28
+ self.new(value.to_s)
29
+ end
30
+
31
+ # Replaces the current value of the `date` header.
32
+ #
33
+ # @parameter value [String] a raw header value for the `date` header.
18
34
  def << value
19
35
  replace(value)
20
36
  end
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "split"
7
- require_relative "quoted_string"
7
+ require_relative "../quoted_string"
8
8
  require_relative "../error"
9
9
 
10
10
  module Protocol
@@ -10,9 +10,25 @@ module Protocol
10
10
  #
11
11
  # The `etag` header provides a unique identifier for a specific version of a resource, typically used for cache validation or conditional requests. It can be either a strong or weak validator as defined in RFC 9110.
12
12
  class ETag < String
13
- # Replaces the current value of the `etag` header with the specified value.
13
+ # Parses a raw header value.
14
14
  #
15
- # @parameter value [String] the new value for the `etag` header.
15
+ # @parameter value [String] a raw header value.
16
+ # @returns [ETag] a new instance.
17
+ def self.parse(value)
18
+ self.new(value)
19
+ end
20
+
21
+ # Coerces a value into a parsed header object.
22
+ #
23
+ # @parameter value [String] the value to coerce.
24
+ # @returns [ETag] a parsed header object.
25
+ def self.coerce(value)
26
+ self.new(value.to_s)
27
+ end
28
+
29
+ # Replaces the current value of the `etag` header.
30
+ #
31
+ # @parameter value [String] a raw header value for the `etag` header.
16
32
  def << value
17
33
  replace(value)
18
34
  end
@@ -52,7 +52,7 @@ module Protocol
52
52
  wildcard? || self.include?(etag) || self.include?(opposite_tag(etag))
53
53
  end
54
54
 
55
- private
55
+ private
56
56
 
57
57
  # Converts a weak tag to its strong counterpart or vice versa.
58
58
  #
@@ -10,18 +10,47 @@ module Protocol
10
10
  #
11
11
  # This isn't a specific header but is used as a base for headers that store multiple values, such as cookies. The values are split and stored as an array internally, and serialized back to a newline-separated string when needed.
12
12
  class Multiple < Array
13
- # Initializes the multiple header with the given value. As the header key-value pair can only contain one value, the value given here is added to the internal array, and subsequent values can be added using the `<<` operator.
13
+ # Parses a raw header value.
14
14
  #
15
- # @parameter value [String] the raw header value.
16
- def initialize(value)
15
+ # Multiple headers receive each value as a separate header entry, so this method takes a single string value and creates a new instance containing it.
16
+ #
17
+ # @parameter value [String] a single raw header value.
18
+ # @returns [Multiple] a new instance containing the parsed value.
19
+ def self.parse(value)
20
+ self.new([value])
21
+ end
22
+
23
+ # Coerces a value into a parsed header object.
24
+ #
25
+ # This method is used by the Headers class when setting values via `[]=` to convert application values into the appropriate policy type.
26
+ #
27
+ # @parameter value [String | Array] the value to coerce.
28
+ # @returns [Multiple] a parsed header object.
29
+ def self.coerce(value)
30
+ case value
31
+ when Array
32
+ self.new(value.map(&:to_s))
33
+ else
34
+ self.parse(value.to_s)
35
+ end
36
+ end
37
+
38
+ # Initializes the multiple header with the given values.
39
+ #
40
+ # @parameter value [Array | Nil] an array of header values, or `nil` for an empty header.
41
+ def initialize(value = nil)
17
42
  super()
18
43
 
19
- self << value
44
+ if value
45
+ self.concat(value)
46
+ end
20
47
  end
21
48
 
22
- # Serializes the stored values into a newline-separated string.
49
+ # Converts the parsed header value into a raw header value.
50
+ #
51
+ # Multiple headers are transmitted as separate header entries, so this serializes to a newline-separated string for storage.
23
52
  #
24
- # @returns [String] the serialized representation of the header values.
53
+ # @returns [String] a raw header value (newline-separated string).
25
54
  def to_s
26
55
  join("\n")
27
56
  end
@@ -12,16 +12,44 @@ module Protocol
12
12
  #
13
13
  # The `priority` header allows clients to express their preference for how resources should be prioritized by the server. It supports directives like `u=` to specify the urgency level of a request, and `i` to indicate whether a response can be delivered incrementally. The urgency levels range from 0 (highest priority) to 7 (lowest priority), while the `i` directive is a boolean flag.
14
14
  class Priority < Split
15
- # Initialize the priority header with the given value.
15
+ # Parses a raw header value.
16
16
  #
17
- # @parameter value [String | Nil] the value of the priority header, if any. The value should be a comma-separated string of directives.
17
+ # @parameter value [String] a raw header value containing comma-separated directives.
18
+ # @returns [Priority] a new instance with normalized (lowercase) directives.
19
+ def self.parse(value)
20
+ self.new(value.downcase.split(COMMA))
21
+ end
22
+
23
+ # Coerces a value into a parsed header object.
24
+ #
25
+ # @parameter value [String | Array] the value to coerce.
26
+ # @returns [Priority] a parsed header object with normalized values.
27
+ def self.coerce(value)
28
+ case value
29
+ when Array
30
+ self.new(value.map(&:downcase))
31
+ else
32
+ self.parse(value.to_s)
33
+ end
34
+ end
35
+
36
+ # Initializes the priority header with the given values.
37
+ #
38
+ # @parameter value [Array | String | Nil] an array of directives, a raw header value, or `nil` for an empty header.
18
39
  def initialize(value = nil)
19
- super(value&.downcase)
40
+ if value.is_a?(Array)
41
+ super(value)
42
+ elsif value.is_a?(String)
43
+ super()
44
+ self << value
45
+ elsif value
46
+ raise ArgumentError, "Invalid value: #{value.inspect}"
47
+ end
20
48
  end
21
49
 
22
50
  # Add a value to the priority header.
23
51
  #
24
- # @parameter value [String] the directive to add to the header.
52
+ # @parameter value [String] a raw header value containing directives to add to the header.
25
53
  def << value
26
54
  super(value.downcase)
27
55
  end
@@ -55,3 +83,4 @@ module Protocol
55
83
  end
56
84
  end
57
85
  end
86
+
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "split"
7
- require_relative "quoted_string"
7
+ require_relative "../quoted_string"
8
8
  require_relative "../error"
9
9
 
10
10
  module Protocol
@@ -13,14 +13,43 @@ module Protocol
13
13
  # Regular expression used to split values on commas, with optional surrounding whitespace.
14
14
  COMMA = /\s*,\s*/
15
15
 
16
- # Initializes a `Split` header with the given value. If the value is provided, it is split into distinct entries and stored as an array.
16
+ # Parses a raw header value.
17
17
  #
18
- # @parameter value [String | Nil] the raw header value containing multiple entries separated by commas, or `nil` for an empty header.
19
- def initialize(value = nil)
20
- if value
21
- super(value.split(COMMA))
18
+ # Split headers receive comma-separated values in a single header entry. This method splits the raw value into individual entries.
19
+ #
20
+ # @parameter value [String] a raw header value containing multiple entries separated by commas.
21
+ # @returns [Split] a new instance containing the parsed values.
22
+ def self.parse(value)
23
+ self.new(value.split(COMMA))
24
+ end
25
+
26
+ # Coerces a value into a parsed header object.
27
+ #
28
+ # This method is used by the Headers class when setting values via `[]=` to convert application values into the appropriate policy type.
29
+ #
30
+ # @parameter value [String | Array] the value to coerce.
31
+ # @returns [Split] a parsed header object.
32
+ def self.coerce(value)
33
+ case value
34
+ when Array
35
+ self.new(value.map(&:to_s))
22
36
  else
37
+ self.parse(value.to_s)
38
+ end
39
+ end
40
+
41
+ # Initializes a `Split` header with the given values.
42
+ #
43
+ # @parameter value [Array | String | Nil] an array of values, a raw header value, or `nil` for an empty header.
44
+ def initialize(value = nil)
45
+ if value.is_a?(Array)
46
+ super(value)
47
+ elsif value.is_a?(String)
48
+ # Compatibility with the old constructor, prefer to use `parse` instead:
23
49
  super()
50
+ self << value
51
+ elsif value
52
+ raise ArgumentError, "Invalid value: #{value.inspect}"
24
53
  end
25
54
  end
26
55
 
@@ -28,14 +57,14 @@ module Protocol
28
57
  #
29
58
  # The input string is split into distinct entries and appended to the array.
30
59
  #
31
- # @parameter value [String] the value or values to add, separated by commas.
60
+ # @parameter value [String] a raw header value containing one or more values separated by commas.
32
61
  def << value
33
62
  self.concat(value.split(COMMA))
34
63
  end
35
64
 
36
- # Serializes the stored values into a comma-separated string.
65
+ # Converts the parsed header value into a raw header value.
37
66
  #
38
- # @returns [String] the serialized representation of the header values.
67
+ # @returns [String] a raw header value (comma-separated string).
39
68
  def to_s
40
69
  join(",")
41
70
  end
@@ -47,7 +76,7 @@ module Protocol
47
76
  false
48
77
  end
49
78
 
50
- protected
79
+ protected
51
80
 
52
81
  def reverse_find(&block)
53
82
  reverse_each do |value|