Skip to content

Commit a750ec4

Browse files
authored
Introduce rewind and rewindable? as a general concept. (#60)
* Consistent implementation of `#wrap` for Wrapper sub-class.
1 parent c2cd3b6 commit a750ec4

File tree

11 files changed

+129
-14
lines changed

11 files changed

+129
-14
lines changed

lib/protocol/http/body/buffered.rb

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,29 @@ module HTTP
1111
module Body
1212
# A body which buffers all it's contents.
1313
class Buffered < Readable
14-
# Wraps an array into a buffered body.
14+
# Tries to wrap an object in a {Buffered} instance.
1515
#
1616
# For compatibility, also accepts anything that behaves like an `Array(String)`.
1717
#
1818
# @parameter body [String | Array(String) | Readable | nil] the body to wrap.
1919
# @returns [Readable | nil] the wrapped body or nil if nil was given.
20-
def self.wrap(body)
21-
if body.is_a?(Readable)
22-
return body
23-
elsif body.is_a?(Array)
24-
return self.new(body)
25-
elsif body.is_a?(String)
26-
return self.new([body])
27-
elsif body
28-
return self.for(body)
20+
def self.wrap(object)
21+
if object.is_a?(Readable)
22+
return object
23+
elsif object.is_a?(Array)
24+
return self.new(object)
25+
elsif object.is_a?(String)
26+
return self.new([object])
27+
elsif object
28+
return self.read(object)
2929
end
3030
end
3131

32-
def self.for(body)
32+
# Read the entire body into a buffered representation.
33+
#
34+
# @parameter body [Readable] the body to read.
35+
# @returns [Buffered] the buffered body.
36+
def self.read(body)
3337
chunks = []
3438

3539
body.each do |chunk|
@@ -77,8 +81,14 @@ def write(chunk)
7781
@chunks << chunk
7882
end
7983

84+
def rewindable?
85+
true
86+
end
87+
8088
def rewind
8189
@index = 0
90+
91+
return true
8292
end
8393

8494
def inspect

lib/protocol/http/body/completable.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ def initialize(body, callback)
2424
@callback = callback
2525
end
2626

27+
def rewindable?
28+
false
29+
end
30+
31+
def rewind
32+
false
33+
end
34+
2735
def finish
2836
super.tap do
2937
if @callback

lib/protocol/http/body/readable.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ def ready?
2929
false
3030
end
3131

32+
def rewindable?
33+
false
34+
end
35+
36+
def rewind
37+
false
38+
end
39+
3240
def length
3341
nil
3442
end
@@ -58,7 +66,7 @@ def call(stream)
5866
# @returns [Buffered] The buffered body.
5967
def finish
6068
# Internally, this invokes `self.each` which then invokes `self.close`.
61-
Buffered.for(self)
69+
Buffered.read(self)
6270
end
6371

6472
# Enumerate all chunks until finished, then invoke `#close`.

lib/protocol/http/body/rewindable.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ module HTTP
1111
module Body
1212
# A body which buffers all it's contents as it is `#read`.
1313
class Rewindable < Wrapper
14+
def self.wrap(message)
15+
if body = message.body
16+
if body.rewindable?
17+
body
18+
else
19+
message.body = self.new(body)
20+
end
21+
end
22+
end
23+
1424
def initialize(body)
1525
super(body)
1626

@@ -26,7 +36,9 @@ def ready?
2636
(@index < @chunks.size) || super
2737
end
2838

29-
# A rewindable body wraps some other body. Convert it to a buffered body
39+
# A rewindable body wraps some other body. Convert it to a buffered body. The buffered body will share the same chunks as the rewindable body.
40+
#
41+
# @returns [Buffered] the buffered body.
3042
def buffered
3143
Buffered.new(@chunks)
3244
end
@@ -54,6 +66,10 @@ def rewind
5466
@index = 0
5567
end
5668

69+
def rewindable?
70+
true
71+
end
72+
5773
def inspect
5874
"\#<#{self.class} #{@index}/#{@chunks.size} chunks read>"
5975
end

lib/protocol/http/body/wrapper.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ module HTTP
1010
module Body
1111
# Wrapping body instance. Typically you'd override `#read`.
1212
class Wrapper < Readable
13+
# Wrap the body of the given message in a new instance of this class.
14+
#
15+
# @parameter message [Request | Response] the message to wrap.
16+
# @returns [Wrapper | nil] the wrapped body or nil if the body was nil.
1317
def self.wrap(message)
1418
if body = message.body
1519
message.body = self.new(body)
@@ -42,6 +46,14 @@ def ready?
4246
@body.ready?
4347
end
4448

49+
def rewind
50+
@body.rewind
51+
end
52+
53+
def rewindable?
54+
@body.rewindable?
55+
end
56+
4557
def length
4658
@body.length
4759
end

test/protocol/http/body/buffered.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@
117117
end
118118

119119
with "#rewind" do
120+
it "is rewindable" do
121+
expect(body).to be(:rewindable?)
122+
end
123+
120124
it "positions the cursor to the beginning" do
121125
expect(body.read).to be == "Hello"
122126
body.rewind

test/protocol/http/body/completable.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,13 @@
8383
expect(completable.read).to be_nil
8484
end
8585
end
86+
87+
with "#rewindable?" do
88+
it "is not rewindable" do
89+
# Because completion can only happen once, we can't rewind the body.
90+
expect(body).to be(:rewindable?)
91+
expect(completable).not.to be(:rewindable?)
92+
expect(completable.rewind).to be == false
93+
end
94+
end
8695
end

test/protocol/http/body/inflate.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
let(:sample) {"The quick brown fox jumps over the lazy dog."}
1515
let(:chunks) {[sample] * 1024}
1616

17-
let(:body) {Protocol::HTTP::Body::Buffered.for(chunks)}
17+
let(:body) {Protocol::HTTP::Body::Buffered.new(chunks)}
1818
let(:deflate_body) {Protocol::HTTP::Body::Deflate.for(body)}
1919
let(:compressed_chunks) {deflate_body.join.each_char.to_a}
2020
let(:compressed_body_chunks) {compressed_chunks}

test/protocol/http/body/readable.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,11 @@
5858
expect(JSON.dump(body)).to be == body.to_json
5959
end
6060
end
61+
62+
with "#rewindable?" do
63+
it "is not rewindable" do
64+
expect(body).not.to be(:rewindable?)
65+
expect(body.rewind).to be == false
66+
end
67+
end
6168
end

test/protocol/http/body/rewindable.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Copyright, 2019-2023, by Samuel Williams.
55

66
require 'protocol/http/body/rewindable'
7+
require 'protocol/http/request'
78

89
describe Protocol::HTTP::Body::Rewindable do
910
let(:source) {Protocol::HTTP::Body::Buffered.new}
@@ -47,6 +48,26 @@
4748
end
4849
end
4950

51+
with ".wrap" do
52+
with "a buffered body" do
53+
let(:body) {Protocol::HTTP::Body::Buffered.new}
54+
let(:message) {Protocol::HTTP::Request.new(nil, nil, 'GET', '/', nil, Protocol::HTTP::Headers.new, body)}
55+
56+
it "returns the body" do
57+
expect(subject.wrap(message)).to be == body
58+
end
59+
end
60+
61+
with "a non-rewindable body" do
62+
let(:body) {Protocol::HTTP::Body::Readable.new}
63+
let(:message) {Protocol::HTTP::Request.new(nil, nil, 'GET', '/', nil, Protocol::HTTP::Headers.new, body)}
64+
65+
it "returns a new rewindable body" do
66+
expect(subject.wrap(message)).to be_a(Protocol::HTTP::Body::Rewindable)
67+
end
68+
end
69+
end
70+
5071
with '#buffered' do
5172
it "can generate buffered representation" do
5273
3.times do |i|
@@ -76,6 +97,12 @@
7697
end
7798
end
7899

100+
with "#rewindable?" do
101+
it "is rewindable" do
102+
expect(body).to be(:rewindable?)
103+
end
104+
end
105+
79106
with '#inspect' do
80107
it "can generate string representation" do
81108
expect(body.inspect).to be == "#<Protocol::HTTP::Body::Rewindable 0/0 chunks read>"

test/protocol/http/body/wrapper.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@
6464
end
6565
end
6666

67+
with "#rewindable?" do
68+
it "should proxy rewindable?" do
69+
expect(source).to receive(:rewindable?).and_return(true)
70+
expect(body.rewindable?).to be == true
71+
end
72+
end
73+
74+
with "#rewind" do
75+
it "should proxy rewind" do
76+
expect(source).to receive(:rewind).and_return(true)
77+
expect(body.rewind).to be == true
78+
end
79+
end
80+
6781
with "#as_json" do
6882
it "generates a JSON representation" do
6983
expect(body.as_json).to have_keys(

0 commit comments

Comments
 (0)