This commit is contained in:
2026-04-07 14:50:23 +09:00
commit b4e485502b
4778 changed files with 2017091 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
defmodule Snyk.MixProject.Common do
def save_to_file(file_path, content) do
file = case File.open(file_path, [:write]) do
{:ok, file} -> file
{:error, error_msg} -> error(error_msg)
end
IO.binwrite(file, JSON.encode!(content))
end
def error(msg) do
Mix.shell().error(msg)
System.halt(1)
end
end
defimpl JSON.Encoder, for: Regex do
def encode(_), do: {:ok, "\"regex\""}
def typeof(_), do: :string
end

View File

@@ -0,0 +1,13 @@
# Used by "mix format" and to export configuration.
export_locals_without_parens = [
run: 1,
run: 2,
activity: 1,
activity: 2
]
[
inputs: ["{mix,.formatter,.json}.exs", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: export_locals_without_parens,
export: [locals_without_parens: export_locals_without_parens]
]

View File

@@ -0,0 +1,30 @@
Copyright (c) 2013, Carlos Brito Lage
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
Neither the name of the Elixir-JSON nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,61 @@
# [Elixir JSON](https://hex.pm/packages/json)
[![Build Status](https://travis-ci.org/cblage/elixir-json.svg?branch=master)](https://travis-ci.org/cblage/elixir-json) [![Hex.pm](https://img.shields.io/hexpm/dt/json.svg?style=flat-square)](https://hex.pm/packages/json) [![Coverage Status](https://coveralls.io/repos/github/cblage/elixir-json/badge.svg?branch=master)](https://coveralls.io/github/cblage/elixir-json?branch=master) [![Inline docs](http://inch-ci.org/github/cblage/elixir-json.svg)](http://inch-ci.org/github/cblage/elixir-json)
This library provides a natively implemented JSON encoder and decoder for Elixir.
You can find the package in [hex.pm](https://hex.pm/packages/json) and the documentation in [hexdocs.pm](https://hexdocs.pm/json/readme.html).
All contributions are welcome!
# Installing
Simply add ```{:json, "~> 1.4"}``` to your project's ```mix.exs``` and run ```mix deps.get```.
# Usage
Encoding an Elixir type
```elixir
@doc "
JSON encode an Elixir list
"
list = [key: "this will be a value"]
is_list(list)
# true
list[:key]
# "this will be a value"
{status, result} = JSON.encode(list)
# {:ok, "{\"key\":\"this will be a value\"}"}
String.length(result)
# 41
```
Decoding a list from a string that contains JSON
```elixir
@doc "
JSON decode a string into an Elixir list
"
json_input = "{\"key\":\"this will be a value\"}"
{status, list} = JSON.decode(json_input)
{:ok, %{"key" => "this will be a value"}}
list[:key]
# nil
list["key"]
# "this will be a value"
```
At any time, you can turn on verbose logging for this library only.
To do so, head to config file of your application and add below lines:
```elixir
use Mix.Config
config :logger, level: :debug
config :json, log_level: :debug
```
Note that, changing only `:logger` level to `:info`, `:warn` or `:error` will silent `:json` too.
# License
The Elixir JSON library is available under the [BSD 3-Clause aka "BSD New" license](http://www.tldrlegal.com/l/BSD3)

View File

@@ -0,0 +1,100 @@
defmodule JSON do
@moduledoc """
Provides a RFC 7159, ECMA 404, and JSONTestSuite compliant JSON Encoder / Decoder
"""
require Logger
import JSON.Logger
alias JSON.Decoder
alias JSON.Encoder
@vsn "1.0.2"
@doc """
Returns a JSON string representation of the Elixir term
## Examples
iex> JSON.encode([result: "this will be a JSON result"])
{:ok, "{\\\"result\\\":\\\"this will be a JSON result\\\"}"}
"""
@spec encode(term) :: {atom, bitstring}
defdelegate encode(term), to: Encoder
@doc """
Returns a JSON string representation of the Elixir term, raises errors when something bad happens
## Examples
iex> JSON.encode!([result: "this will be a JSON result"])
"{\\\"result\\\":\\\"this will be a JSON result\\\"}"
"""
@spec encode!(term) :: bitstring
def encode!(term) do
case encode(term) do
{:ok, value} -> value
{:error, error_info} -> raise JSON.Encoder.Error, error_info: error_info
_ -> raise JSON.Encoder.Error
end
end
@doc """
Converts a valid JSON string into an Elixir term
## Examples
iex> JSON.decode("{\\\"result\\\":\\\"this will be an Elixir result\\\"}")
{:ok, Enum.into([{"result", "this will be an Elixir result"}], Map.new)}
"""
@spec decode(bitstring) :: {atom, term}
@spec decode(charlist) :: {atom, term}
defdelegate decode(bitstring_or_char_list), to: Decoder
@doc """
Converts a valid JSON string into an Elixir term, raises errors when something bad happens
## Examples
iex> JSON.decode!("{\\\"result\\\":\\\"this will be an Elixir result\\\"}")
Enum.into([{"result", "this will be an Elixir result"}], Map.new)
"""
@spec decode!(bitstring) :: term
@spec decode!(charlist) :: term
def decode!(bitstring_or_char_list) do
case decode(bitstring_or_char_list) do
{:ok, value} ->
log(:debug, fn ->
"#{__MODULE__}.decode!(#{inspect(bitstring_or_char_list)}} was sucesfull: #{
inspect(value)
}"
end)
value
{:error, {:unexpected_token, tok}} ->
log(:debug, fn ->
"#{__MODULE__}.decode!(#{inspect(bitstring_or_char_list)}} unexpected token #{tok}"
end)
raise JSON.Decoder.UnexpectedTokenError, token: tok
{:error, :unexpected_end_of_buffer} ->
log(:debug, fn ->
"#{__MODULE__}.decode!(#{inspect(bitstring_or_char_list)}} end of buffer"
end)
raise JSON.Decoder.UnexpectedEndOfBufferError
e ->
log(:debug, fn ->
"#{__MODULE__}.decode!(#{inspect(bitstring_or_char_list)}} an unknown problem occurred #{
inspect(e)
}"
end)
end
end
end

View File

@@ -0,0 +1,136 @@
defprotocol JSON.Decoder do
@moduledoc """
Defines the protocol required for converting raw JSON into Elixir terms
"""
@doc """
Returns an atom and an Elixir term
"""
@spec decode(any) :: {atom, term}
def decode(bitstring_or_char_list)
end
defmodule JSON.Decoder.DefaultImplementations do
require Logger
import JSON.Logger
defimpl JSON.Decoder, for: BitString do
@moduledoc """
JSON Decoder implementation for BitString values
"""
alias JSON.Parser, as: Parser
@doc """
decodes json in BitString format
## Examples
iex> JSON.Decoder.decode ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Decoder.decode "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Decoder.decode "-hello"
{:error, {:unexpected_token, "-hello"}}
"""
def decode(bitstring) do
log(:debug, fn -> "#{__MODULE__}.decode(#{inspect(bitstring)}) starting..." end)
bitstring
|> String.trim()
|> Parser.parse()
|> case do
{:error, error_info} ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(bitstring)}} failed with error: #{inspect(error_info)}"
end)
{:error, error_info}
{:ok, value, rest} ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(bitstring)}) trimming remainder of JSON payload #{
inspect(rest)
}..."
end)
case rest |> String.trim() do
<<>> ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(bitstring)}) successfully trimmed remainder JSON payload!"
end)
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(bitstring)}) returning {:ok. #{inspect(value)}}"
end)
{:ok, value}
rest ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(bitstring)}} failed consume entire buffer: #{rest}"
end)
{:error, {:unexpected_token, rest}}
end
end
end
end
defimpl JSON.Decoder, for: List do
@moduledoc """
JSON Decoder implementation for Charlist values
"""
alias JSON.Decoder, as: Decoder
@doc """
decodes json in BitString format
## Examples
iex> JSON.Decoder.decode ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Decoder.decode "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Decoder.decode "-hello"
{:error, {:unexpected_token, "-hello"}}
"""
def decode(charlist) do
charlist
|> to_string()
|> Decoder.decode()
|> case do
{:ok, value} ->
{:ok, value}
{:error, error_info} when is_binary(error_info) ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(charlist)}} failed with error: #{inspect(error_info)}"
end)
{:error, error_info |> to_charlist()}
{:error, {:unexpected_token, bin}} when is_binary(bin) ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(charlist)}} failed with error: #{inspect(bin)}"
end)
{:error, {:unexpected_token, bin |> to_charlist()}}
e = {:error, error_info} ->
log(:debug, fn ->
"#{__MODULE__}.decode(#{inspect(charlist)}} failed with error: #{inspect(e)}"
end)
{:error, error_info}
end
end
end
end

