linzer 0.7.7.beta1 → 0.7.8

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +3 -1
  5. data/flake.lock +109 -0
  6. data/flake.nix +73 -0
  7. data/lib/linzer/common.rb +51 -0
  8. data/lib/linzer/ecdsa.rb +51 -0
  9. data/lib/linzer/ed25519.rb +35 -0
  10. data/lib/linzer/helper.rb +79 -0
  11. data/lib/linzer/hmac.rb +47 -1
  12. data/lib/linzer/http/bootstrap.rb +11 -0
  13. data/lib/linzer/http/signature_feature.rb +53 -1
  14. data/lib/linzer/http.rb +54 -0
  15. data/lib/linzer/jws.rb +74 -0
  16. data/lib/linzer/key/helper.rb +186 -10
  17. data/lib/linzer/key.rb +73 -0
  18. data/lib/linzer/message/adapter/abstract.rb +75 -10
  19. data/lib/linzer/message/adapter/generic/request.rb +27 -0
  20. data/lib/linzer/message/adapter/generic/response.rb +17 -0
  21. data/lib/linzer/message/adapter/http_gem/request.rb +11 -0
  22. data/lib/linzer/message/adapter/http_gem/response.rb +8 -5
  23. data/lib/linzer/message/adapter/net_http/request.rb +7 -0
  24. data/lib/linzer/message/adapter/net_http/response.rb +4 -0
  25. data/lib/linzer/message/adapter/rack/common.rb +14 -6
  26. data/lib/linzer/message/adapter/rack/request.rb +13 -0
  27. data/lib/linzer/message/adapter/rack/response.rb +11 -0
  28. data/lib/linzer/message/adapter.rb +17 -0
  29. data/lib/linzer/message/field/parser.rb +14 -0
  30. data/lib/linzer/message/field.rb +32 -2
  31. data/lib/linzer/message/wrapper.rb +20 -0
  32. data/lib/linzer/message.rb +113 -3
  33. data/lib/linzer/options.rb +13 -0
  34. data/lib/linzer/rsa.rb +34 -0
  35. data/lib/linzer/rsa_pss.rb +44 -0
  36. data/lib/linzer/signature.rb +113 -1
  37. data/lib/linzer/signer.rb +69 -0
  38. data/lib/linzer/verifier.rb +52 -0
  39. data/lib/linzer/version.rb +3 -1
  40. data/lib/linzer.rb +104 -0
  41. data/lib/rack/auth/signature.rb +90 -6
  42. metadata +30 -16
data/lib/linzer/key.rb CHANGED
@@ -1,7 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linzer
4
+ # Abstract base class for cryptographic keys used in HTTP message signatures.
5
+ #
6
+ # This class provides the common interface for all key types supported by Linzer.
7
+ # Do not instantiate this class directly; use one of the concrete subclasses
8
+ # or the key generation/loading helper methods.
9
+ #
10
+ # @abstract Subclass and override {#sign} and {#verify} to implement
11
+ # a specific cryptographic algorithm.
12
+ #
13
+ # @example Using a concrete key class via helper methods
14
+ # # Generate a new Ed25519 key pair
15
+ # key = Linzer.generate_ed25519_key("my-key-id")
16
+ #
17
+ # # Load an existing RSA-PSS key from PEM
18
+ # key = Linzer.new_rsa_pss_sha512_key(File.read("private.pem"), "rsa-key")
19
+ #
20
+ # @see Ed25519::Key
21
+ # @see ECDSA::Key
22
+ # @see HMAC::Key
23
+ # @see RSA::Key
24
+ # @see RSAPSS::Key
25
+ # @see JWS::Key
4
26
  class Key
27
+ # Creates a new Key instance.
28
+ #
29
+ # @param material [OpenSSL::PKey::PKey, String] The key material.
30
+ # For asymmetric keys, this is typically an OpenSSL key object.
31
+ # For HMAC, this is the raw secret bytes.
32
+ # @param params [Hash] Additional key parameters
33
+ # @option params [String] :id The key identifier (keyid) for this key
34
+ # @option params [String] :digest The digest algorithm (e.g., "SHA256", "SHA512")
35
+ #
36
+ # @raise [Error] If key material is nil or invalid
5
37
  def initialize(material, params = {})
6
38
  @material = material
7
39
  @params = Hash(params).clone.freeze
@@ -9,54 +41,95 @@ module Linzer
9
41
  freeze
10
42
  end
11
43
 
44
+ # @return [Object] The underlying key material
12
45
  attr_reader :material
