ciborium 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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE.md +26 -0
  5. data/README.md +221 -0
  6. data/Rakefile +46 -0
  7. data/ext/ciborium/Cargo.lock +339 -0
  8. data/ext/ciborium/Cargo.toml +26 -0
  9. data/ext/ciborium/build.rs +4 -0
  10. data/ext/ciborium/extconf.rb +8 -0
  11. data/ext/ciborium/src/canonical.rs +60 -0
  12. data/ext/ciborium/src/cursor.rs +106 -0
  13. data/ext/ciborium/src/decode.rs +325 -0
  14. data/ext/ciborium/src/encode.rs +330 -0
  15. data/ext/ciborium/src/error.rs +53 -0
  16. data/ext/ciborium/src/lib.rs +153 -0
  17. data/ext/ciborium/src/scan.rs +223 -0
  18. data/lib/ciborium/compat/buffer.rb +190 -0
  19. data/lib/ciborium/compat/errors.rb +30 -0
  20. data/lib/ciborium/compat/packer.rb +90 -0
  21. data/lib/ciborium/compat/unpacker.rb +111 -0
  22. data/lib/ciborium/compat.rb +110 -0
  23. data/lib/ciborium/decode_options.rb +61 -0
  24. data/lib/ciborium/decoder.rb +141 -0
  25. data/lib/ciborium/encode_options.rb +41 -0
  26. data/lib/ciborium/encoded.rb +10 -0
  27. data/lib/ciborium/encoder.rb +80 -0
  28. data/lib/ciborium/errors.rb +25 -0
  29. data/lib/ciborium/extensions/bignum.rb +35 -0
  30. data/lib/ciborium/extensions/regexp.rb +41 -0
  31. data/lib/ciborium/extensions/time.rb +39 -0
  32. data/lib/ciborium/extensions/uri.rb +29 -0
  33. data/lib/ciborium/extensions.rb +92 -0
  34. data/lib/ciborium/native.rb +25 -0
  35. data/lib/ciborium/replace.rb +105 -0
  36. data/lib/ciborium/simple.rb +21 -0
  37. data/lib/ciborium/tagged.rb +18 -0
  38. data/lib/ciborium/version.rb +6 -0
  39. data/lib/ciborium.rb +65 -0
  40. data/sig/generated/ciborium/compat/buffer.rbs +95 -0
  41. data/sig/generated/ciborium/compat/errors.rbs +23 -0
  42. data/sig/generated/ciborium/compat/packer.rbs +53 -0
  43. data/sig/generated/ciborium/compat/unpacker.rbs +59 -0
  44. data/sig/generated/ciborium/compat.rbs +57 -0
  45. data/sig/generated/ciborium/decode_options.rbs +41 -0
  46. data/sig/generated/ciborium/decoder.rbs +85 -0
  47. data/sig/generated/ciborium/encode_options.rbs +33 -0
  48. data/sig/generated/ciborium/encoded.rbs +16 -0
  49. data/sig/generated/ciborium/encoder.rbs +50 -0
  50. data/sig/generated/ciborium/errors.rbs +32 -0
  51. data/sig/generated/ciborium/extensions/bignum.rbs +20 -0
  52. data/sig/generated/ciborium/extensions/regexp.rbs +30 -0
  53. data/sig/generated/ciborium/extensions/time.rbs +22 -0
  54. data/sig/generated/ciborium/extensions/uri.rbs +20 -0
  55. data/sig/generated/ciborium/extensions.rbs +35 -0
  56. data/sig/generated/ciborium/native.rbs +10 -0
  57. data/sig/generated/ciborium/replace.rbs +37 -0
  58. data/sig/generated/ciborium/version.rbs +5 -0
  59. data/sig/generated/ciborium.rbs +30 -0
  60. data/sig/manual/ciborium/extensions.rbs +12 -0
  61. data/sig/manual/ciborium/native.rbs +23 -0
  62. data/sig/manual/ciborium/tagged.rbs +25 -0
  63. metadata +123 -0