View File

@@ -0,0 +1,49 @@
defprotocol JSON.Encoder do
@fallback_to_any true
@moduledoc """
Defines the protocol required for converting Elixir types into JSON and inferring their json types.
"""
@doc """
Returns a JSON string representation of the Elixir term
## Examples
iex> JSON.Encoder.encode({1, :two, "three"})
{:ok, "[1,\\\"two\\\",\\\"three\\\"]"}
iex> JSON.Encoder.encode([result: "this will be a elixir result"])
{:ok, "{\\\"result\\\":\\\"this will be a elixir result\\\"}"}
iex> JSON.Encoder.encode(%{a: 1, b: 2})
{:ok, "{\\\"a\\\":1,\\\"b\\\":2}"}
"""
@spec encode(tuple | HashDict.t() | list | integer | float | map | list | atom | term) ::
{atom, bitstring}
def encode(term)
@doc """
Returns an atom that reprsents the JSON type for the term
## Examples
iex> JSON.Encoder.typeof(3)
:number
iex> JSON.Encoder.typeof({1, :two, "three"})
:array
iex> JSON.Encoder.typeof([foo: "this will be a elixir result"])
:object
iex> JSON.Encoder.typeof([result: "this will be a elixir result"])
:object
iex> JSON.Encoder.typeof(["this will be a elixir result"])
:array
iex> JSON.Encoder.typeof([foo: "bar"])
:object
"""
@spec typeof(term) :: atom
def typeof(term)
end

View File

