protocol-http 0.55.0 → 0.56.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c016217fd5d9ddfac87278d8a629d47ec338d02441d0f27ed25102962e927b9
4
- data.tar.gz: 51d479e7f25046ca76d08ed60676b7bf4afe5ffa3e234bbb4501478d3fe1db2c
3
+ metadata.gz: 9874b094d1320bc400ff81a991311fa74a9b95d0a89adc9c4a4c41b89380bb2a
4
+ data.tar.gz: d2f9206d98a99557152c4758f266ef70d516072e2cfb9e0b6099dc4901bbbe95
5
5
  SHA512:
6
- metadata.gz: a49eb6993da937df85d263f3b505dc700066c796b102c2e4290b9893700fd9f82852bb17f330ef2bf350e11dbf0782e206ae1d29cc4be3fb422d8f6642f1720b
7
- data.tar.gz: 2680c120c9d72c6ee7c35929984b67e248d26496145dc08700e4a8a5f60568e6dfe2600b5179042d92793d5784ee46e270a59e76f6d272c094c47ccf1c97c0b3
6
+ metadata.gz: ec515579222f78ee8ee4353c346cf8d361bb244deefc4bf0deb5899fe28399c96e273fe8c1fca10e328b9e5872e5e2b40ea09fcf6c4463da704e097a58e795bb
7
+ data.tar.gz: d2b739b64da0af2db050ddccfe292e464ed1defc7310e0152ddeeff58b4455b725483dd76aa237f21ff44d0ae3b1ff798269f81ed2c69a7ba6a1f865c1bc3820
checksums.yaml.gz.sig CHANGED
@@ -1,3 +1,3 @@
1
- H��m�J��
2
- b����fAa�+vSck`�ÙJ���MS�r"��.D
3
- y�������^Je8�6O�����O��iH6�6쐐���Hʸ��6P���_.��z�qX���O�v�T� �އ��ɲ>���xv쓼�~���n�\XD�(3
1
+ �eh����(߄�Rț²lN! �?K�HeAe
2
+ P��9��KVhˊ�F�U��wWa��#ʞKUpoj#n?O���.�Kk_)*�/�7FYI` �����k�?\�5�~dT��4ƥn3P�5�r�G~��˯��4n�:T��-�"W�LW�V��[/�4�'R\Cم�O��gO��b�;����{��"�1��;�l�j��(�o�<O��G���g� =�pD��2���[mj�W$♿JJT����q����c�۷xc���߯���8�-WHZ�h����~
3
+ ��"F�i;5+���k�(�: $����|(H^�9
@@ -10,7 +10,7 @@ require_relative "buffered"
10
10
  module Protocol
11
11
  module HTTP
12
12
  module Body
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
+ # 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.
14
14
  class Stream
15
15
  # The default line separator, used by {gets}.
16
16
  NEWLINE = "\n"
@@ -12,7 +12,7 @@ module Protocol
12
12
  module HTTP
13
13
  module Header
14
14
  # The `accept-content-type` header represents a list of content-types that the client can accept.
15
- class Accept < Array
15
+ class Accept < Split
16
16
  # Regular expression used to split values on commas, with optional surrounding whitespace, taking into account quoted strings.
17
17
  SEPARATOR = /
18
18
  (?: # Start non-capturing group
@@ -68,27 +68,26 @@ module Protocol
68
68
  end
69
69
  end
70
70
 
71
- # Parse the `accept` header value into a list of content types.
71
+ # Parses a raw header value.
72
72
  #
73
- # @parameter value [String] the value of the header.
74
- def initialize(value = nil)
75
- if value
76
- super(value.scan(SEPARATOR).map(&:strip))
77
- 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))
78
77
  end
79
78
 
80
79
  # Adds one or more comma-separated values to the header.
81
80
  #
82
81
  # The input string is split into distinct entries and appended to the array.
83
82
  #
84
- # @parameter value [String] the value or values to add, separated by commas.
83
+ # @parameter value [String] a raw header value containing one or more media types separated by commas.
85
84
  def << value
86
85
  self.concat(value.scan(SEPARATOR).map(&:strip))
87
86
  end
88
87
 
89
- # Serializes the stored values into a comma-separated string.
88
+ # Converts the parsed header value into a raw header value.
90
89
  #
91
- # @returns [String] the serialized representation of the header values.
90
+ # @returns [String] a raw header value (comma-separated string).
92
91
  def to_s
93
92
  join(",")
94
93
  end
@@ -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.
@@ -44,16 +44,30 @@ 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.
50
- def initialize(value = nil)
51
- super(value&.downcase)
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))
52
53
  end
