Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions lib/json_sequence/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,44 @@ def parse(chunk, &block)
# Takes a String buffer to parse and returns String containing any
# text remaining to parse when more data is available.
def do_parse(buffer)
records = buffer.split(RS, -1) # -1 stops suppression of trailing null fields

records.each_with_index do |record, i|
# RFC7464 2.1 Multiple consecutive RS octets do not denote empty
# sequence elements between them and can be ignored.
next if record == ''

# Try to decode the record
begin
value = MultiJson.load(record)
result, remaining = handle_parsed(record, value, is_last_record: i == records.size - 1)
rescue MultiJson::ParseError => err
result, remaining = handle_err(record, err, is_last_record: i == records.size - 1)
end

return remaining if result.nil?
yield result
end
# RFC7464 2.1 Multiple consecutive RS octets do not denote empty
# sequence elements between them and can be ignored.
records = buffer.split(RS).reject(&:empty?)

# Every record except the last is guaranteed to be completed
records[0...-1].each { |record| yield decode_record(record) }

last_result = decode_record(records.last)

# If we have an incomplete record and run out of valid json early, return it to the buffer
return records.last if !buffer.end_with?(RS) && partial_result?(last_result)
yield last_result

''
end

def handle_parsed(record, value, is_last_record:)
def decode_record(record)
value = MultiJson.load(record)
return JsonSequence::Result::MaybeTruncated.new(value) if truncated?(record, value)

JsonSequence::Result::Json.new(value)
rescue MultiJson::ParseError => e
JsonSequence::Result::ParseError.new(record, e)
end

def truncated?(record, value)
case value
when Numeric, TrueClass, FalseClass, NilClass
# Check for truncation, if record was parsed but doesn't end in
# whitespace it may be truncated
if record !~ /\s$/
return is_last_record ? [nil, record] : [JsonSequence::Result::MaybeTruncated.new(value), '']
end
record !~ /\s$/
else
false
end

[JsonSequence::Result::Json.new(value), '']
end

def handle_err(record, err, is_last_record:)
# Last record, might be incomplete, stash for later
is_last_record ? [nil, record] : [JsonSequence::Result::ParseError.new(record, err), '']
def partial_result?(result)
!result.is_a?(JsonSequence::Result::Json)
end
end
end
2 changes: 1 addition & 1 deletion lib/json_sequence/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module JsonSequence
VERSION = "0.2.0"
VERSION = "0.2.1"
end
9 changes: 9 additions & 0 deletions spec/json_sequence/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@
)
end

it 'process records when ending with a RS token' do
expect { |b| parser.parse(%|\x1E123\x0A\x1E|, &b) }.to yield_successive_args(
JsonSequence::Result::Json.new(123),
)
expect { |b| parser.parse(%|123\x0A|, &b) }.to yield_successive_args(
JsonSequence::Result::Json.new(123),
)
end

it 'parses formatted json' do
expect { |b| parser.parse(%|\x1E{"some": "json",\n"more": 1,\n"even more": []}\x0A|, &b) }.to yield_successive_args(
JsonSequence::Result::Json.new('some' => 'json', 'more' => 1, 'even more' => [])
Expand Down