Skip to content

Commit 2ab6d43

Browse files
Merge pull request #443 from OneBusAway/release-please--branches--main--changes--next
release: 0.1.0-alpha.206
2 parents 24ea89c + dc65beb commit 2ab6d43

File tree

17 files changed

+520
-110
lines changed

17 files changed

+520
-110
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.1.0-alpha.205"
2+
".": "0.1.0-alpha.206"
33
}

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 0.1.0-alpha.206 (2025-03-05)
4+
5+
Full Changelog: [v0.1.0-alpha.205...v0.1.0-alpha.206](https://github.com/OneBusAway/ruby-sdk/compare/v0.1.0-alpha.205...v0.1.0-alpha.206)
6+
7+
### Chores
8+
9+
* **internal:** add utils methods for parsing SSE ([#444](https://github.com/OneBusAway/ruby-sdk/issues/444)) ([3e8248b](https://github.com/OneBusAway/ruby-sdk/commit/3e8248b68c2e7af53876afd1bd554b8f0e8d6296))
10+
* **internal:** version bump ([#442](https://github.com/OneBusAway/ruby-sdk/issues/442)) ([cc8bdc5](https://github.com/OneBusAway/ruby-sdk/commit/cc8bdc5cc7885a536bd33dd6450f630452856667))
11+
312
## 0.1.0-alpha.205 (2025-03-04)
413

514
Full Changelog: [v0.1.0-alpha.204...v0.1.0-alpha.205](https://github.com/OneBusAway/ruby-sdk/compare/v0.1.0-alpha.204...v0.1.0-alpha.205)

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ GIT
1111
PATH
1212
remote: .
1313
specs:
14-
onebusaway-sdk (0.1.0.pre.alpha.205)
14+
onebusaway-sdk (0.1.0.pre.alpha.206)
1515
connection_pool
1616

1717
GEM

lib/onebusaway-sdk/base_client.rb

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class << self
2828
# @raise [ArgumentError]
2929
#
3030
def validate!(req)
31-
keys = [:method, :path, :query, :headers, :body, :unwrap, :page, :model, :options]
31+
keys = [:method, :path, :query, :headers, :body, :unwrap, :page, :stream, :model, :options]
3232
case req
3333
in Hash
3434
req.each_key do |k|
@@ -201,6 +201,8 @@ def initialize(
201201
#
202202
# @option req [Class, nil] :page
203203
#
204+
# @option req [Class, nil] :stream
205+
#
204206
# @option req [OnebusawaySDK::Converter, Class, nil] :model
205207
#
206208
# @param opts [Hash{Symbol=>Object}] .
@@ -318,7 +320,7 @@ def initialize(
318320
# @param send_retry_header [Boolean]
319321
#
320322
# @raise [OnebusawaySDK::APIError]
321-
# @return [Array(Net::HTTPResponse, Enumerable)]
323+
# @return [Array(Integer, Net::HTTPResponse, Enumerable)]
322324
#
323325
private def send_request(request, redirect_count:, retry_count:, send_retry_header:)
324326
url, headers, max_retries, timeout = request.fetch_values(:url, :headers, :max_retries, :timeout)
@@ -341,7 +343,7 @@ def initialize(
341343

342344
case status
343345
in ..299
344-
[response, stream]
346+
[status, response, stream]
345347
in 300..399 if redirect_count >= self.class::MAX_REDIRECTS
346348
message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
347349

@@ -359,13 +361,15 @@ def initialize(
359361
)
360362
in OnebusawaySDK::APIConnectionError if retry_count >= max_retries
361363
raise status
362-
in (400..) if retry_count >= max_retries || (response && !self.class.should_retry?(
363-
status,
364-
headers: response
365-
))
364+
in (400..) if retry_count >= max_retries || !self.class.should_retry?(status, headers: response)
366365
decoded = OnebusawaySDK::Util.decode_content(response, stream: stream, suppress_error: true)
367366

368-
stream.each { srv_fault ? break : next }
367+
if srv_fault
368+
OnebusawaySDK::Util.close_fused!(stream)
369+
else
370+
stream.each { next }
371+
end
372+
369373
raise OnebusawaySDK::APIStatusError.for(
370374
url: url,
371375
status: status,
@@ -376,7 +380,11 @@ def initialize(
376380
in (400..) | OnebusawaySDK::APIConnectionError
377381
delay = retry_delay(response, retry_count: retry_count)
378382

379-
stream&.each { srv_fault ? break : next }
383+
if srv_fault
384+
OnebusawaySDK::Util.close_fused!(stream)
385+
else
386+
stream&.each { next }
387+
end
380388
sleep(delay)
381389

382390
send_request(
@@ -388,48 +396,6 @@ def initialize(
388396
end
389397
end
390398

391-
# @private
392-
#
393-
# @param req [Hash{Symbol=>Object}] .
394-
#
395-
# @option req [Symbol] :method
396-
#
397-
# @option req [String, Array<String>] :path
398-
#
399-
# @option req [Hash{String=>Array<String>, String, nil}, nil] :query
400-
#
401-
# @option req [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}, nil] :headers
402-
#
403-
# @option req [Object, nil] :body
404-
#
405-
# @option req [Symbol, nil] :unwrap
406-
#
407-
# @option req [Class, nil] :page
408-
#
409-
# @option req [OnebusawaySDK::Converter, Class, nil] :model
410-
#
411-
# @option req [OnebusawaySDK::RequestOptions, Hash{Symbol=>Object}, nil] :options
412-
#
413-
# @param headers [Hash{String=>String}, Net::HTTPHeader]
414-
#
415-
# @param stream [Enumerable]
416-
#
417-
# @return [Object]
418-
#
419-
private def parse_response(req, headers:, stream:)
420-
decoded = OnebusawaySDK::Util.decode_content(headers, stream: stream)
421-
unwrapped = OnebusawaySDK::Util.dig(decoded, req[:unwrap])
422-
423-
case [req[:page], req.fetch(:model, OnebusawaySDK::Unknown)]
424-
in [Class => page, _]
425-
page.new(client: self, req: req, headers: headers, unwrapped: unwrapped)
426-
in [nil, Class | OnebusawaySDK::Converter => model]
427-
OnebusawaySDK::Converter.coerce(model, unwrapped)
428-
in [nil, nil]
429-
unwrapped
430-
end
431-
end
432-
433399
# Execute the request specified by `req`. This is the method that all resource
434400
# methods call into.
435401
#
@@ -449,6 +415,8 @@ def initialize(
449415
#
450416
# @option req [Class, nil] :page
451417
#
418+
# @option req [Class, nil] :stream
419+
#
452420
# @option req [OnebusawaySDK::Converter, Class, nil] :model
453421
#
454422
# @option req [OnebusawaySDK::RequestOptions, Hash{Symbol=>Object}, nil] :options
@@ -458,19 +426,31 @@ def initialize(
458426
#
459427
def request(req)
460428
self.class.validate!(req)
429+
model = req.fetch(:model) { OnebusawaySDK::Unknown }
461430
opts = req[:options].to_h
462431
OnebusawaySDK::RequestOptions.validate!(opts)
463432
request = build_request(req.except(:options), opts)
433+
url = request.fetch(:url)
464434

465435
# Don't send the current retry count in the headers if the caller modified the header defaults.
466436
send_retry_header = request.fetch(:headers)["x-stainless-retry-count"] == "0"
467-
response, stream = send_request(
437+
status, response, stream = send_request(
468438
request,
469439
redirect_count: 0,
470440
retry_count: 0,
471441
send_retry_header: send_retry_header
472442
)
473-
parse_response(req, headers: response, stream: stream)
443+
444+
decoded = OnebusawaySDK::Util.decode_content(response, stream: stream)
445+
case req
446+
in { stream: Class => st }
447+
st.new(model: model, url: url, status: status, response: response, messages: decoded)
448+
in { page: Class => page }
449+
page.new(client: self, req: req, headers: response, unwrapped: decoded)
450+
else
451+
unwrapped = OnebusawaySDK::Util.dig(decoded, req[:unwrap])
452+
OnebusawaySDK::Converter.coerce(model, unwrapped)
453+
end
474454
end
475455

476456
# @return [String]

lib/onebusaway-sdk/errors.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,12 @@ class APIStatusError < OnebusawaySDK::APIError
9999
# @param body [Object, nil]
100100
# @param request [nil]
101101
# @param response [nil]
102+
# @param message [String, nil]
102103
#
103104
# @return [OnebusawaySDK::APIStatusError]
104105
#
105-
def self.for(url:, status:, body:, request:, response:)
106-
kwargs = {url: url, status: status, body: body, request: request, response: response}
106+
def self.for(url:, status:, body:, request:, response:, message: nil)
107+
kwargs = {url: url, status: status, body: body, request: request, response: response, message: message}
107108

108109
case status
109110
in 400

lib/onebusaway-sdk/pooled_net_requester.rb

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,33 +131,38 @@ def execute(request)
131131
req = self.class.build_request(request)
132132

133133
eof = false
134+
finished = false
134135
enum = Enumerator.new do |y|
135136
with_pool(url) do |conn|
137+
next if finished
138+
136139
self.class.calibrate_socket_timeout(conn, deadline)
137140
conn.start unless conn.started?
138141

139142
self.class.calibrate_socket_timeout(conn, deadline)
140143
conn.request(req) do |rsp|
141144
y << [conn, rsp]
145+
break if finished
146+
142147
rsp.read_body do |bytes|
143148
y << bytes
149+
break if finished
150+
144151
self.class.calibrate_socket_timeout(conn, deadline)
145152
end
146153
eof = true
147154
end
148155
end
149156
end
150157

151-
# need to protect the `Enumerator` against `#.rewind`
152-
fused = false
153158
conn, response = enum.next
154-
body = Enumerator.new do |y|
155-
next if fused
156-
157-
fused = true
158-
loop { y << enum.next }
159-
ensure
160-
conn.finish if !eof && conn.started?
159+
body = OnebusawaySDK::Util.fused_enum(enum) do
160+
finished = true
161+
tap do
162+
enum.next
163+
rescue StopIteration
164+
end
165+
conn.finish if !eof && conn&.started?
161166
end
162167
[response, (response.body = body)]
163168
end

lib/onebusaway-sdk/util.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ def encode_content(headers, body)
496496
#
497497
def decode_content(headers, stream:, suppress_error: false)
498498
case headers["content-type"]
499+
in %r{^text/event-stream}
500+
lines = enum_lines(stream)
501+
parse_sse(lines)
499502
in %r{^application/json}
500503
json = stream.to_a.join
501504
begin
@@ -512,6 +515,121 @@ def decode_content(headers, stream:, suppress_error: false)
512515
end
513516
end
514517
end
518+
519+
class << self
520+
# @private
521+
#
522+
# https://doc.rust-lang.org/std/iter/trait.FusedIterator.html
523+
#
524+
# @param enum [Enumerable]
525+
# @param close [Proc]
526+
#
527+
# @return [Enumerable]
528+
#
529+
def fused_enum(enum, &close)
530+
fused = false
531+
iter = Enumerator.new do |y|
532+
next if fused
533+
534+
fused = true
535+
loop { y << enum.next }
536+
ensure
537+
close&.call
538+
close = nil
539+
end
540+
541+
iter.define_singleton_method(:rewind) do
542+
fused = true
543+
self
544+
end
545+
iter
546+
end
547+
548+
# @private
549+
#
550+
# @param enum [Enumerable, nil]
551+
#
552+
def close_fused!(enum)
553+
return unless enum.is_a?(Enumerator)
554+
555+
# rubocop:disable Lint/UnreachableLoop
556+
enum.rewind.each { break }
557+
# rubocop:enable Lint/UnreachableLoop
558+
end
559+
560+
# @private
561+
#
562+
# @param enum [Enumerable, nil]
563+
# @param blk [Proc]
564+
#
565+
def chain_fused(enum, &blk)
566+
iter = Enumerator.new { blk.call(_1) }
567+
fused_enum(iter) { close_fused!(enum) }
568+
end
569+
end
570+
571+
class << self
572+
# @private
573+
#
574+
# @param enum [Enumerable]
575+
#
576+
# @return [Enumerable]
577+
#
578+
def enum_lines(enum)
579+
chain_fused(enum) do |y|
580+
buffer = String.new
581+
enum.each do |row|
582+
buffer << row
583+
while (idx = buffer.index("\n"))
584+
y << buffer.slice!(..idx)
585+
end
586+
end
587+
y << buffer unless buffer.empty?
588+
end
589+
end
590+
591+
# @private
592+
#
593+
# https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
594+
#
595+
# @param lines [Enumerable]
596+
#
597+
# @return [Hash{Symbol=>Object}]
598+
#
599+
def parse_sse(lines)
600+
chain_fused(lines) do |y|
601+
blank = {event: nil, data: nil, id: nil, retry: nil}
602+
current = {}
603+
604+
lines.each do |line|
605+
case line.strip
606+
in ""
607+
next if current.empty?
608+
y << {**blank, **current}
609+
current = {}
610+
in /^:/
611+
next
612+
in /^([^:]+):\s?(.*)$/
613+
_, field, value = Regexp.last_match.to_a
614+
case field
615+
in "event"
616+
current.merge!(event: value)
617+
in "data"
618+
(current[:data] ||= String.new) << value << "\n"
619+
in "id" unless value.include?("\0")
620+
current.merge!(id: value)
621+
in "retry" if /^\d+$/ =~ value
622+
current.merge!(retry: Integer(value))
623+
else
624+
end
625+
else
626+
end
627+
end
628+
629+
y << {**blank, **current} unless current.empty?
630+
end
631+
end
632+
end
515633
end
516634

517635
# rubocop:enable Metrics/ModuleLength

lib/onebusaway-sdk/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module OnebusawaySDK
4-
VERSION = "0.1.0-alpha.205"
4+
VERSION = "0.1.0-alpha.206"
55
end

0 commit comments

Comments
 (0)