13
46
 
47
+ # Returns the key identifier.
48
+ #
49
+ # The key ID is used in the `keyid` parameter of HTTP signatures to
50
+ # identify which key was used for signing.
51
+ #
52
+ # @return [String, nil] The key identifier, or nil if not set
14
53
  def key_id
15
54
  @params[:id]
16
55
  end
17
56
 
57
+ # Signs data using this key.
58
+ #
59
+ # @abstract Subclasses must override this method.
60
+ #
61
+ # @param args [Array] Implementation-specific arguments (typically data to sign)
62
+ # @return [String] The signature bytes
63
+ # @raise [Error] If called on the abstract base class
64
+ # @raise [SigningError] If the key cannot be used for signing
18
65
  def sign(*args)
19
66
  abstract_error = "Cannot sign data, \"#{self.class}\" is an abstract class."
20
67
  raise Error, abstract_error
21
68
  end
22
69
 
70
+ # Verifies a signature against data using this key.
71
+ #
72
+ # @abstract Subclasses must override this method.
73
+ #
74
+ # @param args [Array] Implementation-specific arguments (typically signature and data)
75
+ # @return [Boolean] true if the signature is valid, false otherwise
76
+ # @raise [Error] If called on the abstract base class
77
+ # @raise [VerifyError] If the key cannot be used for verification
23
78
  def verify(*args)
24
79
  abstract_error = "Cannot verify signature, \"#{self.class}\" is an abstract class."
25
80
  raise Error, abstract_error
26
81
  end
27
82
 
83
+ # Checks if this key can be used for signature verification.
84
+ #
85
+ # @return [Boolean] true if the key contains public key material
28
86
  def public?
29
87
  material.public?
30
88
  end
31
89
 
90
+ # Checks if this key can be used for signing.
91
+ #
92
+ # @return [Boolean] true if the key contains private key material
32
93
  def private?
33
94
  material.private?
34
95
  end
35
96
 
36
97
  private
37
98
 
99
+ # Validates that the key has material.
100
+ # @raise [Error] If key material is nil
38
101
  def validate
39
102
  !material.nil? or raise Error.new "Invalid key. No key material provided."
40
103
  end
41
104
 
105
+ # Validates that a digest algorithm is configured.
106
+ # @raise [Error] If no digest algorithm is set
42
107
  def validate_digest
43
108
  no_digest = !@params.key?(:digest) || @params[:digest].nil? || String(@params[:digest]).empty?
44
109
  no_digest_error = "Invalid key definition, no digest algorithm was selected."
45
110
  raise Error, no_digest_error if no_digest
46
111
  end
47
112
 
113
+ # Validates that this key can be used for signing.
114
+ # @raise [SigningError] If the key does not contain private material
48
115
  def validate_signing_key
49
116
  raise SigningError, "Private key is needed!" unless private?
50
117
  end
51
118
 
119
+ # Validates that this key can be used for verification.
120
+ # @raise [VerifyError] If the key does not contain public material
52
121
  def validate_verify_key
53
122
  raise VerifyError, "Public key is needed!" unless public?
54
123
  end
55
124
 
125
+ # Checks if the key material has a PEM-encoded public key.
126
+ # @return [Boolean]
56
127
  def has_pem_public?
57
128
  material.public_to_pem.match?(/^-----BEGIN PUBLIC KEY-----/)
58
129
  end
59
130
 
131
+ # Checks if the key material has a PEM-encoded private key.
132
+ # @return [Boolean]
60
133
  def has_pem_private?
61
134
  material.private_to_pem.match?(/^-----BEGIN PRIVATE KEY-----/)
62
135
  rescue
@@ -3,61 +3,122 @@
3
3
  module Linzer
4
4
  class Message
5
5
  module Adapter
6
+ # Abstract base class for HTTP message adapters.
7
+ #
8
+ # Adapters provide a unified interface for accessing HTTP message
9
+ # components regardless of the underlying HTTP library. Each adapter
10
+ # implements field retrieval, header access, and signature attachment
11
+ # for a specific HTTP message type.
12
+ #
13
+ # @abstract Subclass and implement {#header}, {#attach!}, {#derived},
14
+ # and {#field} to create a new adapter.
15
+ #
16
+ # @see Rack::Request Rack request adapter
17
+ # @see Rack::Response Rack response adapter
18
+ # @see NetHTTP::Request Net::HTTP request adapter
19
+ # @see NetHTTP::Response Net::HTTP response adapter
6
20
  class Abstract