53
54
 
54
- # Adds a directive to the Cache-Control header. The value will be normalized to lowercase before being added.
55
+ # Coerces a value into a parsed header object.
55
56
  #
56
- # @parameter value [String] the directive to add.
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
+ # Adds a directive to the `cache-control` header. The value will be normalized to lowercase before being added.
69
+ #
70
+ # @parameter value [String] a raw header value containing directives to add.
57
71
  def << value
58
72
  super(value.downcase)
59
73
  end
@@ -132,3 +146,4 @@ module Protocol
132
146
  end
133
147
  end
134
148
  end
149
+
@@ -22,16 +22,30 @@ 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.
28
- def initialize(value = nil)
29
- super(value&.downcase)
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
30
44
  end
31
45
 
32
46
  # Adds a directive to the `connection` header. The value will be normalized to lowercase before being added.
33
47
  #
34
- # @parameter value [String] the directive to add.
48
+ # @parameter value [String] a raw header value containing directives to add.
35
49
  def << value
36
50
  super(value.downcase)
37
51
  end
@@ -61,3 +75,4 @@ module Protocol
61
75
  end
62
76
  end
63
77
  end
78
+
@@ -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
@@ -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,30 @@ 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.
18
- def initialize(value = nil)
19
- super(value&.downcase)
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
20
34
  end
21
35
 
22
36
  # Add a value to the priority header.
23
37
  #
24
- # @parameter value [String] the directive to add to the header.
38
+ # @parameter value [String] a raw header value containing directives to add to the header.
25
39
  def << value
26
40
  super(value.downcase)
27
41
  end
@@ -55,3 +69,4 @@ module Protocol
55
69
  end
56
70
  end
57
71
  end
72
+
@@ -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|
@@ -62,16 +62,30 @@ module Protocol
62
62
  end
63
63
  end
64
64
 
65
- # Initializes the TE header with the given value. The value is split into distinct entries and converted to lowercase for normalization.
65
+ # Parses a raw header value.
66
66
  #
67
- # @parameter value [String | Nil] the raw header value containing transfer encodings separated by commas.
68
- def initialize(value = nil)
69
- super(value&.downcase)
67
+ # @parameter value [String] a raw header value containing comma-separated encodings.
68
+ # @returns [TE] a new instance with normalized (lowercase) encodings.
69
+ def self.parse(value)
70
+ self.new(value.downcase.split(COMMA))
71
+ end
72
+
73
+ # Coerces a value into a parsed header object.
74
+ #
75
+ # @parameter value [String | Array] the value to coerce.
76
+ # @returns [TE] a parsed header object with normalized values.
77
+ def self.coerce(value)
78
+ case value
79
+ when Array
80
+ self.new(value.map(&:downcase))
81
+ else
82
+ self.parse(value.to_s)
83
+ end
70
84
  end
71
85
 
72
86
  # Adds one or more comma-separated values to the TE header. The values are converted to lowercase for normalization.
73
87
  #
74
- # @parameter value [String] the value or values to add, separated by commas.
88
+ # @parameter value [String] a raw header value containing one or more values separated by commas.
75
89
  def << value
76
90
  super(value.downcase)
77
91
  end
@@ -129,3 +143,4 @@ module Protocol
129
143
  end
130
144
  end
131
145
  end
146
+
@@ -27,16 +27,30 @@ module Protocol
27
27
  # The `identity` transfer encoding indicates no transformation has been applied.
28
28
  IDENTITY = "identity"
29
29
 
30
- # Initializes the transfer encoding header with the given value. The value is split into distinct entries and converted to lowercase for normalization.
30
+ # Parses a raw header value.
31
31
  #
32
- # @parameter value [String | Nil] the raw header value containing transfer encodings separated by commas.
33
- def initialize(value = nil)
34
- super(value&.downcase)
32
+ # @parameter value [String] a raw header value containing comma-separated encodings.
33
+ # @returns [TransferEncoding] a new instance with normalized (lowercase) encodings.
34
+ def self.parse(value)
35
+ self.new(value.downcase.split(COMMA))
36
+ end
37
+
38
+ # Coerces a value into a parsed header object.
39
+ #
40
+ # @parameter value [String | Array] the value to coerce.
41
+ # @returns [TransferEncoding] a parsed header object with normalized values.
42
+ def self.coerce(value)
43
+ case value
44
+ when Array
45
+ self.new(value.map(&:downcase))
46
+ else
47
+ self.parse(value.to_s)
48
+ end
35
49
  end