@@ -0,0 +1,184 @@
defimpl JSON.Encoder, for: Tuple do
@doc """
Encodes an Elixir tuple into a JSON array
"""
def encode(term), do: term |> Tuple.to_list() |> JSON.Encoder.Helpers.enum_encode()
@doc """
Returns an atom that represents the JSON type for the term
"""
def typeof(_), do: :array
end
defimpl JSON.Encoder, for: HashDict do
@doc """
Encodes an Elixir HashDict into a JSON object
"""
def encode(dict), do: JSON.Encoder.Helpers.dict_encode(dict)
@doc """
Returns :object
"""
def typeof(_), do: :object
end
defimpl JSON.Encoder, for: List do
@doc """
Encodes an Elixir List into a JSON array
"""
def encode([]), do: {:ok, "[]"}
def encode(list) do
if Keyword.keyword?(list) do
JSON.Encoder.Helpers.dict_encode(list)
else
JSON.Encoder.Helpers.enum_encode(list)
end
end
@doc """
Returns an atom that represents the JSON type for the term
"""
def typeof([]), do: :array
def typeof(list) do
if Keyword.keyword?(list) do
:object
else
:array
end
end
end
defimpl JSON.Encoder, for: [Integer, Float] do
@doc """
Converts Elixir Integer and Floats into JSON Numbers
"""
# Elixir converts octal, etc into decimal when putting in strings
def encode(number), do: {:ok, "#{number}"}
@doc """
Returns an atom that represents the JSON type for the term
"""
def typeof(_), do: :number
end
defimpl JSON.Encoder, for: Atom do
@doc """
Converts Elixir Atoms into their JSON equivalents
"""
def encode(nil), do: {:ok, "null"}
def encode(false), do: {:ok, "false"}
def encode(true), do: {:ok, "true"}
def encode(atom) when is_atom(atom), do: atom |> Atom.to_string() |> JSON.Encoder.encode()
@doc """
Returns an atom that represents the JSON type for the term
"""
def typeof(boolean) when is_boolean(boolean), do: :boolean
def typeof(nil), do: :null
def typeof(atom) when is_atom(atom), do: :string
end
defimpl JSON.Encoder, for: BitString do
# 32 = ascii space, cleaner than using "? ", I think
@acii_space 32
@doc """
Converts Elixir String into JSON String
"""
def encode(bitstring), do: {:ok, <<?">> <> encode_binary_recursive(bitstring, []) <> <<?">>}
defp encode_binary_recursive(<<head::utf8, tail::binary>>, acc) do
encode_binary_recursive(tail, encode_binary_character(head, acc))
end
# stop cond
defp encode_binary_recursive(<<>>, acc), do: acc |> Enum.reverse() |> to_string
defp encode_binary_character(?", acc), do: [?", ?\\ | acc]
defp encode_binary_character(?\b, acc), do: [?b, ?\\ | acc]
defp encode_binary_character(?\f, acc), do: [?f, ?\\ | acc]
defp encode_binary_character(?\n, acc), do: [?n, ?\\ | acc]
defp encode_binary_character(?\r, acc), do: [?r, ?\\ | acc]
defp encode_binary_character(?\t, acc), do: [?t, ?\\ | acc]
defp encode_binary_character(?\\, acc), do: [?\\, ?\\ | acc]
defp encode_binary_character(char, acc) when is_number(char) and char < @acii_space do
encode_hexadecimal_unicode_control_character(char, [?u, ?\\ | acc])
end
# anything else besides these control characters, just let it through
defp encode_binary_character(char, acc) when is_number(char), do: [char | acc]
defp encode_hexadecimal_unicode_control_character(char, acc) when is_number(char) do
[
char
|> Integer.to_charlist(16)
|> zeropad_hexadecimal_unicode_control_character
|> Enum.reverse()
| acc
]
end
defp zeropad_hexadecimal_unicode_control_character([a, b, c]), do: [?0, a, b, c]
defp zeropad_hexadecimal_unicode_control_character([a, b]), do: [?0, ?0, a, b]
defp zeropad_hexadecimal_unicode_control_character([a]), do: [?0, ?0, ?0, a]
defp zeropad_hexadecimal_unicode_control_character(iolist) when is_list(iolist), do: iolist
@doc """
Returns an atom that represents the JSON type for the term
"""
def typeof(_), do: :string
end
defimpl JSON.Encoder, for: Record do
@doc """
Encodes elixir records into json objects
"""
def encode(record), do: record.to_keywords |> JSON.Encoder.Helpers.dict_encode()
@doc """
Encodes a record into a JSON object
"""
def typeof(_), do: :object
end
defimpl JSON.Encoder, for: Map do
@doc """
Encodes maps into object
"""
def encode(map), do: map |> JSON.Encoder.Helpers.dict_encode()
@doc """
Returns an atom that represents the JSON type for the term
"""
def typeof(_), do: :object
end
defimpl JSON.Encoder, for: Any do
@moduledoc """
Falllback module for encoding any other values
"""
@doc """
Encodes a map into a JSON object
"""
def encode(%{} = struct) do
struct
|> Map.to_list()
|> JSON.Encoder.Helpers.dict_encode()
end
def encode(x) do
x
|> Kernel.inspect()
|> JSON.Encoder.encode()
end
@doc """
Fallback method
"""
def typeof(struct) when is_map(struct), do: :object
def typeof(_), do: :string
end