21
+ # @raise [Error] This class cannot be instantiated directly
7
22
  def initialize(operation, **options)
8
23
  raise Linzer::Error, "Cannot instantiate an abstract class!"
9
24
  end
10
25
 
26
+ # Checks if this adapter wraps an HTTP request.
27
+ # @return [Boolean] true if the wrapped message is a request
11
28
  def request?
12
29
  self.class.to_s.include?("Request")
13
30
  end
14
31
 
32
+ # Checks if this adapter wraps an HTTP response.
33
+ # @return [Boolean] true if the wrapped message is a response
15
34
  def response?
16
35
  self.class.to_s.include?("Response")
17
36
  end
18
37
 
38
+ # Checks if this response has an attached request.
39
+ #
40
+ # Attached requests enable the `;req` parameter for accessing
41
+ # request fields from a response signature.
42
+ #
43
+ # @return [Boolean] true if an attached request is present
19
44
  def attached_request?
20
45
  response? && !!@attached_request
21
46
  end
22
47
 
48
+ # Checks if a component exists in the message.
49
+ #
50
+ # @param f [String] The component identifier
51
+ # @return [Boolean] true if the component can be retrieved
23
52
  def field?(f)
24
53
  !!self[f]
25
54
  end
26
55
 
56
+ # Retrieves a component value from the message.
57
+ #
58
+ # Handles both regular header fields and derived components,
59
+ # including parameter processing (`;sf`, `;bs`, `;req`, `;key`).
60
+ #
61
+ # @param field [String, FieldId] The component identifier
62
+ # @return [String, Integer, nil] The component value, or nil if not found
63
+ #
64
+ # @example Header field
65
+ # adapter["content-type"] # => "application/json"
66
+ #
67
+ # @example Derived component
68
+ # adapter["@method"] # => "POST"
69
+ #
70
+ # @example With structured field parameter
71
+ # adapter['"example-dict";key="a"'] # => "1"
27
72
  def [](field)
28
73
  field_id = field.is_a?(FieldId) ? field : parse_field_name(field)
29
74
  return nil if field_id.nil? || field_id.item.nil?
30
75
  retrieve(field_id.item, field_id.derived? ? :derived : :field)
31
76
  end
32
77
 
78
+ # Retrieves a raw header value by name.
79
+ #
80
+ # @abstract Subclasses must implement this method.
81
+ # @param name [String] The header name
82
+ # @return [String, nil] The header value
33
83
  def header(name)
34
84
  raise Linzer::Error, "Sub-classes are required to implement this method!"
35
85
  end
36
86
 
87
+ # Attaches a signature to the underlying HTTP message.
88
+ #
89
+ # @abstract Subclasses must implement this method.
90
+ # @param signature [Signature] The signature to attach
91
+ # @return [Object] The underlying HTTP message
37
92
  def attach!(signature)
38
93
  raise Linzer::Error, "Sub-classes are required to implement this method!"
39
94
  end
40
95
 
41
96
  private
42
97
 
98
+ # Parses a field name string into a FieldId.
99
+ # @return [FieldId, nil] The parsed identifier, or nil if invalid
43
100
  def parse_field_name(field_name)
44
101
  field_id = FieldId.new(field_name: field_name)
45
102
  component = field_id.item
46
103
 
47
104
  return nil if component.nil?
48
105
 
49
- # 2.2.9
106
+ # RFC 9421 Section 2.2.9: @status is only valid for responses
50
107
  invalid = "@status component identifier is invalid in a request message"
51
108
  raise Error, invalid if request? && component.value == "@status"
52
109
 
53
110
  field_id
54
111
  end
55
112
 
113
+ # Validates that an attached message is a request.
114
+ # @raise [Error] If the message is not a request
56
115
  def validate_attached_request(message)
57
116
  msg = "The attached message is not a valid HTTP request!"
58
117
  raise Linzer::Error, msg unless message.request?
59
118
  end
60
119
 
120
+ # Validates component identifier parameters.
121
+ # @return [Object, nil] The validated name, or nil if invalid
61
122
  def validate_parameters(name, method)
62
123
  has_unknown = name.parameters.any? { |p, _| !KNOWN_PARAMETERS.include?(p) }
63
124
  return nil if has_unknown
@@ -68,29 +129,26 @@ module Linzer
68
129
  has_bs = name.parameters["bs"]
69
130
  value = name.value
70
131
 
71
- # Section 2.2.8 of RFC 9421
132
+ # Section 2.2.8 of RFC 9421: name param only for @query-param
72
133
  return nil if has_name && value != "@query-param"
