protocol-content 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0631b08562d204932666cba81cddae2505c9dbf9d9c53414493e156aee864c91
4
+ data.tar.gz: 7c7c22c2fb998b2b6839dcf11e8c20e52de69ef1c11eed26c12014bc27f03401
5
+ SHA512:
6
+ metadata.gz: a9d13f6dc00e82e2108083e9069577a4f6a910789bdbe1cc9ee7701a077359ed052a1ad06cd55f0112ff5f833bda4b3581d041934e594d94d5e56405d31a33ee
7
+ data.tar.gz: 01ac8df9eefddbdde84e412a77a7c8b7848dac3116b04225cc097ccf6903f7644daee17a962dd5e7be9d4000fb86b38083dd803453758c0612034bd0f3d764ca
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,5 @@
1
+ @.�ܛ�� �:��LAuc�)ɡ. W�L*�$z�n.j��{�n
2
+ B1U T�3߀��Ϭ*��H�Njm`�;�>��<c*F��dYF$}x��?#lX����2_v~�Z4>�&E�'�6l����N��
3
+ �*�'��`s8��(������?���Lҫ�Ϙ�3�+���� vr�f[�8Q/� hڵc�#[!�R��d�K9Nu`�6+��l��L!,˾��Ҋ�b��R��fp�����m,Q� ������
4
+ G,��z�Cb�(�@�uiD��j�#Z*#���h�ɵ`�Q"��m�
5
+ �*��U�d�xn����0,����IՂ��.o߽oH��R/u�[i)F��{�y�9*
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "../content"
7
+ require_relative "json_parser"
8
+
9
+ require "protocol/url/form_data/parser"
10
+ require "protocol/multipart/form_data/parser"
11
+
12
+ module Protocol
13
+ module Content
14
+ class Parser
15
+ DEFAULT = build do |parser|
16
+ json_parser = JSONParser.new
17
+ parser.register(JSONParser::MEDIA_TYPE) do |input|
18
+ json_parser.parse(input)
19
+ end
20
+
21
+ url_encoded_form_parser = Protocol::URL::FormData::Parser.new
22
+ parser.register(Protocol::URL::FormData::Parser::MEDIA_TYPE) do |input, _media_type, &block|
23
+ url_encoded_form_parser.parse(input, &block)
24
+ rescue Protocol::URL::LimitError
25
+ raise ContentTooLargeError
26
+ end
27
+
28
+ multipart_form_parser = Protocol::Multipart::FormData::Parser.new
29
+ parser.register(Protocol::Multipart::FormData::Parser::MEDIA_TYPE) do |input, media_type, &block|
30
+ if boundary = media_type.parameters["boundary"]
31
+ multipart_form_parser.parse(input, boundary: boundary, &block)
32
+ else
33
+ raise ArgumentError, "Multipart media type is missing a boundary!"
34
+ end
35
+ rescue Protocol::Multipart::LimitError, Protocol::URL::LimitError
36
+ raise ContentTooLargeError
37
+ end
38
+ end
39
+
40
+ # The default parser for common media types.
41
+ # @returns [Parser] The frozen default parser.
42
+ def self.default
43
+ return DEFAULT
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Protocol
7
+ module Content
8
+ # A generic content error.
9
+ class Error < StandardError
10
+ end
11
+
12
+ # Raised when content cannot be parsed.
13
+ class ParseError < Error
14
+ end
15
+
16
+ # Raised when content exceeds a configured parser limit.
17
+ class ContentTooLargeError < ParseError
18
+ end
19
+
20
+ # Raised when no parser accepts a media type.
21
+ class UnsupportedMediaTypeError < Error
22
+ # Initialize the error.
23
+ # @parameter media_type [Protocol::Media::Type | Nil] The unsupported media type.
24
+ def initialize(media_type)
25
+ if media_type
26
+ super("Unsupported media type: #{media_type}")
27
+ else
28
+ super("Missing media type!")
29
+ end
30
+
31
+ @media_type = media_type
32
+ end
33
+
34
+ # The unsupported media type, if one was provided.
35
+ attr :media_type
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "json"
7
+
8
+ require_relative "error"
9
+
10
+ module Protocol
11
+ module Content
12
+ # Parses JSON content with bounded input size and nesting depth.
13
+ class JSONParser
14
+ MEDIA_TYPE = "application/json"
15
+
16
+ # The encoded JSON document size limit.
17
+ SIZE_LIMIT = 2 * 1024 * 1024
18
+
19
+ # The JSON document nesting depth limit.
20
+ DEPTH_LIMIT = 32
21
+
22
+ # Initialize the JSON parser.
23
+ # @parameter size_limit [Integer | Nil] The encoded document size limit.
24
+ # @parameter depth_limit [Integer | Nil] The document nesting depth limit.
25
+ # @parameter options [Hash] Options passed to `JSON.parse`.
26
+ def initialize(size_limit: SIZE_LIMIT, depth_limit: DEPTH_LIMIT, **options)
27
+ @size_limit = size_limit
28
+ options[:max_nesting] = depth_limit || false
29
+ @options = options
30
+ end
31
+
32
+ # Parse JSON content.
33
+ # @parameter input [Object] The readable input.
34
+ # @returns [Object] The decoded JSON value.
35
+ def parse(input)
36
+ if @size_limit
37
+ buffer = String.new.b
38
+
39
+ # Read up to the size limit, allowing for partial reads:
40
+ while buffer.bytesize < @size_limit
41
+ chunk = input.read(@size_limit - buffer.bytesize)
42
+ break unless chunk
43
+ # An empty chunk cannot make progress, so stop reading:
44
+ break if chunk.empty?
45
+
46
+ buffer << chunk
47
+ end
48
+
49
+ if buffer.bytesize == @size_limit && input.read(1)
50
+ raise ContentTooLargeError, "JSON content size exceeded limit of #{@size_limit}!"
51
+ end
52
+ else
53
+ buffer = input.read
54
+ end
55
+
56
+ return JSON.parse(buffer, **@options)
57
+ rescue JSON::NestingError
58
+ raise ContentTooLargeError
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "protocol/media/map"
7
+ require "protocol/media/type"
8
+
9
+ require_relative "error"
10
+
11
+ module Protocol
12
+ module Content
13
+ # Selects a content parser according to its media type.
14
+ class Parser
15
+ # Build and freeze a parser.
16
+ # @yields {|parser| ...} The mutable parser being configured.
17
+ # @parameter parser [Parser] The parser being configured.
18
+ # @returns [Parser] The configured parser.
19
+ def self.build
20
+ parser = self.new
21
+ yield parser
22
+ return parser.freeze
23
+ end
24
+
25
+ # Initialize an empty parser.
26
+ def initialize
27
+ @handlers = Protocol::Media::Map.new
28
+ end
29
+
30
+ # Register a handler for a media type or range.
31
+ # @parameter media_range [String | Protocol::Media::Range] The accepted media type or range.
32
+ # @parameter handler [#call | Nil] The content handler.
33
+ # @yields {|input, media_type| ...} The content to parse.
34
+ # @parameter input [Object] The readable input.
35
+ # @parameter media_type [Protocol::Media::Type] The parsed media type.
36
+ # @returns [#call] The registered handler.
37
+ def register(media_range, handler = nil, &block)
38
+ if handler && block
39
+ raise ArgumentError, "Provide either a handler or a block!"
40
+ end
41
+
42
+ handler ||= block
43
+
44
+ unless handler&.respond_to?(:call)
45
+ raise ArgumentError, "A content handler must respond to #call!"
46
+ end
47
+
48
+ @handlers[media_range] = handler
49
+ return handler
50
+ end
51
+
52
+ # Parse content using the handler matching its media type.
53
+ # @parameter media_type [String | Protocol::Media::Type | Nil] The media type.
54
+ # @parameter input [Object] The readable input.
55
+ # @yields {...} An optional block forwarded to the selected content handler.
56
+ # @returns [Object] The parsed content value.
57
+ def parse(media_type, input, &block)
58
+ media_type = Protocol::Media::Type.for(media_type)
59
+
60
+ if media_type && handler = @handlers[media_type]
61
+ return handler.call(input, media_type, &block)
62
+ end
63
+
64
+ raise UnsupportedMediaTypeError, media_type
65
+ end
66
+
67
+ # Freeze the parser and its handler registry.
68
+ # @returns [self] The frozen parser.
69
+ def freeze
70
+ @handlers.freeze
71
+ super
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ # @namespace
7
+ module Protocol
8
+ # Models media-typed content carried by protocol messages.
9
+ module Content
10
+ VERSION = "0.1.0"
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "content/version"
7
+ require_relative "content/error"
8
+ require_relative "content/parser"
9
+
10
+ module Protocol
11
+ # @namespace
12
+ module Content
13
+ end
14
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2026, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,63 @@
1
+ # Protocol::Content
2
+
3
+ Provides transport-independent parsing for media-typed content.
4
+
5
+ [![Development Status](https://github.com/socketry/protocol-content/workflows/Test/badge.svg)](https://github.com/socketry/protocol-content/actions?workflow=Test)
6
+
7
+ ## Usage
8
+
9
+ Please see the [project documentation](https://socketry.github.io/protocol-content/) for more details.
10
+
11
+ - [Getting Started](https://socketry.github.io/protocol-content/guides/getting-started/index) - This guide explains how to parse media-typed content using built-in and custom parsers.
12
+
13
+ ## Releases
14
+
15
+ Please see the [project releases](https://socketry.github.io/protocol-content/releases/index) for all releases.
16
+
17
+ ### v0.1.0
18
+
19
+ - Add media-type parser dispatch for readable content.
20
+ - Add JSON, URL-encoded form, and multipart form parsers with explicit convenient defaults.
21
+ - Bound JSON input size and nesting depth using consistently named limits.
22
+ - Add `ContentTooLargeError` for content parser limit violations.
23
+ - Forward blocks to content handlers for incremental and streaming parsing.
24
+
25
+ ## Contributing
26
+
27
+ We welcome contributions to this project.
28
+
29
+ 1. Fork it.
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
31
+ 3. Commit your changes (`git commit -am 'Add some feature.'`).
32
+ 4. Push to the branch (`git push origin my-new-feature`).
33
+ 5. Create a new pull request.
34
+
35
+ ### Running Tests
36
+
37
+ To run the test suite:
38
+
39
+ ``` shell
40
+ bundle exec sus
41
+ ```
42
+
43
+ ### Making Releases
44
+
45
+ To make a new release:
46
+
47
+ ``` shell
48
+ bundle exec bake gem:release:patch # or minor or major
49
+ ```
50
+
51
+ ### Developer Certificate of Origin
52
+
53
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
54
+
55
+ ### Community Guidelines
56
+
57
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
58
+
59
+ ## See Also
60
+
61
+ - [protocol-http](https://github.com/socketry/protocol-http) provides HTTP message abstractions.
62
+ - [protocol-media](https://github.com/socketry/protocol-media) provides media type abstractions.
63
+ - [async-rest](https://github.com/socketry/async-rest) provides asynchronous REST client abstractions.
data/releases.md ADDED
@@ -0,0 +1,9 @@
1
+ # Releases
2
+
3
+ ## v0.1.0
4
+
5
+ - Add media-type parser dispatch for readable content.
6
+ - Add JSON, URL-encoded form, and multipart form parsers with explicit convenient defaults.
7
+ - Bound JSON input size and nesting depth using consistently named limits.
8
+ - Add `ContentTooLargeError` for content parser limit violations.
9
+ - Forward blocks to content handlers for incremental and streaming parsing.
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protocol-content
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ bindir: bin
9
+ cert_chain:
10
+ - |
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
13
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
14
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
15
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
16
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
17
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
18
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
19
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
20
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
21
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
22
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
23
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
24
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
25
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
26
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
27
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
28
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
30
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
31
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
32
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
33
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
34
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
35
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
36
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
37
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
38
+ -----END CERTIFICATE-----
39
+ date: 1980-01-02 00:00:00.000000000 Z
40
+ dependencies:
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: protocol-media
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: protocol-multipart
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.6'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: protocol-url
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.10'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.10'
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - lib/protocol/content.rb
102
+ - lib/protocol/content/default.rb
103
+ - lib/protocol/content/error.rb
104
+ - lib/protocol/content/json_parser.rb
105
+ - lib/protocol/content/parser.rb
106
+ - lib/protocol/content/version.rb
107
+ - license.md
108
+ - readme.md
109
+ - releases.md
110
+ homepage: https://github.com/socketry/protocol-content
111
+ licenses:
112
+ - MIT
113
+ metadata:
114
+ bug_tracker_uri: https://github.com/socketry/protocol-content/issues
115
+ changelog_uri: https://github.com/socketry/protocol-content/blob/main/releases.md
116
+ documentation_uri: https://socketry.github.io/protocol-content/
117
+ source_code_uri: https://github.com/socketry/protocol-content.git
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '3.3'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubygems_version: 4.0.10
133
+ specification_version: 4
134
+ summary: Provides parsing for media-typed content.
135
+ test_files: []
metadata.gz.sig ADDED
Binary file