View File

@@ -0,0 +1,45 @@
defmodule JSON.Decoder.Error do
@moduledoc """
Thrown when an unknown decoder error happens
"""
defexception message: "Invalid JSON - unknown error"
end
defmodule JSON.Decoder.UnexpectedEndOfBufferError do
@moduledoc """
Thrown when the json payload is incomplete
"""
defexception message: "Invalid JSON - unexpected end of buffer"
end
defmodule JSON.Decoder.UnexpectedTokenError do
@moduledoc """
Thrown when the json payload is invalid
"""
defexception token: nil
@doc """
Invalid JSON - Unexpected token
"""
def message(exception), do: "Invalid JSON - unexpected token >>#{exception.token}<<"
end
defmodule JSON.Encoder.Error do
@moduledoc """
Thrown when an encoder error happens
"""
defexception error_info: nil
@doc """
Invalid Term
"""
def message(exception) do
error_message = "An error occurred while encoding the JSON object"
if nil != exception.error_info do
error_message <> " >>#{exception.error_info}<<"
else
error_message
end
end
end

View File

@@ -0,0 +1,34 @@
defmodule JSON.Encoder.Helpers do
@moduledoc """
Helper functions for JSON.Encoder
"""
alias JSON.Encoder, as: Encoder
@doc """
Given an enumerable encode the enumerable as an array.
"""
def enum_encode(coll) do
{:ok, "[" <> Enum.map_join(coll, ",", &encode_item(&1)) <> "]"}
end
@doc """
Given an enumerable that yields tuples of `{key, value}` encode the enumerable
as an object.
"""
def dict_encode(coll) do
{:ok,
"{" <>
Enum.map_join(coll, ",", fn {key, object} ->
encode_item(key) <> ":" <> encode_item(object)
end) <> "}"}
end
defp encode_item(item) do
case Encoder.encode(item) do
{:ok, encoded_item} -> encoded_item
# propagate error, will trigger error in map_join
err -> err
end
end
end

View File

@@ -0,0 +1,51 @@
defmodule JSON.Logger do
@moduledoc """
Exposes separate log level configuration so developers can set logging
verbosity for json library
To configure log level only for json library, add following line in config file
use Mix.Config
# to make json be very verbose
config :json, log_level: :debug
# to make json be silent
config :json, log_level: :error
"""
require Logger
@log_levels [:error, :warn, :info, :debug]
@spec allowed_levels() :: [Logger.level()]
def allowed_levels() do
json_log_level = Application.get_env(:json, :log_level, :info)
@log_levels
|> Enum.reduce_while([], fn
^json_log_level, acc ->
{:halt, [json_log_level | acc]}
l, acc ->
{:cont, [l | acc]}
end)
|> Enum.reverse()
end
@doc """
Logs given message to logger at given log level
Supported log levels are:
- `:debug` - All messages are logged
- `:info` - only :info, :warn and :error messages are logged
- `:warn` - only :warn and :error messages are logged
- `:error` - only :error messages are logged
"""
defmacro log(level, message) do
quote bind_quoted: [level: level, message: message] do
if level in JSON.Logger.allowed_levels() do
Logger.log(level, message)
else
:ok
end
end
end
end

View File