73
134
 
74
135
  # No derived values come from trailers section
75
136
  return nil if method == :derived && name.parameters["tr"]
76
137
 
77
- # From: 2.1. HTTP Fields:
78
- # The bs parameter, which requires the raw bytes of the field values
79
- # from the message, is not compatible with the use of the sf or key
80
- # parameters, which require the parsed data structures of the field
81
- # values after combination
138
+ # RFC 9421 Section 2.1: bs incompatible with sf/key
82
139
  return nil if has_sf && has_bs
83
140
 
84
- # req param only makes sense on responses with an associated request
85
- # return nil if has_req && (!response? || !attached_request?)
141
+ # req param only makes sense on responses
86
142
  return nil if has_req && !response?
87
143
 
88
144
  name
89
145
  end
90
146
 
147
+ # Known component identifier parameters from RFC 9421.
91
148
  KNOWN_PARAMETERS = %w[sf key bs req tr name]
92
149
  private_constant :KNOWN_PARAMETERS
93
150
 
151
+ # Retrieves a component value with parameter processing.
94
152
  def retrieve(name, method)
95
153
  if !name.parameters.empty?
96
154
  valid_params = validate_parameters(name, method)
@@ -117,6 +175,8 @@ module Linzer
117
175
  end
118
176
  end
119
177
 
178
+ # Processes a structured field value with optional key extraction.
179
+ # @see https://www.rfc-editor.org/rfc/rfc9421.html#section-2.1.1
120
180
  def sf(value, key = nil)
121
181
  dict = Starry.parse_dictionary(value)
122
182
 
@@ -128,14 +188,19 @@ module Linzer
128
188
  end
129
189
  end
130
190
 
191
+ # Binary-wraps a field value.
192
+ # @see https://www.rfc-editor.org/rfc/rfc9421.html#section-2.1.3
131
193
  def bs(value)
132
194
  Starry.serialize(value.encode(Encoding::ASCII_8BIT))
133
195
  end
134
196
 
197
+ # Retrieves a trailer field value.
198
+ # @abstract Subclasses should implement if trailer support is needed.
135
199
  def tr(trailer)
136
- @operation.body.trailers[trailer.value.to_s]
200
+ raise Error, "Sub-classes are required to implement this method!"
137
201
  end
138
202
 
203
+ # Retrieves a field from the attached request.
139
204
  def req(field, method)
140
205
  attached_request? ? @attached_request[String(field)] : nil
141
206
  end
@@ -5,17 +5,44 @@ require "cgi"
5
5
  module Linzer
6
6
  class Message
7
7
  module Adapter
8
+ # Generic adapters for HTTP messages.
9
+ #
10
+ # These adapters provide a base implementation that can be extended
11
+ # for HTTP libraries not directly supported by Linzer.
8
12
  module Generic
13
+ # Generic HTTP request adapter.
14
+ #
15
+ # Provides a base implementation for request message access.
16
+ # Assumes the operation responds to `[]` for header access and
17
+ # has a `uri` attribute.
18
+ #
19
+ # @example Creating a custom adapter
20
+ # class MyRequestAdapter < Linzer::Message::Adapter::Generic::Request
21
+ # private
22
+ # def derived(name)
23
+ # return @operation.http_method if name.value == "@method"
24
+ # super
25
+ # end
26
+ # end
9
27
  class Request < Abstract
28
+ # Creates a new request adapter.
29
+ # @param operation [Object] The HTTP request object
30
+ # @param options [Hash] Additional options (unused in base class)
10
31
  def initialize(operation, **options)
11
32
  @operation = operation
12
33
  freeze
13
34
  end
14
35
 
36
+ # Retrieves a header value by name.
37
+ # @param name [String] The header name
38
+ # @return [String, nil] The header value
15
39
  def header(name)
16
40
  @operation[name]
17
41
  end
18
42
 
43
+ # Attaches a signature to the request.
44
+ # @param signature [Signature] The signature to attach
45
+ # @return [Object] The underlying request object
19
46
  def attach!(signature)
20
47
  signature.to_h.each { |h, v| @operation[h] = v }
21
48
  @operation
@@ -4,7 +4,18 @@ module Linzer
4
4
  class Message
5
5
  module Adapter
6
6
  module Generic
7
+ # Generic HTTP response adapter.
8
+ #
9
+ # Provides a base implementation for response message access.
10
+ # Assumes the operation responds to `[]` for header access.
11
+ #
12
+ # @abstract Subclass must implement {#derived} method.
7
13
  class Response < Abstract