@@ -0,0 +1,223 @@
1
+ enum Pending {
2
+ Items(usize),
3
+ Indefinite { map: bool, items: usize },
4
+ Tag,
5
+ // Chunks of an indefinite-length byte (false) or text (true) string.
6
+ Chunks { text: bool },
7
+ }
8
+
9
+ /// Where a scan of an incomplete item stopped: the offset of the first head it could not read,
10
+ /// relative to the start of the item, and the containers open at that point.
11
+ #[derive(Default)]
12
+ struct Progress {
13
+ resume: usize,
14
+ stack: Vec<Pending>,
15
+ }
16
+
17
+ /// The progress of scanning one item across calls, so a streaming decoder fed in parts scans
18
+ /// each byte once rather than once per part.
19
+ #[magnus::wrap(class = "Ciborium::Native::ScanState", free_immediately)]
20
+ pub(crate) struct ScanState(std::cell::RefCell<Progress>);
21
+
22
+ impl ScanState {
23
+ pub(crate) fn new() -> Self {
24
+ Self(std::cell::RefCell::new(Progress::default()))
25
+ }
26
+
27
+ /// Forgets the progress, for when the next scan starts at another item.
28
+ pub(crate) fn clear(&self) {
29
+ *self.0.borrow_mut() = Progress::default();
30
+ }
31
+ }
32
+
33
+ pub(crate) enum Scan {
34
+ Complete(usize),
35
+ Incomplete,
36
+ }
37
+
38
+ /// Finds where the data item at `pos` ends without building Ruby objects, checking the same
39
+ /// well-formedness rules as the decoder except those that need the decoded values.
40
+ pub(crate) struct Scanner<'a> {
41
+ cursor: crate::cursor::Cursor<'a>,
42
+ max_depth: usize,
43
+ stack: Vec<Pending>,
44
+ }
45
+
46
+ impl<'a> Scanner<'a> {
47
+ pub(crate) fn new(input: &'a [u8], pos: usize, max_depth: usize) -> Self {
48
+ Self {
49
+ cursor: crate::cursor::Cursor::new(input, pos),
50
+ max_depth,
51
+ stack: Vec::new(),
52
+ }
53
+ }
54
+
55
+ /// Scans the item at `pos`, continuing from where `state` stopped, and records where this
56
+ /// scan stops when the item is still incomplete.
57
+ pub(crate) fn scan_resuming(
58
+ input: &'a [u8],
59
+ pos: usize,
60
+ max_depth: usize,
61
+ state: &ScanState,
62
+ ) -> Result<Scan, crate::error::Error> {
63
+ let mut progress = state.0.borrow_mut();
64
+ let mut scanner = crate::scan::Scanner::new(input, pos, max_depth);
65
+ if pos + progress.resume <= input.len() {
66
+ scanner.cursor = crate::cursor::Cursor::new(input, pos + progress.resume);
67
+ scanner.stack = std::mem::take(&mut progress.stack);
68
+ }
69
+ let result = scanner.run();
70
+ *progress = match result {
71
+ Ok(Scan::Incomplete) => Progress {
72
+ resume: scanner.cursor.pos() - pos,
73
+ stack: scanner.stack,
74
+ },
75
+ _ => Progress::default(),
76
+ };
77
+ result
78
+ }
79
+
80
+ pub(crate) fn scan(&mut self) -> Result<usize, crate::error::Error> {
81
+ match self.run()? {
82
+ Scan::Complete(end) => Ok(end),
83
+ Scan::Incomplete => Err(crate::error::Error::Incomplete),
84
+ }
85
+ }
86
+
87
+ /// Scans until the item ends or the input does. On running out of input, the cursor and
88
+ /// stack are left at the start of the head that could not be read in full.
89
+ fn run(&mut self) -> Result<Scan, crate::error::Error> {
90
+ loop {
91
+ let start = self.cursor.pos();
92
+ match self.step(start) {
93
+ Ok(true) => return Ok(Scan::Complete(self.cursor.pos())),
94
+ Ok(false) => {}
95
+ Err(crate::error::Error::Incomplete) => {
96
+ self.cursor = crate::cursor::Cursor::new(self.cursor.input(), start);
97
+ return Ok(Scan::Incomplete);
98
+ }
99
+ Err(error) => return Err(error),
100
+ }
101
+ }
102
+ }
103
+
104
+ /// Reads one head with its body, if any. Returns true once the top-level item is finished.
105
+ /// Leaves the stack untouched when the input ends before the head and body do.
106
+ fn step(&mut self, start: usize) -> Result<bool, crate::error::Error> {
107
+ let header = self.cursor.head()?;
108
+ if let Some(Pending::Chunks { text }) = self.stack.last() {
109
+ let text = *text;
110
+ match header {
111
+ ciborium_ll::Header::Break => {
112
+ self.stack.pop();
113
+ return Ok(self.complete());
114
+ }
115
+ ciborium_ll::Header::Bytes(Some(len)) if !text => {
116
+ self.cursor.string(Some(len), false)?;
117
+ }
118
+ ciborium_ll::Header::Text(Some(len)) if text => {
119
+ self.cursor.string(Some(len), true)?;
120
+ }
121
+ _ => {
122
+ return Err(crate::error::Error::malformed(format!(
123
+ "invalid chunk in indefinite-length string at offset {start}"
124
+ )));
125
+ }
126
+ }
127
+ return Ok(false);
128
+ }
129
+ match header {
130
+ ciborium_ll::Header::Positive(_)
131
+ | ciborium_ll::Header::Negative(_)
132
+ | ciborium_ll::Header::Float(_)
133
+ | ciborium_ll::Header::Simple(_)
134
+ | ciborium_ll::Header::Array(Some(0))
135
+ | ciborium_ll::Header::Map(Some(0)) => {}
136
+ ciborium_ll::Header::Bytes(Some(len)) => {
137
+ self.cursor.string(Some(len), false)?;
138
+ }
139
+ ciborium_ll::Header::Text(Some(len)) => {
140
+ self.cursor.string(Some(len), true)?;
141
+ }
142
+ ciborium_ll::Header::Bytes(None) => {
143
+ self.open(Pending::Chunks { text: false })?;
144
+ return Ok(false);
145
+ }
146
+ ciborium_ll::Header::Text(None) => {
147
+ self.open(Pending::Chunks { text: true })?;
148
+ return Ok(false);
149
+ }
150
+ ciborium_ll::Header::Array(Some(n)) => {
151
+ self.open(Pending::Items(n))?;
152
+ return Ok(false);
153
+ }
154
+ ciborium_ll::Header::Map(Some(n)) => {
155
+ self.open(Pending::Items(n.saturating_mul(2)))?;
156
+ return Ok(false);
157
+ }
158
+ ciborium_ll::Header::Array(None) => {
159
+ self.open(Pending::Indefinite {
160
+ map: false,
161
+ items: 0,
162
+ })?;
163
+ return Ok(false);
164
+ }
165
+ ciborium_ll::Header::Map(None) => {
166
+ self.open(Pending::Indefinite {
167
+ map: true,
168
+ items: 0,
169
+ })?;
170
+ return Ok(false);
171
+ }
172
+ ciborium_ll::Header::Tag(_) => {
173
+ self.open(Pending::Tag)?;
174
+ return Ok(false);
175
+ }
176
+ ciborium_ll::Header::Break => match self.stack.last() {
177
+ Some(Pending::Indefinite { map, items }) if !map || items % 2 == 0 => {
178
+ self.stack.pop();
179
+ }
180
+ _ => {
181
+ return Err(crate::error::Error::malformed(format!(
182
+ "unexpected break at offset {start}"
183
+ )));
184
+ }
185
+ },
186
+ }
187
+ Ok(self.complete())
188
+ }
189
+
190
+ fn open(&mut self, pending: Pending) -> Result<(), crate::error::Error> {
191
+ // An indefinite-length string adds no nesting the decoder counts, and holds only chunks,
192
+ // so it is always the innermost entry.
193
+ if !matches!(pending, Pending::Chunks { .. }) && self.stack.len() >= self.max_depth {
194
+ return Err(crate::error::Error::Depth(self.max_depth));
195
+ }
196
+ self.stack.push(pending);
197
+ Ok(())
198
+ }
199
+
200
+ /// Counts a finished item against its enclosing containers. Returns true once the top-level
201
+ /// item is finished.
202
+ fn complete(&mut self) -> bool {
203
+ loop {
204
+ match self.stack.last_mut() {
205
+ None => return true,
206
+ Some(Pending::Items(n)) => {
207
+ *n -= 1;
208
+ if *n > 0 {
209
+ return false;
210
+ }
211
+ }
212
+ Some(Pending::Indefinite { items, .. }) => {
213
+ *items += 1;
214
+ return false;
215
+ }
216
+ Some(Pending::Tag) => {}
217
+ // Popped before its string is counted, so never the innermost entry here.
218
+ Some(Pending::Chunks { .. }) => return false,
219
+ }
220
+ self.stack.pop();
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Compat
6
+ # A byte queue in front of an optional IO, as CBOR::Buffer of the cbor gem: reads refill from
7
+ # the IO when the queue runs dry, and writes go out to it once the queue fills.
8
+ class Buffer
9
+ DEFAULT_IO_BUFFER_SIZE = 32 * 1024 #: Integer
10
+
11
+ # @rbs @io: untyped
12
+ # @rbs @io_buffer_size: Integer
13
+ # @rbs @data: String
14
+ # @rbs @pos: Integer
15
+
16
+ attr_reader :io #: untyped
17
+
18
+ # The cbor gem's read_reference_threshold and write_reference_threshold tune its chunk
19
+ # sharing, which this buffer does not do, so they are accepted and ignored.
20
+ #
21
+ #: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
22
+ def initialize(io = nil, options = nil)
23
+ if options.nil? && io.is_a?(Hash)
24
+ options = io
25
+ io = nil
26
+ end
27
+ @io = io
28
+ @io_buffer_size = Integer(options ? options.fetch(:io_buffer_size, DEFAULT_IO_BUFFER_SIZE) : DEFAULT_IO_BUFFER_SIZE)
29
+ @data = "".b
30
+ @pos = 0
31
+ end
32
+
33
+ #: () -> nil
34
+ def clear
35
+ @data = "".b
36
+ @pos = 0
37
+ nil
38
+ end
39
+
40
+ #: () -> Integer
41
+ def size
42
+ @data.bytesize - @pos
43
+ end
44
+
45
+ #: () -> bool
46
+ def empty?
47
+ size.zero?
48
+ end
49
+
50
+ #: (String data) -> Integer
51
+ def write(data)
52
+ @data << data.b
53
+ flush if @io && size >= @io_buffer_size
54
+ data.bytesize
55
+ end
56
+
57
+ #: (String data) -> self
58
+ def <<(data)
59
+ write(data)
60
+ self
61
+ end
62
+
63
+ # Consumes up to n bytes, or everything without n, refilling from the IO until it ends.
64
+ # Returns nil when n is positive and nothing is left.
65
+ #
66
+ #: (?Integer? n, ?String? out) -> String?
67
+ def read(n = nil, out = nil)
68
+ return read_all(nil, out) if n.nil?
69
+ return replace(out, "".b) if n.zero?
70
+
71
+ fill(n)
72
+ return if empty?
73
+
74
+ replace(out, take(n))
75
+ end
76
+
77
+ # Consumes exactly n bytes, or everything without n. Raises EOFError, consuming nothing,
78
+ # when fewer than n bytes are left.
79
+ #
80
+ #: (?Integer? n, ?String? out) -> String
81
+ def read_all(n = nil, out = nil)
82
+ if n.nil?
83
+ fill(nil)
84
+ return replace(out, take(size))
85
+ end
86
+ return replace(out, "".b) if n.zero?
87
+
88
+ fill(n)
89
+ raise ::EOFError, "end of buffer reached" if size < n
90
+
91
+ replace(out, take(n))
92
+ end
93
+
94
+ # Consumes up to n bytes and returns how many it consumed.
95
+ #
96
+ #: (Integer n) -> Integer
97
+ def skip(n)
98
+ fill(n)
99
+ take(n).bytesize
100
+ end
101
+
102
+ # Consumes exactly n bytes, or raises EOFError consuming nothing.
103
+ #
104
+ #: (Integer n) -> self
105
+ def skip_all(n)
106
+ fill(n)
107
+ raise ::EOFError, "end of buffer reached" if size < n
108
+
109
+ take(n)
110
+ self
111
+ end
112
+
113
+ #: () -> String
114
+ def to_str
115
+ @data.byteslice(@pos..) || "".b
116
+ end
117
+
118
+ alias_method :to_s, :to_str
119
+
120
+ #: () -> Array[String]
121
+ def to_a
122
+ [to_str]
123
+ end
124
+
125
+ # Writes the queued bytes to the IO, if there is one.
126
+ #
127
+ #: () -> self
128
+ def flush
129
+ write_to(@io) if @io
130
+ self
131
+ end
132
+
133
+ #: () -> nil
134
+ def close
135
+ @io&.close
136
+ nil
137
+ end
138
+
139
+ # Writes the queued bytes to io and consumes them.
140
+ #
141
+ #: (untyped io) -> Integer
142
+ def write_to(io)
143
+ data = take(size)
144
+ io.write(data)
145
+ data.bytesize
146
+ end
147
+
148
+ #: (Integer? n) -> void
149
+ private def fill(n)
150
+ return unless @io
151
+
152
+ while n.nil? || size < n
153
+ chunk = read_io
154
+ break unless chunk
155
+
156
+ @data << chunk.b
157
+ end
158
+ end
159
+
160
+ #: () -> String?
161
+ private def read_io
162
+ if @io.respond_to?(:readpartial)
163
+ @io.readpartial(@io_buffer_size)
164
+ else
165
+ @io.read(@io_buffer_size)
166
+ end
167
+ rescue ::EOFError
168
+ nil
169
+ end
170
+
171
+ #: (Integer n) -> String
172
+ private def take(n)
173
+ data = @data.byteslice(@pos, n) || "".b
174
+ @pos += data.bytesize
175
+ if @pos == @data.bytesize
176
+ @data = "".b
177
+ @pos = 0
178
+ end
179
+ data
180
+ end
181
+
182
+ #: (String? out, String data) -> String
183
+ private def replace(out, data)
184
+ return data unless out
185
+
186
+ out.replace(data)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Compat
6
+ class UnpackError < StandardError; end
7
+
8
+ class MalformedFormatError < UnpackError; end
9
+
10
+ class StackError < UnpackError; end
11
+
12
+ # Raised by Unpacker#read_array_header and #read_map_header on an item of another type.
13
+ class TypeError < StandardError; end
14
+
15
+ # Raises the cbor gem's exception for a Ciborium decoding failure, keeping it as the cause.
16
+ #
17
+ #: [T] () { () -> T } -> T
18
+ def self.translate_errors
19
+ yield
20
+ rescue IncompleteError
21
+ raise ::EOFError, "end of buffer reached"
22
+ rescue DepthError
23
+ raise StackError, "nesting too deep"
24
+ rescue UnexpectedTypeError
25
+ raise TypeError, "unexpected type"
26
+ rescue DecodeError
27
+ raise MalformedFormatError, "malformed CBOR"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Compat
6
+ # CBOR::Packer of the cbor gem, writing through Ciborium::Encoder.
7
+ class Packer
8
+ # @rbs @buffer: Buffer
9
+ # @rbs @encoder: Ciborium::Encoder
10
+
11
+ attr_reader :buffer #: Buffer
12
+
13
+ #: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
14
+ def initialize(io = nil, options = nil)
15
+ @buffer = Buffer.new(io, options)
16
+ @encoder = Ciborium::Encoder.new(**Compat.encode_options)
17
+ end
18
+
19
+ #: (untyped object) -> self
20
+ def write(object)
21
+ @encoder.write(object)
22
+ drain
23
+ end
24
+
25
+ alias_method :pack, :write
26
+
27
+ #: () -> self
28
+ def write_nil
29
+ write(nil)
30
+ end
31
+
32
+ #: (Integer length) -> self
33
+ def write_array_header(length)
34
+ @encoder.write_array_header(length)
35
+ drain
36
+ end
37
+
38
+ #: (Integer length) -> self
39
+ def write_map_header(length)
40
+ @encoder.write_map_header(length)
41
+ drain
42
+ end
43
+
44
+ #: () -> self
45
+ def flush
46
+ @buffer.flush
47
+ self
48
+ end
49
+
50
+ #: () -> nil
51
+ def clear
52
+ @buffer.clear
53
+ end
54
+
55
+ #: () -> Integer
56
+ def size
57
+ @buffer.size
58
+ end
59
+
60
+ #: () -> bool
61
+ def empty?
62
+ @buffer.empty?
63
+ end
64
+
65
+ #: (untyped io) -> Integer
66
+ def write_to(io)
67
+ @buffer.write_to(io)
68
+ end
69
+
70
+ #: () -> String
71
+ def to_str
72
+ @buffer.to_str
73
+ end
74
+
75
+ alias_method :to_s, :to_str
76
+
77
+ #: () -> Array[String]
78
+ def to_a
79
+ @buffer.to_a
80
+ end
81
+
82
+ #: () -> self
83
+ private def drain
84
+ @buffer << @encoder.bytes
85
+ @encoder.clear
86
+ self
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Compat
6
+ # CBOR::Unpacker of the cbor gem, reading through Ciborium::Decoder. Input passes through
7
+ # buffer, which also reads from io when more is needed.
8
+ class Unpacker
9
+ # @rbs @buffer: Buffer
10
+ # @rbs @decoder: Ciborium::Decoder
11
+
12
+ attr_reader :buffer #: Buffer
13
+
14
+ # options takes symbolize_keys, and the Buffer options.
15
+ #
16
+ #: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
17
+ def initialize(io = nil, options = nil)
18
+ if options.nil? && io.is_a?(Hash)
19
+ options = io
20
+ io = nil
21
+ end
22
+ @buffer = Buffer.new(io, options)
23
+ @decoder = Ciborium::Decoder.new(**Compat.decode_options, symbolize_keys: !!options&.[](:symbolize_keys))
24
+ end
25
+
26
+ #: (String data) -> self
27
+ def feed(data)
28
+ @buffer << data
29
+ self
30
+ end
31
+
32
+ #: () -> untyped
33
+ def read
34
+ with_input { @decoder.read }
35
+ end
36
+
37
+ alias_method :unpack, :read
38
+
39
+ #: () -> nil
40
+ def skip
41
+ with_input { @decoder.skip }
42
+ nil
43
+ end
44
+
45
+ # Whether the next item is null. Consumes nothing.
46
+ #
47
+ #: () -> bool
48
+ def skip_nil
49
+ with_input { @decoder.next_nil? }
50
+ end
51
+
52
+ #: () -> Integer
53
+ def read_array_header
54
+ with_input { @decoder.read_array_header }
55
+ end
56
+
57
+ #: () -> Integer
58
+ def read_map_header
59
+ with_input { @decoder.read_map_header }
60
+ end
61
+
62
+ # Yields each item until the input runs out, leaving a partial item buffered.
63
+ #
64
+ #: () { (untyped) -> void } -> nil
65
+ #: () -> Enumerator[untyped, nil]
66
+ def each
67
+ return enum_for(:each) unless block_given?
68
+
69
+ loop do
70
+ item = begin
71
+ read
72
+ rescue ::EOFError
73
+ break
74
+ end
75
+ yield item
76
+ end
77
+ nil
78
+ end
79
+
80
+ #: (String data) { (untyped) -> void } -> nil
81
+ def feed_each(data, &block)
82
+ feed(data)
83
+ each(&block)
84
+ end
85
+
86
+ #: () -> nil
87
+ def reset
88
+ @buffer.clear
89
+ @decoder.reset
90
+ nil
91
+ end
92
+
93
+ # Runs the block on the decoder, moving more input into it until the block stops raising
94
+ # IncompleteError or the input ends.
95
+ #
96
+ #: [T] () { () -> T } -> T
97
+ private def with_input
98
+ Compat.translate_errors do
99
+ @decoder.feed(@buffer.read_all(@buffer.size)) unless @buffer.empty?
100
+ yield
101
+ rescue IncompleteError
102
+ chunk = @buffer.read(Buffer::DEFAULT_IO_BUFFER_SIZE)
103
+ raise unless chunk
104
+
105
+ @decoder.feed(chunk)
106
+ retry
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end