require "spec" require "json" class JSON::PullParser def assert(event_kind : Kind) kind.should eq(event_kind) read_next end def assert(value : Nil) kind.should eq(JSON::PullParser::Kind::Null) read_next end def assert(value : Int) kind.should eq(JSON::PullParser::Kind::Int) int_value.should eq(value) read_next end def assert(value : Float) kind.should eq(JSON::PullParser::Kind::Float) float_value.should eq(value) read_next end def assert(value : Bool) kind.should eq(JSON::PullParser::Kind::Bool) bool_value.should eq(value) read_next end def assert(value : String) kind.should eq(JSON::PullParser::Kind::String) string_value.should eq(value) read_next end def assert(value : String, &) kind.should eq(JSON::PullParser::Kind::String) string_value.should eq(value) read_next yield end def assert(array : Array) assert_array do array.each do |x| assert x.raw end end end def assert(hash : Hash) assert_object do hash.each do |key, value| assert(key) do assert value.raw end end end end def assert_array(&) kind.should eq(JSON::PullParser::Kind::BeginArray) read_next yield kind.should eq(JSON::PullParser::Kind::EndArray) read_next end def assert_array assert_array { } end def assert_object(&) kind.should eq(JSON::PullParser::Kind::BeginObject) read_next yield kind.should eq(JSON::PullParser::Kind::EndObject) read_next end def assert_object assert_object { } end def assert_error expect_raises JSON::ParseException do read_next end end end private def assert_pull_parse(string) it "parses #{string}" do parser = JSON::PullParser.new string parser.assert JSON.parse(string).raw parser.kind.should eq(JSON::PullParser::Kind::EOF) end end private def assert_pull_parse_error(string) it "errors on #{string}" do expect_raises JSON::ParseException do parser = JSON::PullParser.new string until parser.kind.eof? parser.read_next end end end end private def assert_raw(string, file = __FILE__, line = __LINE__) it "parses raw #{string.inspect}", file, line do pull = JSON::PullParser.new(string) pull.read_raw.should eq(string) end end private def it_reads(value, file = __FILE__, line = __LINE__) type = value.class it "reads #{type}: #{value.to_json}", file: file, line: line do pull = JSON::PullParser.new(value.to_json) pull.read?(type).should eq(value) end end describe JSON::PullParser do assert_pull_parse "null" assert_pull_parse "false" assert_pull_parse "true" assert_pull_parse "1" assert_pull_parse "1.5" assert_pull_parse %("hello") assert_pull_parse "[]" assert_pull_parse "[[]]" assert_pull_parse "[1]" assert_pull_parse "[1.5]" assert_pull_parse "[null]" assert_pull_parse "[true]" assert_pull_parse "[false]" assert_pull_parse %(["hello"]) assert_pull_parse "[1, 2]" assert_pull_parse "{}" assert_pull_parse %({"foo": 1}) assert_pull_parse %({"foo": "bar"}) assert_pull_parse %({"foo": [1, 2]}) assert_pull_parse %({"foo": 1, "bar": 2}) assert_pull_parse %({"foo": "foo1", "bar": "bar1"}) assert_pull_parse_error "[null 2]" assert_pull_parse_error "[false 2]" assert_pull_parse_error "[true 2]" assert_pull_parse_error "[1 2]" assert_pull_parse_error "[1.5 2]" assert_pull_parse_error %(["hello" 2]) assert_pull_parse_error "[,1]" assert_pull_parse_error "[}]" assert_pull_parse_error "[" assert_pull_parse_error %({,"foo": 1}) assert_pull_parse_error "[]]" assert_pull_parse_error "{}}" assert_pull_parse_error %({"foo",1}) assert_pull_parse_error %({"foo"::1}) assert_pull_parse_error %(["foo":1]) assert_pull_parse_error %({"foo": []:1}) assert_pull_parse_error "[[]" assert_pull_parse_error %({"foo": {}) assert_pull_parse_error %({"name": "John", "age", 1}) assert_pull_parse_error %({"name": "John", "age": "foo", "bar"}) it "parses when the input IO is already empty" do JSON::PullParser.new(IO::Memory.new).kind.should eq JSON::PullParser::Kind::EOF end it "prevents stack overflow for arrays" do parser = JSON::PullParser.new(("[" * 513) + ("]" * 513)) expect_raises JSON::ParseException, "Nesting of 513 is too deep" do while true break if parser.kind.eof? parser.read_next end end end it "prevents stack overflow for hashes" do parser = JSON::PullParser.new((%({"x": ) * 513) + ("}" * 513)) expect_raises JSON::ParseException, "Nesting of 513 is too deep" do while true break if parser.kind.eof? parser.read_next end end end # Prevent too deep nesting (prevents stack overflow) assert_pull_parse_error(("[" * 513) + ("]" * 513)) assert_pull_parse_error(("{" * 513) + ("}" * 513)) describe "skip" do [ {"null", "null"}, {"bool", "false"}, {"int", "3"}, {"float", "3.5"}, {"string", %("hello")}, {"array", %([10, 20, [30], [40]])}, {"object", %({"foo": [1, 2], "bar": {"baz": [3]}})}, ].each do |(desc, obj)| it "skips #{desc}" do pull = JSON::PullParser.new("[1, #{obj}, 2]") pull.read_array do pull.read_int.should eq(1) pull.skip pull.read_int.should eq(2) end end end end it "reads bool or null" do JSON::PullParser.new("null").read_bool_or_null.should be_nil JSON::PullParser.new("false").read_bool_or_null.should be_false end it "reads int or null" do JSON::PullParser.new("null").read_int_or_null.should be_nil JSON::PullParser.new("1").read_int_or_null.should eq(1) end it "reads float or null" do JSON::PullParser.new("null").read_float_or_null.should be_nil JSON::PullParser.new("1.5").read_float_or_null.should eq(1.5) end it "reads string or null" do JSON::PullParser.new("null").read_string_or_null.should be_nil JSON::PullParser.new(%("hello")).read_string_or_null.should eq("hello") end it "reads array or null" do JSON::PullParser.new("null").read_array_or_null { fail "expected block not to be called" } pull = JSON::PullParser.new(%([1])) pull.read_array_or_null do pull.read_int.should eq(1) end end it "reads object or null" do JSON::PullParser.new("null").read_object_or_null { fail "expected block not to be called" } pull = JSON::PullParser.new(%({"foo": 1})) pull.read_object_or_null do |key| key.should eq("foo") pull.read_int.should eq(1) end end describe "on key" do it "finds key" do pull = JSON::PullParser.new(%({"foo": 1, "bar": 2})) bar = nil pull.on_key("bar") do bar = pull.read_int end bar.should eq(2) end it "yields parser" do pull = JSON::PullParser.new(%({"foo": 1, "bar": 2})) pull.on_key("bar", &.read_int).should eq(2) end it "doesn't find key" do pull = JSON::PullParser.new(%({"foo": 1, "baz": 2})) bar = nil pull.on_key("bar") do bar = pull.read_int end bar.should be_nil end it "finds key with bang" do pull = JSON::PullParser.new(%({"foo": 1, "bar": 2})) bar = nil pull.on_key!("bar") do bar = pull.read_int end bar.should eq(2) end it "yields parser with bang" do pull = JSON::PullParser.new(%({"foo": 1, "bar": 2})) pull.on_key!("bar", &.read_int).should eq(2) end it "doesn't find key with bang" do pull = JSON::PullParser.new(%({"foo": 1, "baz": 2})) expect_raises Exception, "JSON key not found: bar" do pull.on_key!("bar") do end end end it "reads float when it is an int" do pull = JSON::PullParser.new(%(1)) f = pull.read_float f.should be_a(Float64) f.should eq(1.0) end ["1", "[1]", %({"x": [1]})].each do |value| it "yields all keys when skipping #{value}" do pull = JSON::PullParser.new(%({"foo": #{value}, "bar": 2})) pull.read_object do |key| key.should_not eq("") pull.skip end end end end describe "raw" do assert_raw "null" assert_raw "true" assert_raw "false" assert_raw "1234" assert_raw "1234.5678" assert_raw %("hello") assert_raw %([1,"hello",true,false,null,[1,2,3]]) assert_raw %({"foo":[1,2,{"bar":[1,"hello",true,false,1.5]}]}) assert_raw %({"foo":"bar"}) end describe "#read?" do {% for pair in [[Int8, 1_i8], [Int16, 1_i16], [Int32, 1_i32], [Int64, 1_i64], [Int128, "Int128.new(1)".id], [UInt8, 1_u8], [UInt16, 1_u16], [UInt32, 1_u32], [UInt64, 1_u64], [UInt128, "UInt128.new(1)".id], [Float32, 1.0_f32], [Float64, 1.0], [String, "foo"], [Bool, true]] %} {% type = pair[0] %} {% value = pair[1] %} it "reads {{type}} when the token is a compatible kind" do pull = JSON::PullParser.new({{value}}.to_json) pull.read?({{type}}).should eq({{value}}) end it "returns nil instead of {{type}} when the token is not compatible" do pull = JSON::PullParser.new(%({"foo": "bar"})) pull.read?({{type}}).should be_nil end {% end %} {% for num in Int::Primitive.union_types %} it_reads {{ num }}::MIN {% unless num < Int::Unsigned %} it_reads {{ num }}.new(-10) it_reads {{ num }}.zero {% end %} it_reads {{ num }}.new(10) it_reads {{ num }}::MAX {% end %} {% for i in [8, 16, 32, 64, 128] %} it "returns nil in place of Int{{i}} when an overflow occurs" do JSON::PullParser.new(Int{{i}}::MAX.to_s + "0").read?(Int{{i}}).should be_nil JSON::PullParser.new(Int{{i}}::MIN.to_s + "0").read?(Int{{i}}).should be_nil end it "returns nil in place of UInt{{i}} when an overflow occurs" do JSON::PullParser.new(UInt{{i}}::MAX.to_s + "0").read?(UInt{{i}}).should be_nil JSON::PullParser.new("-1").read?(UInt{{i}}).should be_nil end {% end %} it "reads > Float32::MAX" do pull = JSON::PullParser.new(Float64::MAX.to_s) pull.read?(Float32).should be_nil end it "reads < Float32::MIN" do pull = JSON::PullParser.new(Float64::MIN.to_s) pull.read?(Float32).should be_nil end it "reads > Float64::MAX" do pull = JSON::PullParser.new("1" + Float64::MAX.to_s) pull.read?(Float64).should be_nil end it "reads < Float64::MIN" do pull = JSON::PullParser.new("-1" + Float64::MAX.to_s) pull.read?(Float64).should be_nil end it "doesn't accept nan or infinity" do pull = JSON::PullParser.new(%("nan")) pull.read?(Float64).should be_nil pull = JSON::PullParser.new(%("infinity")) pull.read?(Float64).should be_nil pull = JSON::PullParser.new(%("+infinity")) pull.read?(Float64).should be_nil pull = JSON::PullParser.new(%("-infinity")) pull.read?(Float64).should be_nil end end it "#raise" do pull = JSON::PullParser.new("[1, 2, 3]") expect_raises(JSON::ParseException, "foo bar at line 1, column 2") do pull.raise "foo bar" end pull.read_begin_array expect_raises(JSON::ParseException, "foo bar at line 1, column 3") do pull.raise "foo bar" end end end