36
50
 
37
51
  # Adds one or more comma-separated values to the transfer encoding header. The values are converted to lowercase for normalization.
38
52
  #
39
- # @parameter value [String] the value or values to add, separated by commas.
53
+ # @parameter value [String] a raw header value containing one or more values separated by commas.
40
54
  def << value
41
55
  super(value.downcase)
42
56
  end
@@ -76,3 +90,4 @@ module Protocol
76
90
  end
77
91
  end
78
92
  end
93
+
@@ -12,16 +12,30 @@ module Protocol
12
12
  #
13
13
  # The `vary` header is used in HTTP responses to indicate which request headers affect the selected response. It allows caches to differentiate stored responses based on specific request headers.
14
14
  class Vary < Split
15
- # Initializes a `Vary` header with the given value. The value is split into distinct entries and converted to lowercase for normalization.
15
+ # Parses a raw header value.
16
16
  #
17
- # @parameter value [String] the raw header value containing request header names separated by commas.
18
- def initialize(value)
19
- super(value.downcase)
17
+ # @parameter value [String] a raw header value containing comma-separated header names.
18
+ # @returns [Vary] a new instance with normalized (lowercase) header names.
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 [Vary] 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
20
34
  end
21
35
 
22
36
  # Adds one or more comma-separated values to the `vary` header. The values are converted to lowercase for normalization.
23
37
  #
24
- # @parameter value [String] the value or values to add, separated by commas.
38
+ # @parameter value [String] a raw header value containing one or more values separated by commas.
25
39
  def << value
26
40
  super(value.downcase)
27
41
  end
@@ -29,3 +43,4 @@ module Protocol
29
43
  end
30
44
  end
31
45
  end
46
+
@@ -189,7 +189,7 @@ module Protocol
189
189
  #
190
190
  # @yields {|key, value| ...}
191
191
  # @parameter key [String] The header key.
192
- # @parameter value [String] The header value.
192
+ # @parameter value [String] The raw header value.
193
193
  def each(&block)
194
194
  @fields.each(&block)
195
195
  end
@@ -228,7 +228,6 @@ module Protocol
228
228
  # @parameter key [String] the header key.
229
229
  # @parameter value [String] the header value to assign.
230
230
  def add(key, value)
231
- # The value MUST be a string, so we convert it to a string to prevent errors later on.
232
231
  value = value.to_s
233
232
 
234
233
  if @indexed
@@ -238,18 +237,51 @@ module Protocol
238
237
  @fields << [key, value]
239
238
  end
240
239
 
241
- alias []= add
242
-
243
240
  # Set the specified header key to the specified value, replacing any existing header keys with the same name.
244
241
  #
245
242
  # @parameter key [String] the header key to replace.
246
243
  # @parameter value [String] the header value to assign.
247
244
  def set(key, value)
248
- # TODO This could be a bit more efficient:
249
245
  self.delete(key)
250
246
  self.add(key, value)
251
247
  end
252
248
 
249
+ # Set the specified header key to the specified value, replacing any existing values.
250
+ #
251
+ # The value can be a String or a coercable value.
252
+ #
253
+ # @parameter key [String] the header key.
254
+ # @parameter value [String | Array] the header value to assign.
255
+ def []=(key, value)
256
+ key = key.downcase
257
+
258
+ # Delete existing value if any:
259
+ self.delete(key)
260
+
261
+ if policy = @policy[key]
262
+ unless value.is_a?(policy)
263
+ value = policy.coerce(value)
264
+ end
265
+ else
266
+ value = value.to_s
267
+ end
268
+
269
+ # Clear the indexed cache so it will be rebuilt with parsed values when accessed:
270
+ if @indexed
271
+ @indexed[key] = value
272
+ end
273
+
274
+ @fields << [key, value.to_s]
275
+ end
276
+
277
+ # Get the value of the specified header key.
278
+ #
279
+ # @parameter key [String] The header key.
280
+ # @returns [String | Array | Object] The header value.
281
+ def [] key
282
+ to_h[key]
283
+ end
284
+
253
285
  # Merge the headers into this instance.
254
286
  def merge!(headers)
255
287
  headers.each do |key, value|
@@ -338,6 +370,11 @@ module Protocol
338
370
  # @parameter key [String] The header key.
