# encoding: UTF-8

require 'spec_helper'

describe "FFI_Yajl::Parser" do

  shared_examples_for "correct json parsing" do
    context "when json has 23456789012E666" do
      let(:json) {  '{"key": 23456789012E666}' }

      it "should return infinity" do
        infinity = (1.0/0)
        expect(parser).to eq({"key" => infinity})
      end
    end

    context "when parsing nil" do
      let(:json) { nil }
      it "should not coredump ruby" do
        expect{ parser }.to raise_error(FFI_Yajl::ParseError)
      end
    end

    context "when parsing bare int" do
      let(:json) { "1" }
      it "should parse to the int value" do
        expect( parser ).to eq(1)
      end
    end

    context "when parsing bare string" do
      let(:json) { '"a"' }
      it "should parse to the string value" do
        expect( parser ).to eq("a")
      end
    end

    context "when parsing bare true" do
      let(:json) { "true" }
      it "should parse to the true value" do
        expect( parser ).to eq(true)
      end
    end

    context "when parsing bare false" do
      let(:json) { "false" }
      it "should parse to the false value" do
        expect( parser ).to eq(false)
      end
    end

    context "when parsing bare null" do
      let(:json) { "null" }
      it "should parse to the nil value" do
        expect( parser ).to eq(nil)
      end
    end

    context "when parsing bare float" do
      let(:json) { "1.1" }
      it "should parse to the a float" do
        expect( parser ).to eq(1.1)
      end
    end

    context "when json has comments" do
      let(:json) { '{"key": /* this is a comment */ "value"}' }

      context "when allow_comments is false" do
        let(:options) { { :allow_comments => false } }

        it "should not parse" do
          expect{parser}.to raise_error(FFI_Yajl::ParseError)
        end
      end

      context "when allow_comments is true" do
        let(:options) { { :allow_comments => true } }

        it "should parse" do
          expect(parser).to eq({"key"=>"value"})
        end
      end

      context "by default" do
        let(:options) { }

        it "should parse" do
          expect(parser).to eq({"key"=>"value"})
        end
      end
    end

    context "when json has multiline comments" do
      let(:json) { %Q{{"key": \n/*\n this is a multiline comment \n*/\n "value"}} }

      context "when allow_comments is false" do
        let(:options) { { :allow_comments => false } }

        it "should not parse" do
          expect{parser}.to raise_error(FFI_Yajl::ParseError)
        end
      end

      context "when allow_comments is true" do
        let(:options) { { :allow_comments => true } }

        it "should parse" do
          expect(parser).to eq({"key"=>"value"})
        end
      end
    end

    context "when json has inline comments" do
      let(:json) { %Q{{"key": \n// this is an inline comment\n "value"}} }

      context "when allow_comments is false" do
        let(:options) { { :allow_comments => false } }

        it "should not parse" do
          expect{parser}.to raise_error(FFI_Yajl::ParseError)
        end
      end

      context "when allow_comments is true" do
        let(:options) { { :allow_comments => true } }

        it "should parse" do
          expect(parser).to eq({"key"=>"value"})
        end
      end
    end

    context "when json is invalid UTF8" do
      let(:json) { "[\"#{"\201\203"}\"]" }

      it "should not parse by default" do
        expect{parser}.to raise_error(FFI_Yajl::ParseError)
      end

      context "when :dont_validate_strings is set to true" do
        let(:options) { { :dont_validate_strings => true } }

        it "should parse" do
          expect(parser).to eq(["\x81\x83"])
        end
      end

      context "when :dont_validate_strings is set to false" do
        let(:options) { { :dont_validate_strings => false } }

        it "should not parse" do
          expect{parser}.to raise_error(FFI_Yajl::ParseError)
        end
      end

      context "when :check_utf8 is set to true" do
        let(:options) { { :check_utf8 => true } }

        it "should not parse" do
          expect{parser}.to raise_error(FFI_Yajl::ParseError)
        end

        context "when :dont_validate_strings is set to true" do
          let(:options) { { :check_utf8 => true, :dont_validate_strings => true } }

          it "should raise an ArgumentError" do
            expect{parser}.to raise_error(ArgumentError)
          end
        end

        context "when :dont_validate_strings is set to false" do
          let(:options) { { :check_utf8 => true, :dont_validate_strings => false } }

          it "should not parse" do
            expect{parser}.to raise_error(FFI_Yajl::ParseError)
          end
        end
      end

      context "when :check_utf8 is set to false" do
        let(:options) { { :check_utf8 => false } }

        it "should parse" do
          expect(parser).to eq(["\x81\x83"])
        end

        context "when :dont_validate_strings is set to true" do
          let(:options) { { :check_utf8 => false, :dont_validate_strings => true } }

          it "should parse" do
            expect(parser).to eq(["\x81\x83"])
          end
        end

        context "when :dont_validate_strings is set to false" do
          let(:options) { { :check_utf8 => false, :dont_validate_strings => false } }

          it "should raise an ArgumentError" do
            expect{parser}.to raise_error(ArgumentError)
          end
        end
      end
    end

    context "when JSON is a StringIO" do
      let(:json) { StringIO.new('{"key": 1234}') }

      it "should parse" do
        expect(parser).to eq({"key" => 1234})
      end
    end

    context "when parsing a JSON string" do
      let(:json) { '{"key": 1234}' }

      it "should parse correctly" do
        expect(parser).to eq({"key" => 1234})
      end

      context "when symbolize_keys is true" do
        let(:options) { { :symbolize_keys => true } }

        it "should symbolize keys correctly" do
          expect(parser).to eq({:key => 1234})
        end
      end

      context "when passing a block" do
        it "should parse correctly" do
          skip "handle blocks"
          output = nil
          parser do |obj|
            output = obj
          end
          expect(output).to eq({"key" => 1234})
        end
      end
    end

    context "when parsing a JSON hash with only strings" do
      let(:json) { '{"key": "value"}' }

      if RUBY_VERSION.to_f >= 1.9
        context "when Encoding.default_internal is nil" do
          before do
            @saved_encoding = Encoding.default_internal
            Encoding.default_internal = nil
          end
          after do
            Encoding.default_internal = @saved_encoding
          end
          it "encodes keys to UTF-8" do
            expect(parser.keys.first.encoding).to eql(Encoding.find('utf-8'))
          end
          it "encodes values to UTF-8" do
            expect(parser.values.first.encoding).to eql(Encoding.find('utf-8'))
          end
        end

        %w{utf-8 us-ascii}.each do |encoding|
          context "when Encoding.default_internal is #{encoding}" do
            before do
              @saved_encoding = Encoding.default_internal
              Encoding.default_internal = nil
            end
            after do
              Encoding.default_internal = @saved_encoding
            end
            it "encodes keys to #{encoding}" do
              skip "fix us-ascii" if encoding == "us-ascii"
              expect(parser.keys.first.encoding).to eql(Encoding.find(encoding))
            end
            it "encodes values to #{encoding}" do
              skip "fix us-ascii" if encoding == "us-ascii"
              expect(parser.values.first.encoding).to eql(Encoding.find(encoding))
            end
          end
        end
      end
    end

    context "when a parsed key has utf-8 multibyte characters" do
      let(:json) { '{"日本語": 1234}' }

      it "should parse correctly" do
        expect(parser).to eq({"日本語" => 1234})
      end

      context "when symbolize_keys is true" do
        let(:options) { { :symbolize_keys => true } }

        it "should symbolize keys correctly" do
          expect(parser).to eq({:"日本語" => 1234})
        end

        if RUBY_VERSION.to_f >= 1.9
          it "should parse non-ascii symbols in UTF-8" do
            expect(parser.keys.fetch(0).encoding).to eq(Encoding::UTF_8)
          end
        end
      end
    end

    context "when parsing 2147483649" do
      let(:json) { "{\"id\": 2147483649}" }

      it "should parse corectly" do
        expect(parser).to eql({"id" => 2147483649})
      end
    end

    context "when parsing 5687389800" do
      let(:json) { "{\"id\": 5687389800}" }

      it "should parse corectly" do
        expect(parser).to eql({"id" => 5687389800})
      end
    end

    context "when parsing 1046289770033519442869495707521600000000" do
      let(:json) { "{\"id\": 1046289770033519442869495707521600000000}" }

      it "should parse corectly" do
        expect(parser).to eql({"id" => 1046289770033519442869495707521600000000})
      end
    end

    # NOTE: we are choosing to be compatible with yajl-ruby here vs. JSON
    # gem and libyajl C behavior (which is to throw an exception in this case)
    context "when the JSON is empty string" do
      let(:json) { '' }

      it "returns nil" do
        expect(parser).to be_nil
      end
    end

    # NOTE: this fixes yajl-ruby being too permissive
    context "when dealing with too much or too little input" do
      context "when trailing braces are missing" do
        let(:json) { '{"foo":{"foo": 1234}' }

        it "raises an exception" do
          expect { parser }.to raise_error(FFI_Yajl::ParseError)
        end
      end

      context "when trailing brackets are missing" do
        let(:json) { '[["foo", "bar"]' }

        it "raises an exception" do
          expect { parser }.to raise_error(FFI_Yajl::ParseError)
        end
      end

      context "when an extra brace is present" do
        let(:json) { '{"foo":{"foo": 1234}}}' }

        it "raises an exception" do
          expect { parser }.to raise_error(FFI_Yajl::ParseError)
        end

        context "with allow_trailing_garbage" do
          let(:options) { { :allow_trailing_garbage => true } }
          it "parses" do
            expect(parser).to eq({"foo"=>{"foo"=>1234}})
          end
        end

      end

      context "when an extra bracket is present" do
        let(:json) { '[["foo", "bar"]]]' }

        it "raises an exception" do
          expect { parser }.to raise_error(FFI_Yajl::ParseError)
        end
      end
    end

    context "when parsing heavy metal umlauts in keys" do
      let(:json) { '{"München": "Bayern"}' }

      it "correctly parses" do
        expect(parser).to eql( "München" => "Bayern" )
      end
    end

    context "when parsing floats" do
      context "parses simple floating point values" do
        let(:json) { '{"foo": 3.14159265358979}' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 3.14159265358979 )
        end
      end

      context "parses simple negative floating point values" do
        let(:json) { '{"foo":-2.00231930436153}' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => -2.00231930436153 )
        end
      end

      context "parses floats with negative exponents and a large E" do
        let(:json) { '{"foo": 1.602176565E-19}' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 1.602176565e-19 )
        end
      end

      context "parses floats with negative exponents and a small e" do
        let(:json) { '{"foo": 6.6260689633e-34 }' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 6.6260689633e-34 )
        end
      end

      context "parses floats with positive exponents and a large E" do
        let(:json) { '{"foo": 6.0221413E+23}' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 6.0221413e+23 )
        end
      end

      context "parses floats with positive exponents and a small e" do
        let(:json) { '{"foo": 8.9875517873681764e+9 }' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 8.9875517873681764e+9 )
        end
      end

      context "parses floats with an exponent without a sign and a large E" do
        let(:json) { '{"foo": 2.99792458E8  }' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 2.99792458e+8 )
        end
      end

      context "parses floats with an exponent without a sign and a small e" do
        let(:json) { '{"foo": 1.0973731568539e7 }' }

        it "correctly parses" do
          expect(parser).to eql( "foo" => 1.0973731568539e+7 )
        end
      end
    end

    context "when parsing big floats", :ruby_gte_193 => true do
      let(:json) { '[0.' + '1' * 2**23 + ']' }

      it "parses" do
        expect{ parser }.not_to raise_error
      end
    end

    context "when parsing long hash keys with symbolize_keys option", :ruby_gte_193 => true do
      let(:json) { '{"' + 'a' * 2**23 + '": 0}' }
      let(:options) { { :symbolize_keys => true } }

      it "parses" do
        expect{ parser }.not_to raise_error
      end
    end
  end

  context "when options are set to empty hash" do
    let(:options) { {} }

    context "when using a parsing object" do
      let(:parser) { FFI_Yajl::Parser.new(options).parse(json) }

      it_behaves_like "correct json parsing"
    end

    context "when using the class method" do
      let(:parser) { FFI_Yajl::Parser.parse(json, options) }

      it_behaves_like "correct json parsing"
    end
  end

  context "when options are set to nil" do
    let(:options) { nil }

    context "when using a parsing object" do
      let(:parser) { FFI_Yajl::Parser.new(options).parse(json) }

      it_behaves_like "correct json parsing"
    end

    context "when using the class method" do
      let(:parser) { FFI_Yajl::Parser.parse(json, options) }

      it_behaves_like "correct json parsing"
    end
  end

  context "when options default to nothing" do
    let(:options) { nil }

    context "when using a parsing object" do
      let(:parser) do
        if options.nil?
          FFI_Yajl::Parser.new.parse(json)
        else
          FFI_Yajl::Parser.new(options).parse(json)
        end
      end

      it_behaves_like "correct json parsing"
    end

    context "when using the class method" do
      let(:parser) do
        if options.nil?
          FFI_Yajl::Parser.parse(json)
        else
          FFI_Yajl::Parser.parse(json, options)
        end
      end

      it_behaves_like "correct json parsing"
    end
  end
end