14
+ # Creates a new response adapter.
15
+ # @param operation [Object] The HTTP response object
16
+ # @param options [Hash] Additional options
17
+ # @option options [Object] :attached_request An associated request
18
+ # for `;req` parameter support
8
19
  def initialize(operation, **options)
9
20
  @operation = operation
10
21
  attached_request = options[:attached_request]
@@ -13,10 +24,16 @@ module Linzer
13
24
  freeze
14
25
  end
15
26
 
27
+ # Retrieves a header value by name.
28
+ # @param name [String] The header name
29
+ # @return [String, nil] The header value
16
30
  def header(name)
17
31
  @operation[name]
18
32
  end
19
33
 
34
+ # Attaches a signature to the response.
35
+ # @param signature [Signature] The signature to attach
36
+ # @return [Object] The underlying response object
20
37
  def attach!(signature)
21
38
  signature.to_h.each { |h, v| @operation[h] = v }
22
39
  @operation
@@ -3,7 +3,18 @@
3
3
  module Linzer
4
4
  class Message
5
5
  module Adapter
6
+ # http.rb gem message adapters.
7
+ #
8
+ # Provides adapters for {HTTP::Request} and {HTTP::Response} objects
9
+ # from the http.rb gem.
10
+ #
11
+ # @note These adapters are loaded on-demand when using the
12
+ # {Linzer::HTTP::SignatureFeature}.
6
13
  module HTTPGem
14
+ # Adapter for {HTTP::Request} objects from http.rb gem.
15
+ #
16
+ # Extends the generic request adapter with http.rb-specific
17
+ # method name retrieval.
7
18
  class Request < Generic::Request
8
19
  private
9
20
 
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # HTTP message adapter for HTTP::Response class from http ruby gem.
4
- # https://github.com/httprb/http
5
- #
6
- # It's not loaded automatically to avoid making http gem a dependency.
7
- #
8
3
  module Linzer
9
4
  class Message
10
5
  module Adapter
11
6
  module HTTPGem
7
+ # Adapter for {HTTP::Response} objects from http.rb gem.
8
+ #
9
+ # Extends the generic response adapter with http.rb-specific
10
+ # status code retrieval.
11
+ #
12
+ # @note Not loaded automatically to avoid making http gem a dependency.
13
+ #
14
+ # @see https://github.com/httprb/http http.rb gem
12
15
  class Response < Generic::Response
13
16
  private
14
17
 
@@ -3,7 +3,14 @@
3
3
  module Linzer
4
4
  class Message
5
5
  module Adapter
6
+ # Net::HTTP message adapters.
7
+ #
8
+ # Provides adapters for {Net::HTTPRequest} and {Net::HTTPResponse} objects.
6
9
  module NetHTTP
10
+ # Adapter for {Net::HTTPRequest} objects.
11
+ #
12
+ # Extends the generic request adapter with Net::HTTP-specific
13
+ # method name retrieval.
7
14
  class Request < Generic::Request
8
15
  private
9
16
 
@@ -4,6 +4,10 @@ module Linzer
4
4
  class Message
5
5
  module Adapter
6
6
  module NetHTTP
7
+ # Adapter for {Net::HTTPResponse} objects.
8
+ #
9
+ # Extends the generic response adapter with Net::HTTP-specific
10
+ # status code retrieval.
7
11
  class Response < Generic::Response
8
12
  private
9
13
 
@@ -3,7 +3,12 @@
3
3
  module Linzer
4
4
  class Message
5
5
  module Adapter
6
+ # Rack HTTP message adapters.
7
+ #
8
+ # Provides adapters for {::Rack::Request} and {::Rack::Response} objects.
6
9
  module Rack
10
+ # Shared functionality for Rack request and response adapters.
11
+ # @api private
7
12
  module Common