@@ -0,0 +1,121 @@
defmodule JSON.Parser do
@moduledoc """
Implements a JSON Parser for Bitstring values
"""
alias JSON.Parser, as: Parser
alias Parser.Array, as: ArrayParser
alias Parser.Number, as: NumberParser
alias Parser.Object, as: ObjectParser
alias Parser.String, as: StringParser
require Logger
import JSON.Logger
@doc """
parses a valid JSON value, returns its elixir representation
## Examples
iex> JSON.Parser.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.parse "-hello"
{:error, {:unexpected_token, "-hello"}}
iex> JSON.Parser.parse "129245"
{:ok, 129245, ""}
iex> JSON.Parser.parse "7.something"
{:ok, 7, ".something"}
iex> JSON.Parser.parse "-88.22suffix"
{:ok, -88.22, "suffix"}
iex> JSON.Parser.parse "-12e4and then some"
{:ok, -1.2e+5, "and then some"}
iex> JSON.Parser.parse "7842490016E-12-and more"
{:ok, 7.842490016e-3, "-and more"}
iex> JSON.Parser.parse "null"
{:ok, nil, ""}
iex> JSON.Parser.parse "false"
{:ok, false, ""}
iex> JSON.Parser.parse "true"
{:ok, true, ""}
iex> JSON.Parser.parse "\\\"7.something\\\""
{:ok, "7.something", ""}
iex> JSON.Parser.parse "\\\"-88.22suffix\\\" foo bar"
{:ok, "-88.22suffix", " foo bar"}
iex> JSON.Parser.parse "\\\"star -> \\\\u272d <- star\\\""
{:ok, "star -> ✭ <- star", ""}
iex> JSON.Parser.parse "[]"
{:ok, [], ""}
iex> JSON.Parser.parse "[\\\"foo\\\", 1, 2, 1.5] lala"
{:ok, ["foo", 1, 2, 1.5], " lala"}
iex> JSON.Parser.parse "{\\\"result\\\": \\\"this will be a elixir result\\\"} lalal"
{:ok, Enum.into([{"result", "this will be a elixir result"}], Map.new), " lalal"}
"""
def parse(<<?[, _::binary>> = bin) do
log(:debug, fn -> "#{__MODULE__}.parse(bin) starting ArrayParser.parse(bin)..." end)
ArrayParser.parse(bin)
end
def parse(<<?{, _::binary>> = bin) do
log(:debug, fn -> "#{__MODULE__}.parse(bin) starting ObjectParser.parse(bin)..." end)
ObjectParser.parse(bin)
end
def parse(<<?", _::binary>> = bin) do
log(:debug, fn -> "#{__MODULE__}.parse(bin) starting ArrayParser.parse(bin)..." end)
StringParser.parse(bin)
end
def parse(<<?-, number::utf8, _::binary>> = bin) when number in ?0..?9 do
log(:debug, fn -> "#{__MODULE__}.parse(bin) starting negative NumberParser.parse(bin)..." end)
NumberParser.parse(bin)
end
def parse(<<number::utf8, _::binary>> = bin) when number in ?0..?9 do
log(:debug, fn -> "#{__MODULE__}.parse(bin) starting NumberParser.parse(bin)..." end)
NumberParser.parse(bin)
end
def parse(<<?n, ?u, ?l, ?l, rest::binary>>) do
log(:debug, fn -> "#{__MODULE__}.parse(bin) parsed `null` token." end)
{:ok, nil, rest}
end
def parse(<<?t, ?r, ?u, ?e, rest::binary>>) do
log(:debug, fn -> "#{__MODULE__}.parse(bin) parsed `true` token." end)
{:ok, true, rest}
end
def parse(<<?f, ?a, ?l, ?s, ?e, rest::binary>>) do
log(:debug, fn -> "#{__MODULE__}.parse(bin) parsed `false` token." end)
{:ok, false, rest}
end
def parse(<<>>) do
log(:debug, fn -> "#{__MODULE__}.parse(<<>>) unexpected end of buffer." end)
{:error, :unexpected_end_of_buffer}
end
def parse(json) do
log(:debug, fn -> "#{__MODULE__}.parse(json) unexpected token: #{inspect(json)}" end)
{:error, {:unexpected_token, json}}
end
end

View File

@@ -0,0 +1,126 @@
defmodule JSON.Parser.Array do
@moduledoc """
Implements a JSON Array Parser for Bitstring values
"""
alias JSON.Parser, as: Parser
require Logger
import JSON.Logger
@doc """
parses a valid JSON array value, returns its elixir list representation
## Examples
iex> JSON.Parser.Array.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.Array.parse "[1, 2 "
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.Array.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.Array.parse "[] lala"
{:ok, [], " lala"}
iex> JSON.Parser.Array.parse "[]"
{:ok, [], ""}
iex> JSON.Parser.Array.parse "[\\\"foo\\\", 1, 2, 1.5] lala"
{:ok, ["foo", 1, 2, 1.5], " lala"}
"""
def parse(<<?[, rest::binary>>) do
log(:debug, fn ->
"#{__MODULE__}.parse(#{inspect(rest)}) trimming string and the calling parse_array_contents()"
end)
rest |> String.trim() |> parse_array_contents()
end
def parse(<<>>) do
log(:debug, fn -> "#{__MODULE__}.parse(<<>>) unexpected end of buffer." end)
{:error, :unexpected_end_of_buffer}
end
def parse(json) do
log(:debug, fn -> "#{__MODULE__}.parse(<<>>) unexpected token: #{inspect(json)}" end)
{:error, {:unexpected_token, json}}
end
# begin parse array
defp parse_array_contents(json) when is_binary(json) do
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(#{inspect(json)}) beginning to parse array contents..."
end)
parse_array_contents([], json)
end
# stop condition
defp parse_array_contents(acc, <<?], rest::binary>>) do
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(#{inspect(acc)}, #{inspect(rest)}) finished parsing array contents."
end)
{:ok, Enum.reverse(acc), rest}
end
# error condition
defp parse_array_contents(_, <<>>) do
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(acc, <<>>) unexpected end of buffer."
end)
{:error, :unexpected_end_of_buffer}
end
defp parse_array_contents(acc, json) do
json
|> String.trim()
|> Parser.parse()
|> case do
{:error, error_info} ->
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(#{inspect(acc)}, #{inspect(json)}) generated an error: #{
inspect(error_info)
}"
end)
{:error, error_info}
{:ok, value, after_value} ->
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(acc, json) sucessfully parsed value `#{
inspect(value)
}`, with
after_value=#{inspect(after_value)}"
end)
after_value
|> String.trim()
|> case do
<<?,, after_comma::binary>> ->
trimmed = String.trim(after_comma)
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(acc, json) found a comma, continuing parsing of #{
inspect(trimmed)
}"
end)
parse_array_contents([value | acc], trimmed)
rest ->
log(:debug, fn ->
"#{__MODULE__}.parse_array_contents(acc, json) continuing parsing of #{
inspect(rest)
}"
end)
parse_array_contents([value | acc], rest)
end
end
end
end

View File

