From 4cb6a7db875e793f2cc86169c3b65815f9779d82 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 4 Aug 2026 21:06:56 +1200 Subject: [PATCH] Add IO conversion for readable bodies. --- lib/protocol/http/body/readable.rb | 8 ++++++++ lib/protocol/http/body/stream.rb | 22 +++++++++++++--------- releases.md | 5 +++++ test/protocol/http/body/readable.rb | 16 ++++++++++++++++ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/protocol/http/body/readable.rb b/lib/protocol/http/body/readable.rb index 21b04b0c..aa03731b 100644 --- a/lib/protocol/http/body/readable.rb +++ b/lib/protocol/http/body/readable.rb @@ -4,6 +4,8 @@ # Copyright, 2019-2024, by Samuel Williams. # Copyright, 2023, by Bruno Sutic. +require_relative "stream" + module Protocol module HTTP module Body @@ -186,6 +188,12 @@ def as_json(...) def to_json(...) as_json.to_json(...) end + + # Return an IO-compatible stream for reading this body. + # @returns [Stream] The IO-compatible stream adapter. + def to_io + return Stream.new(self) + end end end end diff --git a/lib/protocol/http/body/stream.rb b/lib/protocol/http/body/stream.rb index 6e3da3d8..72ba02e9 100644 --- a/lib/protocol/http/body/stream.rb +++ b/lib/protocol/http/body/stream.rb @@ -5,8 +5,6 @@ # Copyright, 2023, by Genki Takiuchi. # Copyright, 2025, by William T. Nelson. -require_relative "buffered" - module Protocol module HTTP module Body @@ -17,13 +15,15 @@ class Stream # Initialize the stream with the given input and output. # - # @parameter input [Readable] The input stream. - # @parameter output [Writable] The output stream. - def initialize(input = nil, output = Buffered.new) + # @parameter input [Readable | Nil] The input stream. + # @parameter output [Writable | Nil] The output stream. + def initialize(input = nil, output = nil) @input = input @output = output - raise ArgumentError, "Non-writable output!" unless output.respond_to?(:write) + if @output + raise ArgumentError, "Non-writable output!" unless output.respond_to?(:write) + end # Will hold remaining data in `#read`. @buffer = nil @@ -32,10 +32,10 @@ def initialize(input = nil, output = Buffered.new) @closed_read = false end - # @attribute [Readable] The input stream. + # @attribute [Readable | Nil] The input stream. attr :input - # @attribute [Writable] The output stream. + # @attribute [Writable | Nil] The output stream. attr :output # This provides a read-only interface for data, which is surprisingly tricky to implement correctly. @@ -409,7 +409,11 @@ def inspect # @returns [Boolean] Whether there are any output chunks remaining. def empty? - @output.empty? + if @output + return @output.empty? + else + return true + end end private diff --git a/releases.md b/releases.md index 3e1ae536..2b65ddf9 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,10 @@ # Releases +## Unreleased + + - Add `Protocol::HTTP::Body::Readable#to_io` for obtaining an IO-compatible stream adapter. + - Avoid creating an implicit buffered output for `Protocol::HTTP::Body::Stream`. + ## v0.68.0 - Add HTTP status descriptions. diff --git a/test/protocol/http/body/readable.rb b/test/protocol/http/body/readable.rb index 0cb4c997..f5cb9e6f 100644 --- a/test/protocol/http/body/readable.rb +++ b/test/protocol/http/body/readable.rb @@ -9,6 +9,22 @@ describe Protocol::HTTP::Body::Readable do let(:body) {subject.new} + with "#to_io" do + let(:body) {Protocol::HTTP::Body::Buffered.new(["Hello", "World"])} + + it "returns an IO-compatible stream" do + stream = body.to_io + + expect(stream).to be_a(Protocol::HTTP::Body::Stream) + expect(stream.output).to be_nil + expect(body.to_io).not.to be_equal(stream) + expect(stream.read(5)).to be == "Hello" + expect(stream.read(5)).to be == "World" + expect(stream.read(5)).to be_nil + expect{stream.write("!")}.to raise_exception(IOError) + end + end + it "might not be empty" do expect(body).not.to be(:empty?) end