diff --git a/lib/json_sequence/parser.rb b/lib/json_sequence/parser.rb index b299ed2..a07993c 100644 --- a/lib/json_sequence/parser.rb +++ b/lib/json_sequence/parser.rb @@ -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 diff --git a/lib/json_sequence/version.rb b/lib/json_sequence/version.rb index 435a063..04c9e31 100644 --- a/lib/json_sequence/version.rb +++ b/lib/json_sequence/version.rb @@ -1,3 +1,3 @@ module JsonSequence - VERSION = "0.2.0" + VERSION = "0.2.1" end diff --git a/spec/json_sequence/parser_spec.rb b/spec/json_sequence/parser_spec.rb index f1d5974..1659252 100644 --- a/spec/json_sequence/parser_spec.rb +++ b/spec/json_sequence/parser_spec.rb @@ -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' => [])