339
371
  # @returns [String | Array | Object] The merged header value.
340
372
  def delete(key)
373
+ # If we've indexed the headers, we can bail out early if the key is not present:
374
+ if @indexed && !@indexed.key?(key.downcase)
375
+ return nil
376
+ end
377
+
341
378
  deleted, @fields = @fields.partition do |field|
342
379
  field.first.downcase == key
343
380
  end
@@ -350,7 +387,7 @@ module Protocol
350
387
  return @indexed.delete(key)
351
388
  elsif policy = @policy[key]
352
389
  (key, value), *tail = deleted
353
- merged = policy.new(value)
390
+ merged = policy.parse(value)
354
391
 
355
392
  tail.each{|k,v| merged << v}
356
393
 
@@ -376,7 +413,11 @@ module Protocol
376
413
  if current_value = hash[key]
377
414
  current_value << value
378
415
  else
379
- hash[key] = policy.new(value)
416
+ if policy.respond_to?(:parse)
417
+ hash[key] = policy.parse(value)
418
+ else
419
+ hash[key] = policy.new(value)
420
+ end
380
421
  end
381
422
  else
382
423
  # By default, headers are not allowed in trailers:
@@ -392,14 +433,6 @@ module Protocol
392
433
  end
393
434
  end
394
435
 
395
- # Get the value of the specified header key.
396
- #
397
- # @parameter key [String] The header key.
398
- # @returns [String | Array | Object] The header value.
399
- def [] key
400
- to_h[key]
401
- end
402
-
403
436
  # Compute a hash table of headers, where the keys are normalized to lower case and the values are normalized according to the policy for that header.
404
437
  #
405
438
  # @returns [Hash] A hash table of `{key, value}` pairs.
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.55.0"
8
+ VERSION = "0.56.1"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -30,6 +30,14 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
30
30
 
31
31
  Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
32
32
 
33
+ ### v0.56.0
34
+
35
+ - Introduce `Header::*.parse(value)` which parses a raw header value string into a header instance.
36
+ - Introduce `Header::*.coerce(value)` which coerces any value (`String`, `Array`, etc.) into a header instance with normalization.
37
+ - `Header::*#initialize` now accepts arrays without normalization for efficiency, or strings for backward compatibility.
38
+ - Update `Headers#[]=` to use `coerce(value)` for smart conversion of user input.
39
+ - Normalization (e.g., lowercasing) is applied by `parse`, `coerce`, and `<<` methods, but not by `new` when given arrays.
40
+
33
41
  ### v0.55.0
34
42
 
35
43
  - **Breaking**: Move `Protocol::HTTP::Header::QuotedString` to `Protocol::HTTP::QuotedString` for better reusability.
@@ -78,10 +86,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
78
86
  - Clarify behaviour of streaming bodies and copy `Protocol::Rack::Body::Streaming` to `Protocol::HTTP::Body::Streamable`.
79
87
  - Copy `Async::HTTP::Body::Writable` to `Protocol::HTTP::Body::Writable`.
80
88
 
81
- ### v0.31.0
82
-
83
- - Ensure chunks are flushed if required, when streaming.
84
-
85
89
  ## See Also
86
90
 
87
91
  - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.56.0
4
+
5
+ - Introduce `Header::*.parse(value)` which parses a raw header value string into a header instance.
6
+ - Introduce `Header::*.coerce(value)` which coerces any value (`String`, `Array`, etc.) into a header instance with normalization.
7
+ - `Header::*#initialize` now accepts arrays without normalization for efficiency, or strings for backward compatibility.
8
+ - Update `Headers#[]=` to use `coerce(value)` for smart conversion of user input.
9
+ - Normalization (e.g., lowercasing) is applied by `parse`, `coerce`, and `<<` methods, but not by `new` when given arrays.
10
+
3
11
  ## v0.55.0
4
12
 
5
13
  - **Breaking**: Move `Protocol::HTTP::Header::QuotedString` to `Protocol::HTTP::QuotedString` for better reusability.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.55.0
4
+ version: 0.56.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  - !ruby/object:Gem::Version
135
135
  version: '0'
136
136
  requirements: []
137
- rubygems_version: 3.7.2
137
+ rubygems_version: 3.6.9
138
138
  specification_version: 4
139
139
  summary: Provides abstractions to handle HTTP protocols.
140
140
  test_files: []
metadata.gz.sig CHANGED
Binary file