Current section

Files

Jump to
codex_sdk lib codex protocol plugin helpers.ex
Raw

lib/codex/protocol/plugin/helpers.ex

defmodule Codex.Protocol.Plugin.Helpers do
@moduledoc false
alias CliSubprocessCore.Schema.Conventions
alias CliSubprocessCore.Schema.Error, as: SchemaError
alias Codex.Schema
@spec required_string() :: Zoi.schema()
def required_string do
Conventions.trimmed_string()
|> Zoi.min(1)
end
@spec optional_string() :: Zoi.schema()
def optional_string, do: Conventions.optional_trimmed_string()
@spec any_map() :: Zoi.schema()
def any_map, do: Conventions.any_map()
@spec optional_map() :: Zoi.schema()
def optional_map, do: Zoi.optional(Zoi.nullish(any_map()))
@spec default_array(Zoi.schema(), list()) :: Zoi.schema()
def default_array(schema, default \\ []) when is_list(default) do
Zoi.default(Zoi.optional(Zoi.nullish(Zoi.array(schema))), default)
end
@spec default_string_list([String.t()]) :: Zoi.schema()
def default_string_list(default \\ []) when is_list(default) do
Zoi.default(Zoi.optional(Zoi.nullish(Zoi.array(required_string()))), default)
end
@spec boolean_flag() :: Zoi.schema()
def boolean_flag do
Zoi.default(
Zoi.optional(
Zoi.nullish(Zoi.any() |> Zoi.transform({__MODULE__, :normalize_boolean_flag, []}))
),
false
)
end
@spec normalize_boolean_flag(term(), keyword()) :: {:ok, boolean()} | {:error, String.t()}
def normalize_boolean_flag(true, _opts), do: {:ok, true}
def normalize_boolean_flag(false, _opts), do: {:ok, false}
def normalize_boolean_flag("true", _opts), do: {:ok, true}
def normalize_boolean_flag("false", _opts), do: {:ok, false}
def normalize_boolean_flag(value, _opts) when is_atom(value),
do: normalize_boolean_flag(Atom.to_string(value), [])
def normalize_boolean_flag(_value, _opts), do: {:error, "expected a boolean"}
@spec raw_true?(term()) :: boolean()
def raw_true?(true), do: true
def raw_true?("true"), do: true
def raw_true?(_), do: false
@spec parse(Zoi.schema(), term(), atom(), map(), (map() -> struct())) ::
{:ok, struct()} | {:error, {atom(), CliSubprocessCore.Schema.error_detail()}}
def parse(schema, value, tag, key_mapping, projector) when is_function(projector, 1) do
case Schema.parse(schema, normalize_input(value, key_mapping), tag) do
{:ok, parsed} ->
try do
{:ok, projector.(parsed)}
rescue
error in [SchemaError] ->
{:error, {tag, error.details}}
end
{:error, {^tag, details}} ->
{:error, {tag, details}}
end
end
@spec parse!(Zoi.schema(), term(), atom(), map(), (map() -> struct())) :: struct()
def parse!(schema, value, tag, key_mapping, projector) when is_function(projector, 1) do
schema
|> Schema.parse!(normalize_input(value, key_mapping), tag)
|> projector.()
rescue
error in [SchemaError] ->
reraise SchemaError, [tag: tag, details: error.details], __STACKTRACE__
end
@spec split_extra(map(), [String.t()]) :: {map(), map()}
def split_extra(parsed, known_fields), do: Schema.split_extra(parsed, known_fields)
@spec parse_nested(term(), module()) :: struct() | nil
def parse_nested(nil, _module), do: nil
def parse_nested(%module{} = value, module), do: value
def parse_nested(value, module) do
module.parse!(value)
end
@spec parse_list([term()] | nil, module()) :: [struct()]
def parse_list(nil, _module), do: []
def parse_list(values, module) when is_list(values),
do: Enum.map(values, &parse_nested(&1, module))
@spec encode_nested(term(), module()) :: map() | nil
def encode_nested(nil, _module), do: nil
def encode_nested(%module{} = value, module), do: module.to_map(value)
def encode_nested(value, _module) when is_map(value), do: value
@spec encode_list([term()], module()) :: [map()]
def encode_list(values, module) when is_list(values) do
Enum.map(values, &encode_nested(&1, module))
end
@spec maybe_put(map(), String.t(), term()) :: map()
def maybe_put(map, _key, nil), do: map
def maybe_put(map, key, value), do: Map.put(map, key, value)
@spec normalize_input(term(), map()) :: term()
def normalize_input(nil, _key_mapping), do: %{}
def normalize_input(list, key_mapping) when is_list(list),
do: list |> Enum.into(%{}) |> normalize_input(key_mapping)
def normalize_input(%{} = value, key_mapping) when is_map(key_mapping) do
value
|> Enum.map(fn {key, nested_value} ->
{normalize_key(key, key_mapping), normalize_value(nested_value, key_mapping)}
end)
|> Map.new()
end
def normalize_input(value, _key_mapping), do: value
defp normalize_value(%{} = value, key_mapping), do: normalize_input(value, key_mapping)
defp normalize_value(values, key_mapping) when is_list(values) do
Enum.map(values, &normalize_value(&1, key_mapping))
end
defp normalize_value(value, _key_mapping), do: value
defp normalize_key(key, key_mapping) when is_atom(key) do
key
|> Atom.to_string()
|> normalize_key(key_mapping)
end
defp normalize_key(key, key_mapping) when is_binary(key), do: Map.get(key_mapping, key, key)
end