Files
n8n-openai-adapter/lib/elixir/lib/json.ex
T

548 lines
16 KiB
Elixir

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
defprotocol JSON.Encoder do
@moduledoc """
A protocol for custom JSON encoding of data structures.
If you have a struct, you can derive the implementation of this protocol
by specifying which fields should be encoded to JSON:
@derive {JSON.Encoder, only: [...]}
defstruct ...
Additionally, you can exclude specific fields using the `:except` option or
encode all fields by omitting both options entirely, but these should be used
with caution:
@derive {JSON.Encoder, except: [...]}
defstruct ...
@derive JSON.Encoder
defstruct ...
> #### Leaking Private Information {: .error}
>
> Prefer using `:only` to avoid accidentally leaking private information when
> new fields are added. Other approaches should be used with auction.
You can also use `Protocol.derive/3` if you don't own the struct that you want
to encode to JSON:
Protocol.derive(JSON.Encoder, NameOfTheStruct, only: [...])
Protocol.derive(JSON.Encoder, NameOfTheStruct, except: [...])
Protocol.derive(JSON.Encoder, NameOfTheStruct)
"""
@undefined_impl_description """
the protocol must be explicitly implemented.
If you have a struct, you can derive the implementation specifying \
which fields should be encoded to JSON:
@derive {JSON.Encoder, only: [....]}
defstruct ...
It is also possible to encode all fields, although this should be \
used carefully to avoid accidentally leaking private information \
when new fields are added:
@derive JSON.Encoder
defstruct ...
Finally, if you don't own the struct you want to encode to JSON, \
you may use Protocol.derive/3 placed outside of any module:
Protocol.derive(JSON.Encoder, NameOfTheStruct, only: [...])
Protocol.derive(JSON.Encoder, NameOfTheStruct)\
"""
@impl true
defmacro __deriving__(module, opts) do
fields = module |> Macro.struct_info!(__CALLER__) |> Enum.map(& &1.field)
fields = fields_to_encode(fields, opts)
vars = Macro.generate_arguments(length(fields), __MODULE__)
kv = Enum.zip(fields, vars)
{io, _prefix} =
Enum.flat_map_reduce(kv, ?{, fn {field, value}, prefix ->
key = IO.iodata_to_binary([prefix, :elixir_json.encode_binary(Atom.to_string(field)), ?:])
{[key, quote(do: encoder.(unquote(value), encoder))], ?,}
end)
io = if io == [], do: "{}", else: io ++ [?}]
quote do
defimpl JSON.Encoder, for: unquote(module) do
def encode(%{unquote_splicing(kv)}, encoder) do
unquote(io)
end
end
end
end
defp fields_to_encode(fields, opts) do
cond do
only = Keyword.get(opts, :only) ->
case only -- fields do
[] ->
only
error_keys ->
raise ArgumentError,
"unknown struct fields #{inspect(error_keys)} specified in :only. Expected one of: " <>
"#{inspect(fields -- [:__struct__])}"
end
except = Keyword.get(opts, :except) ->
case except -- fields do
[] ->
fields -- [:__struct__ | except]
error_keys ->
raise ArgumentError,
"unknown struct fields #{inspect(error_keys)} specified in :except. Expected one of: " <>
"#{inspect(fields -- [:__struct__])}"
end
true ->
fields -- [:__struct__]
end
end
@doc """
A function invoked to encode the given term to `t:iodata/0`.
"""
def encode(term, encoder)
end
defimpl JSON.Encoder, for: Atom do
def encode(value, encoder) do
case value do
nil -> "null"
true -> "true"
false -> "false"
_ -> encoder.(Atom.to_string(value), encoder)
end
end
end
defimpl JSON.Encoder, for: BitString do
def encode(value, _encoder) do
:elixir_json.encode_binary(value)
end
end
defimpl JSON.Encoder, for: List do
def encode(value, encoder) do
:elixir_json.encode_list(value, encoder)
end
end
defimpl JSON.Encoder, for: Integer do
def encode(value, _encoder) do
:elixir_json.encode_integer(value)
end
end
defimpl JSON.Encoder, for: Float do
def encode(value, _encoder) do
:elixir_json.encode_float(value)
end
end
defimpl JSON.Encoder, for: Map do
def encode(value, encoder) do
case :maps.next(:maps.iterator(value)) do
:none ->
"{}"
{key, value, iterator} ->
[?{, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
end
end
defp next(iterator, encoder) do
case :maps.next(iterator) do
:none ->
"}"
{key, value, iterator} ->
[?,, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
end
end
# Erlang supports only numbers, binaries, and atoms as keys,
# we support anything that implements the String.Chars protocol.
defp key(key, encoder) when is_atom(key), do: encoder.(Atom.to_string(key), encoder)
defp key(key, encoder) when is_binary(key), do: encoder.(key, encoder)
defp key(key, encoder), do: encoder.(String.Chars.to_string(key), encoder)
end
defimpl JSON.Encoder, for: Duration do
def encode(value, _encoder) do
[?", Duration.to_iso8601(value), ?"]
end
end
defimpl JSON.Encoder, for: Date do
def encode(%{calendar: Calendar.ISO} = date, _encoder) do
%{year: year, month: month, day: day} = date
[?", Calendar.ISO.date_to_iodata(year, month, day), ?"]
end
def encode(value, _encoder) do
[?", Date.to_iso8601(value), ?"]
end
end
defimpl JSON.Encoder, for: Time do
def encode(%{calendar: Calendar.ISO} = time, _encoder) do
%{
hour: hour,
minute: minute,
second: second,
microsecond: microsecond
} = time
[?", Calendar.ISO.time_to_iodata(hour, minute, second, microsecond), ?"]
end
def encode(value, _encoder) do
[?", Time.to_iso8601(value), ?"]
end
end
defimpl JSON.Encoder, for: NaiveDateTime do
def encode(%{calendar: Calendar.ISO} = naive_datetime, _encoder) do
%{
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond
} = naive_datetime
[
?",
Calendar.ISO.date_to_iodata(year, month, day),
?T,
Calendar.ISO.time_to_iodata(hour, minute, second, microsecond),
?"
]
end
def encode(value, _encoder) do
[?", NaiveDateTime.to_iso8601(value), ?"]
end
end
defimpl JSON.Encoder, for: DateTime do
def encode(%{calendar: Calendar.ISO} = datetime, _encoder) do
%{
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond,
time_zone: time_zone,
utc_offset: utc_offset,
std_offset: std_offset
} = datetime
[
?",
Calendar.ISO.date_to_iodata(year, month, day),
?T,
Calendar.ISO.time_to_iodata(hour, minute, second, microsecond),
Calendar.ISO.offset_to_iodata(utc_offset, std_offset, time_zone, :extended),
?"
]
end
def encode(value, _encoder) do
[?", DateTime.to_iso8601(value), ?"]
end
end
defmodule JSON.DecodeError do
@moduledoc """
The exception raised by `JSON.decode!/1`.
"""
defexception [:message, :offset, :data]
end
defmodule JSON do
@moduledoc ~S"""
JSON encoding and decoding.
Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html/rfc8259) and
[ECMA 404](https://ecma-international.org/publications-and-standards/standards/ecma-404/)
standards.
## Encoding
Elixir primitive types are encoded to JSON as follows:
| **Elixir** | **JSON** |
|----------------------------|----------|
| `integer() \| float()` | Number |
| `true \| false ` | Boolean |
| `nil` | Null |
| `binary()` | String |
| `atom()` | String |
| `list()` | Array |
| `%{String.Chars.t() => _}` | Object |
You may also implement the `JSON.Encoder` protocol for custom data structures.
Some built-in data-structures already derive the `JSON.Encoder` protocol:
| **Elixir** | **JSON** |
|------------------------|-----------------|
| `Date.t()` | ISO 8601 string |
| `Time.t()` | ISO 8601 string |
| `DateTime.t()` | ISO 8601 string |
| `NaiveDateTime.t()` | ISO 8601 string |
| `Duration.t()` | ISO 8601 string |
## Decoding
Elixir types are decoded from JSON as follows:
| **JSON** | **Elixir** |
|----------|------------------------|
| Number | `integer() \| float()` |
| Boolean | `true \| false` |
| Null | `nil` |
| String | `binary()` |
| Object | `%{binary() => _}` |
"""
@moduledoc since: "1.18.0"
@type encoder :: (term(), encoder() -> iodata())
@type decode_error_reason ::
{:unexpected_end, non_neg_integer()}
| {:invalid_byte, non_neg_integer(), byte()}
| {:unexpected_sequence, non_neg_integer(), binary()}
@typedoc """
Decoders for customizing JSON decoding behavior.
"""
@type decoders :: [
array_start: (term() -> term()),
array_push: (term(), term() -> term()),
array_finish: (term(), term() -> {term(), term()}),
object_start: (term() -> term()),
object_push: (term(), term(), term() -> term()),
object_finish: (term(), term() -> {term(), term()}),
float: (String.t() -> term()),
integer: (String.t() -> term()),
string: (String.t() -> term()),
null: term()
]
@doc ~S"""
Decodes the given JSON.
Returns `{:ok, decoded}` or `{:error, reason}`.
## Examples
iex> JSON.decode("[null,123,\"string\",{\"key\":\"value\"}]")
{:ok, [nil, 123, "string", %{"key" => "value"}]}
## Error reasons
The error tuple will have one of the following reasons.
* `{:unexpected_end, offset}` if `binary` contains incomplete JSON value
* `{:invalid_byte, offset, byte}` if `binary` contains unexpected byte or invalid UTF-8 byte
* `{:unexpected_sequence, offset, bytes}` if `binary` contains invalid UTF-8 escape
"""
@spec decode(binary()) :: {:ok, term()} | {:error, decode_error_reason()}
def decode(binary) when is_binary(binary) do
with {decoded, :ok, rest} <- decode(binary, :ok, []) do
if rest == "" do
{:ok, decoded}
else
{:error, {:invalid_byte, byte_size(binary) - byte_size(rest), :binary.at(rest, 0)}}
end
end
end
@doc ~S"""
Decodes the given JSON with the given decoders.
Returns `{decoded, acc, rest}` or `{:error, reason}`.
See `decode/1` for the error reasons.
## Decoders
All decoders are optional. If not provided, they will fall back to
implementations used by the `decode/1` function:
* for `array_start`: `fn _ -> [] end`
* for `array_push`: `fn elem, acc -> [elem | acc] end`
* for `array_finish`: `fn acc, old_acc -> {Enum.reverse(acc), old_acc} end`
* for `object_start`: `fn _ -> [] end`
* for `object_push`: `fn key, value, acc -> [{key, value} | acc] end`
* for `object_finish`: `fn acc, old_acc -> {Map.new(acc), old_acc} end`
* for `float`: `&String.to_float/1`
* for `integer`: `&String.to_integer/1`
* for `string`: `&Function.identity/1`
* for `null`: the atom `nil`
For streaming decoding, see Erlang's [`:json`](`:json`) module.
"""
@spec decode(binary(), term(), decoders()) ::
{term(), term(), binary()} | {:error, decode_error_reason()}
def decode(binary, acc, decoders) when is_binary(binary) and is_list(decoders) do
decoders = Keyword.put_new(decoders, :null, nil)
try do
:elixir_json.decode(binary, acc, Map.new(decoders))
catch
:error, :unexpected_end ->
{:error, {:unexpected_end, byte_size(binary)}}
:error, {:invalid_byte, byte} ->
{:error, {:invalid_byte, offset(__STACKTRACE__), byte}}
:error, {:unexpected_sequence, bytes} ->
{:error, {:unexpected_sequence, offset(__STACKTRACE__), bytes}}
end
end
defp offset(stacktrace) do
with [{_, _, _, opts} | _] <- stacktrace,
%{cause: %{position: position}} <- opts[:error_info] do
position
else
_ -> 0
end
end
@doc ~S"""
Decodes the given JSON but raises an exception in case of errors.
Returns the decoded content. See `decode/1` for possible errors.
## Examples
iex> JSON.decode!("[null,123,\"string\",{\"key\":\"value\"}]")
[nil, 123, "string", %{"key" => "value"}]
"""
@spec decode!(binary()) :: term()
def decode!(binary) when is_binary(binary) do
case decode(binary) do
{:ok, decoded} ->
decoded
{:error, {:unexpected_end, offset}} ->
raise JSON.DecodeError,
message: "unexpected end of JSON binary at position (byte offset) #{offset}",
data: binary,
offset: offset
{:error, {:invalid_byte, offset, byte}} ->
raise JSON.DecodeError,
message: "invalid byte #{byte} at position (byte offset) #{offset}",
data: binary,
offset: offset
{:error, {:unexpected_sequence, offset, bytes}} ->
raise JSON.DecodeError,
message: "unexpected sequence #{inspect(bytes)} at position (byte offset) #{offset}",
data: binary,
offset: offset
end
end
@doc ~S"""
Encodes the given term to JSON as a binary.
The second argument is a function that is recursively
invoked to encode a term.
> #### IO and performance {: .tip}
>
> If you need to encode data to be sent over the network
> or written to the filesystem, consider using the more
> efficient `encode_to_iodata!/2`.
## Examples
iex> JSON.encode!([123, "string", %{key: "value"}])
"[123,\"string\",{\"key\":\"value\"}]"
"""
@spec encode!(term(), encoder()) :: binary()
def encode!(term, encoder \\ &protocol_encode/2) do
IO.iodata_to_binary(encoder.(term, encoder))
end
@doc ~S"""
Encodes the given term to JSON as an iodata.
This is the most efficient format if the JSON is going to be
used for IO purposes.
The second argument is a function that is recursively
invoked to encode a term.
## Examples
iex> data = JSON.encode_to_iodata!([123, "string", %{key: "value"}])
iex> IO.iodata_to_binary(data)
"[123,\"string\",{\"key\":\"value\"}]"
"""
@spec encode_to_iodata!(term(), encoder()) :: iodata()
def encode_to_iodata!(term, encoder \\ &protocol_encode/2) do
encoder.(term, encoder)
end
@doc """
This is the default encode implementation passed to `encode!/1`.
This function is most typically passed as second argument to
`encode!/2` and `encode_to_iodata!/2`. The default implementation
is an optimized dispatch to the `JSON.Encoder` protocol.
"""
@spec protocol_encode(term(), encoder()) :: iodata()
def protocol_encode(value, encoder) when is_atom(value) do
case value do
nil -> "null"
true -> "true"
false -> "false"
_ -> encoder.(Atom.to_string(value), encoder)
end
end
def protocol_encode(value, _encoder) when is_binary(value),
do: :elixir_json.encode_binary(value)
def protocol_encode(value, _encoder) when is_integer(value),
do: :elixir_json.encode_integer(value)
def protocol_encode(value, _encoder) when is_float(value),
do: :elixir_json.encode_float(value)
def protocol_encode(value, encoder) when is_list(value),
do: :elixir_json.encode_list(value, encoder)
def protocol_encode(%{} = value, encoder) when not is_map_key(value, :__struct__),
do: JSON.Encoder.Map.encode(value, encoder)
def protocol_encode(value, encoder),
do: JSON.Encoder.encode(value, encoder)
end