8
13
  DERIVED_COMPONENT = {
9
14
  "@method" => :request_method,
@@ -61,14 +66,17 @@ module Linzer
61
66
 
62
67
  def field(name)
63
68
  has_tr = name.parameters["tr"]
64
- if has_tr
65
- value = tr(name)
69
+ return nil if has_tr
70
+
71
+ item_value = String(name.value)
72
+ field_value = if request?
73
+ rack_header_name = rack_header_name(item_value)
74
+ @operation.env[rack_header_name]
66
75
  else
67
- rack_header_name = rack_header_name(name.value.to_s)
68
- value = @operation.env[rack_header_name] if request?
69
- value = @operation.get_header(name.value.to_s) if response?
76
+ @operation.get_header(item_value)
70
77
  end
71
- value.dup&.strip
78
+
79
+ field_value.dup&.strip
72
80
  end
73
81
 
74
82
  def derive(operation, method)
@@ -4,19 +4,32 @@ module Linzer
4
4
  class Message
5
5
  module Adapter
6
6
  module Rack
7
+ # Adapter for {::Rack::Request} objects.
8
+ #
9
+ # Handles the Rack-specific header naming conventions (HTTP_* prefix,
10
+ # uppercase, underscores).
7
11
  class Request < Abstract
8
12
  include Common
9
13
 
14
+ # Creates a new Rack request adapter.
15
+ # @param operation [::Rack::Request] The Rack request
16
+ # @param options [Hash] Additional options (unused)
10
17
  def initialize(operation, **options)
11
18
  @operation = operation
12
19
  validate
13
20
  freeze
14
21
  end
15
22
 
23
+ # Retrieves a header value by name.
24
+ # @param name [String] The header name (e.g., "content-type")
25
+ # @return [String, nil] The header value
16
26
  def header(name)
17
27
  @operation.get_header(rack_header_name(name))
18
28
  end
19
29
 
30
+ # Attaches a signature to the request.
31
+ # @param signature [Signature] The signature to attach
32
+ # @return [::Rack::Request] The request with signature headers
20
33
  def attach!(signature)
21
34
  signature.to_h.each do |h, v|
22
35
  @operation.set_header(rack_header_name(h), v)
@@ -4,9 +4,14 @@ module Linzer
4
4
  class Message
5
5
  module Adapter
6
6
  module Rack
7
+ # Adapter for {::Rack::Response} objects.
7
8
  class Response < Abstract
8
9
  include Common
9
10
 
11
+ # Creates a new Rack response adapter.
12
+ # @param operation [::Rack::Response] The Rack response
13
+ # @param options [Hash] Additional options
14
+ # @option options [Object] :attached_request Request for `;req` support
10
15
  def initialize(operation, **options)
11
16
  @operation = operation
12
17
  validate
@@ -16,10 +21,16 @@ module Linzer
16
21
  freeze
17
22
  end
18
23
 
24
+ # Retrieves a header value by name.
25
+ # @param name [String] The header name
26
+ # @return [String, nil] The header value
19
27
  def header(name)
20
28
  @operation.get_header(name)
21
29
  end
22
30
 
31
+ # Attaches a signature to the response.
32
+ # @param signature [Signature] The signature to attach
33
+ # @return [::Rack::Response] The response with signature headers
23
34
  def attach!(signature)
24
35
  signature.to_h.each do |h, v|
25
36
  @operation.set_header(h, v)
@@ -8,3 +8,20 @@ require_relative "adapter/rack/request"
8
8
  require_relative "adapter/rack/response"
9
9
  require_relative "adapter/net_http/request"
10
10
  require_relative "adapter/net_http/response"
11
+
12
+ module Linzer
13
+ class Message
14
+ # Namespace for HTTP message adapters.
15
+ #
16
+ # Adapters provide a unified interface for accessing HTTP message
17
+ # components across different HTTP libraries. Each supported library
18
+ # has its own adapter implementation.
19
+ #
20
+ # @see Abstract Base adapter class
21
+ # @see Rack Rack request/response adapters
22
+ # @see NetHTTP Net::HTTP request/response adapters
23
+ # @see Generic Generic adapters for extension
24
+ module Adapter
25
+ end
26
+ end
27
+ end
@@ -4,9 +4,23 @@ module Linzer
4
4
  class Message
5
5
  class Field
6
6
  class Identifier
7
+ # Parses component identifier strings into structured items.
8
+ #
9
+ # Handles various formats:
10
+ # - Simple names: `"content-type"`
11
+ # - Derived components: `"@method"`
12
+ # - With parameters: `"content-type";bs`, `"example-dict";key="a"`
13
+ # - Already serialized: `'"content-type"'`
14
+ #
15
+ # @api private
7
16
  module Parser
8
17
  extend self
9
18
 
19
+ # Parses a field name into a structured item.
20
+ #
21
+ # @param field_name [String] The component identifier string
22
+ # @return [Starry::Item] The parsed structured field item
23
+ # @raise [Error] If the field name cannot be parsed
10
24
  def parse(field_name)
11
25
  case
12
26
  when field_name.match?(/";/), field_name.start_with?('"')