@@ -0,0 +1,106 @@
defmodule JSON.Parser.Number do
@moduledoc """
Implements a JSON Numeber Parser for Bitstring values
"""
@doc """
parses a valid JSON numerical value, returns its elixir numerical representation
## Examples
iex> JSON.Parser.Number.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.Number.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.Number.parse "-hello"
{:error, {:unexpected_token, "hello"}}
iex> JSON.Parser.Number.parse "129245"
{:ok, 129245, ""}
iex> JSON.Parser.Number.parse "7.something"
{:ok, 7, ".something"}
iex> JSON.Parser.Number.parse "7.4566something"
{:ok, 7.4566, "something"}
iex> JSON.Parser.Number.parse "-88.22suffix"
{:ok, -88.22, "suffix"}
iex> JSON.Parser.Number.parse "-12e4and then some"
{:ok, -1.2e+5, "and then some"}
iex> JSON.Parser.Number.parse "7842490016E-12-and more"
{:ok, 7.842490016e-3, "-and more"}
"""
def parse(<<?-, rest::binary>>) do
case parse(rest) do
{:ok, number, json} -> {:ok, -1 * number, json}
{:error, error_info} -> {:error, error_info}
end
end
def parse(binary) do
case binary do
<<number::utf8, _::binary>> when number in ?0..?9 ->
binary |> to_integer |> add_fractional |> apply_exponent
<<>> ->
{:error, :unexpected_end_of_buffer}
_ ->
{:error, {:unexpected_token, binary}}
end
end
# error condition
defp add_fractional({:error, error_info}), do: {:error, error_info}
# stop condition
defp add_fractional({:ok, acc, bin}) do
case bin do
<<?., after_dot::binary>> ->
case after_dot do
<<c::utf8, _::binary>> when c in ?0..?9 ->
{fractional, rest} = parse_fractional(after_dot, 0, 10.0)
{:ok, acc + fractional, rest}
_ ->
{:ok, acc, bin}
end
_ ->
{:ok, acc, bin}
end
end
defp parse_fractional(<<number::utf8, rest::binary>>, acc, power) when number in ?0..?9 do
parse_fractional(rest, acc + (number - ?0) / power, power * 10)
end
defp parse_fractional(json, acc, _) when is_binary(json), do: {acc, json}
# error condition
defp apply_exponent({:error, error_info}), do: {:error, error_info}
# stop condition
defp apply_exponent({:ok, acc, <<exponent::utf8, rest::binary>>}) when exponent in 'eE' do
case to_integer(rest) do
{:ok, power, rest} -> {:ok, acc * :math.pow(10, power), rest}
{:error, error_info} -> {:error, error_info}
end
end
defp apply_exponent({:ok, acc, json}), do: {:ok, acc, json}
defp to_integer(<<>>), do: {:error, :unexpected_end_of_buffer}
defp to_integer(binary) do
case Integer.parse(binary) do
{result, rest} when is_integer(result) and is_binary(rest) -> {:ok, result, rest}
:error -> {:error, {:unexpected_token, binary}}
end
end
end

View File

@@ -0,0 +1,92 @@
defmodule JSON.Parser.Object do
@moduledoc """
Implements a JSON Object Parser for Bitstring values
"""
alias JSON.Parser, as: Parser
@doc """
parses a valid JSON object value, returns its elixir representation
## Examples
iex> JSON.Parser.Object.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.Object.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.Object.parse "[] "
{:error, {:unexpected_token, "[] "}}
iex> JSON.Parser.Object.parse "[]"
{:error, {:unexpected_token, "[]"}}
iex> JSON.Parser.Object.parse "[\\\"foo\\\", 1, 2, 1.5] lala"
{:error, {:unexpected_token, "[\\\"foo\\\", 1, 2, 1.5] lala"}}
iex> JSON.Parser.Object.parse "{\\\"result\\\": \\\"this will be a elixir result\\\"} lalal"
{:ok, Enum.into([{"result", "this will be a elixir result"}], Map.new), " lalal"}
"""
def parse(<<?{, rest::binary>>) do
rest
|> String.trim()
|> parse_object_contents()
end
def parse(<<>>), do: {:error, :unexpected_end_of_buffer}
def parse(json), do: {:error, {:unexpected_token, json}}
# Object Parsing
defp parse_object_key(json) do
case Parser.String.parse(json) do
{:error, error_info} ->
{:error, error_info}
{:ok, key, after_key} ->
case String.trim(after_key) do
<<?:, after_colon::binary>> ->
{:ok, key, String.trim(after_colon)}
<<>> ->
{:error, :unexpected_end_of_buffer}
_ ->
{:error, {:unexpected_token, String.trim(after_key)}}
end
end
end
defp parse_object_value(acc, key, after_key) do
case Parser.parse(after_key) do
{:error, error_info} ->
{:error, error_info}
{:ok, value, after_value} ->
acc = Map.put(acc, key, value)
after_value
|> String.trim()
|> case do
<<?,, after_comma::binary>> ->
parse_object_contents(acc, String.trim(after_comma))
rest ->
parse_object_contents(acc, rest)
end
end
end
defp parse_object_contents(json), do: parse_object_contents(Map.new(), json)
defp parse_object_contents(acc, <<?", _::binary>> = bin) do
case parse_object_key(bin) do
{:error, error_info} -> {:error, error_info}
{:ok, key, after_key} -> parse_object_value(acc, key, after_key)
end
end
defp parse_object_contents(acc, <<?}, rest::binary>>), do: {:ok, acc, rest}
defp parse_object_contents(_, <<>>), do: {:error, :unexpected_end_of_buffer}
defp parse_object_contents(_, json), do: {:error, {:unexpected_token, json}}
end

View File

@@ -0,0 +1,99 @@
defmodule JSON.Parser.String do
@moduledoc """
Implements a JSON String Parser for Bitstring values
"""
alias JSON.Parser.Unicode, as: UnicodeParser
use Bitwise
@doc """
parses a valid JSON string, returns its elixir representation
## Examples
iex> JSON.Parser.String.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.String.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.String.parse "-hello"
{:error, {:unexpected_token, "-hello"}}
iex> JSON.Parser.String.parse "129245"
{:error, {:unexpected_token, "129245"}}
iex> JSON.Parser.String.parse "\\\"7.something\\\""
{:ok, "7.something", ""}
iex> JSON.Parser.String.parse "\\\"-88.22suffix\\\" foo bar"
{:ok, "-88.22suffix", " foo bar"}
iex> JSON.Parser.String.parse "\\\"star -> \\\\u272d <- star\\\""
{:ok, "star -> ✭ <- star", ""}
iex> JSON.Parser.String.parse "\\\"\\\\u00df ist wunderbar\\\""
{:ok, "ß ist wunderbar", ""}
iex> JSON.Parser.String.parse "\\\"Rafaëlla\\\" foo bar"
{:ok, "Rafaëlla", " foo bar"}
iex> JSON.Parser.String.parse "\\\"Éloise woot\\\" Éloise"
{:ok, "Éloise woot", " Éloise"}
"""
def parse(<<?"::utf8, json::binary>>), do: parse_string_contents(json, [])
def parse(<<>>), do: {:error, :unexpected_end_of_buffer}
def parse(json), do: {:error, {:unexpected_token, json}}
# stop conditions
defp parse_string_contents(<<>>, _), do: {:error, :unexpected_end_of_buffer}
# found the closing ", lets reverse the acc and encode it as a string!
defp parse_string_contents(<<?"::utf8, json::binary>>, acc) do
encoded = acc |> Enum.reverse() |> List.to_string()
{:ok, encoded, json}
end
# parsing
defp parse_string_contents(<<?\\, ?f, json::binary>>, acc),
do: parse_string_contents(json, [?\f | acc])
defp parse_string_contents(<<?\\, ?n, json::binary>>, acc),
do: parse_string_contents(json, [?\n | acc])
defp parse_string_contents(<<?\\, ?r, json::binary>>, acc),
do: parse_string_contents(json, [?\r | acc])
defp parse_string_contents(<<?\\, ?t, json::binary>>, acc),
do: parse_string_contents(json, [?\t | acc])
defp parse_string_contents(<<?\\, ?", json::binary>>, acc),
do: parse_string_contents(json, [?" | acc])
defp parse_string_contents(<<?\\, ?\\, json::binary>>, acc),
do: parse_string_contents(json, [?\\ | acc])
defp parse_string_contents(<<?\\, ?/, json::binary>>, acc),
do: parse_string_contents(json, [?/ | acc])
defp parse_string_contents(<<?\\, ?u, _::binary>> = bin, acc) do
case UnicodeParser.parse(bin) do
{:error, error_info} ->
{:error, error_info}
{:ok, decoded_unicode_codepoint, after_codepoint} ->
case decoded_unicode_codepoint do
<<_::utf8>> ->
parse_string_contents(after_codepoint, [decoded_unicode_codepoint | acc])
_ ->
{:error, {:unexpected_token, bin}}
end
end
end
defp parse_string_contents(<<char::utf8, json::binary>>, acc) do
parse_string_contents(json, [char | acc])
end
end

View File

@@ -0,0 +1,67 @@
defmodule JSON.Parser.Unicode do
@moduledoc """
Implements a JSON Unicode Parser for Bitstring values
"""
use Bitwise
@doc """
parses a valid chain of escaped unicode and returns the string representation,
plus the remainder of the string
## Examples
iex> JSON.Parser.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.parse "-hello"
{:error, {:unexpected_token, "-hello"}}
"""
def parse(<<?\\, ?u, json::binary>>), do: parse_escaped_unicode_codepoint(json, 0, 0)
def parse(<<>>), do: {:error, :unexpected_end_of_buffer}
def parse(json), do: {:error, {:unexpected_token, json}}
# Parsing sugorrogate pairs
# http://unicodebook.readthedocs.org/unicode_encodings.html
# Inspired by Poison's function
defp parse_escaped_unicode_codepoint(
<<?d, hex::utf8, f1, f2, ?\\, ?u, ?d, hex2::utf8, s1, s2, json::binary>>,
_,
0
)
when hex >= 56 do
first_part = (List.to_integer([?d, hex, f1, f2], 16) &&& 1023) <<< 10
second_part = List.to_integer([?d, hex2, s1, s2], 16) &&& 1023
complete = 0x10000 + first_part + second_part
{:ok, <<complete::utf8>>, json}
end
# parse_escaped_unicode_codepoint tries to parse
# a valid hexadecimal (composed of 4 characters) value that potentially
# represents a unicode codepoint
defp parse_escaped_unicode_codepoint(json, acc, chars_parsed) when 4 === chars_parsed do
{:ok, <<acc::utf8>>, json}
end
defp parse_escaped_unicode_codepoint(<<hex::utf8, json::binary>>, acc, chars_parsed)
when hex in ?0..?9 do
parse_escaped_unicode_codepoint(json, 16 * acc + hex - ?0, chars_parsed + 1)
end
defp parse_escaped_unicode_codepoint(<<hex::utf8, json::binary>>, acc, chars_parsed)
when hex in ?a..?f do
parse_escaped_unicode_codepoint(json, 16 * acc + 10 + hex - ?a, chars_parsed + 1)
end
defp parse_escaped_unicode_codepoint(<<hex::utf8, json::binary>>, acc, chars_parsed)
when hex in ?A..?F do
parse_escaped_unicode_codepoint(json, 16 * acc + 10 + hex - ?A, chars_parsed + 1)
end
defp parse_escaped_unicode_codepoint(<<>>, _, _), do: {:error, :unexpected_end_of_buffer}
defp parse_escaped_unicode_codepoint(json, _, _), do: {:error, {:unexpected_token, json}}
end

View File

@@ -0,0 +1,65 @@
defmodule ElixirJSON_140_SNAPSHOT.Mixfile do
use Mix.Project
@version "1.4.1"
def project do
[
app: :json,
version: @version,
elixir: "~> 1.7",
deps: deps(Mix.env()),
description: "The First Native Elixir library for JSON encoding and decoding",
package: package(),
source_url: "https://github.com/cblage/elixir-json",
homepage_url: "https://hex.pm/packages/json",
test_coverage: [tool: ExCoveralls],
docs: docs(),
preferred_cli_env: [
docs: :docs,
coveralls: :test,
test: :test
],
dialyzer_ignored_warnings: [
{:warn_umatched_return, {:_, :_}, {:unmatched_return, :_}}
]
]
end
def application do
[applications: applications(Mix.env())]
end
defp applications(:dev), do: [] ++ applications(:default)
defp applications(_all), do: [:logger]
def deps(_) do
[
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:dialyzex, "~> 1.2", only: [:dev]},
{:excoveralls, "~> 0.13.4", only: :test, optional: true, runtime: false}
]
end
defp docs() do
[
main: "readme",
name: "JSON",
source_ref: "v#{@version}",
canonical: "http://hexdocs.pm/json",
source_url: "https://github.com/cblage/elixir-json",
extras: [
"README.md"
]
]
end
def package do
[
maintainers: ["cblage"],
licenses: ["BSD 3-Clause"],
links: %{"GitHub" => "https://github.com/cblage/elixir-json"}
]
end
end

View File

@@ -0,0 +1,19 @@
defmodule Mix.Tasks.Read.Mix do
use Mix.Task
@impl Mix.Task
def run(args) do
import Snyk.MixProject.Common
import Snyk.MixProject.Mix.Project
project_path = Enum.join(args, " ")
data = load_mix_project(project_path)
json_file_path = "snyk-mix-#{System.system_time(:millisecond)}.json";
save_to_file(json_file_path, data)
IO.puts(json_file_path)
end
end

View File

@@ -0,0 +1,61 @@
import Snyk.MixProject.Common
defmodule Snyk.MixProject.Mix.Project do
def load_mix_project(""), do: error("Please provide a valid path for the project")
def load_mix_project(path) do
manifest = load_manifest(path)
apps = get_apps(manifest[:apps_path], path)
lock_file_name = get_lock_file_name(manifest[:lockfile])
lock_file_path = Path.join(path, lock_file_name)
lock_file = read_file(lock_file_path)
parent_umbrella_manifest = case Path.dirname(lock_file_path) do
^path -> nil
parent_path -> load_manifest(parent_path, "parent_app")
end
%{
manifest: manifest,
lock: lock_file,
apps: apps,
parent_umbrella_manifest: parent_umbrella_manifest
}
end
defp read_file(path) do
Path.expand(path)
|> Code.eval_file()
end
defp get_lock_file_name(nil), do: get_lock_file_name("")
defp get_lock_file_name(""), do: "mix.lock"
defp get_lock_file_name(filename), do: filename
defp load_manifest(path), do: load_manifest(path, "root_app")
defp load_manifest(path, app) do
Mix.Project.in_project(
String.to_atom(app),
path,
fn module ->
module.project ++ [module_name: inspect(module)]
end
)
end
defp get_apps(nil, _), do: nil
defp get_apps(apps_path, path) do
Path.join([path, apps_path, "/*/mix.exs"])
|> Path.absname
|> Path.wildcard
|> Enum.map(fn path -> Path.dirname(path) end)
|> Enum.reduce(
%{},
fn full_project_path, acc ->
relative_project_path = Path.relative_to(full_project_path, path)
Map.put(acc, relative_project_path, load_manifest(full_project_path, relative_project_path))
end
)
end
end

View File

@@ -0,0 +1,23 @@
defmodule Snyk.MixProject do
use Mix.Project
def project do
[
app: :snyk,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger, :iex]
]
end
defp deps do
[]
end
end

View File

@@ -0,0 +1 @@
%{}