-module(anthropic@validation).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/anthropic/validation.gleam").
-export([field_to_string/1, validation_error/2, validation_error_with_value/3, error_to_string/1, errors_to_string/1, get_model_limits/1, validate_messages/1, validate_model/1, validate_max_tokens/2, validate_temperature/1, validate_top_p/1, validate_top_k/1, validate_system/1, validate_stop_sequences/1, validate_tools/1, validate_request/1, is_valid/1, validate_or_error/1, validate_content_blocks/1]).
-export_type([validation_field/0, validation_error/0, model_limits/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Request validation for Anthropic API\n"
"\n"
" This module provides comprehensive validation for API requests before\n"
" they are sent, catching common errors early and providing helpful messages.\n"
"\n"
" ## Validation Checks\n"
"\n"
" - Non-empty messages list\n"
" - Valid model name format\n"
" - max_tokens within bounds (1 to 4096 for most models)\n"
" - Valid temperature range (0.0 to 1.0)\n"
" - Valid top_p range (0.0 to 1.0)\n"
" - Valid top_k (positive integer)\n"
" - Tool definition validation\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let request = create_request(\"claude-sonnet-4-20250514\", messages, 1024)\n"
"\n"
" case validate_request(request) {\n"
" Ok(_) -> api.create_message(client, request)\n"
" Error(errors) -> {\n"
" io.println(\"Validation failed:\")\n"
" list.each(errors, fn(e) { io.println(\" - \" <> e.message) })\n"
" }\n"
" }\n"
" ```\n"
).
-type validation_field() :: messages_field |
model_field |
max_tokens_field |
temperature_field |
top_p_field |
top_k_field |
system_field |
stop_sequences_field |
tools_field |
tool_choice_field.
-type validation_error() :: {validation_error,
validation_field(),
binary(),
gleam@option:option(binary())}.
-type model_limits() :: {model_limits, integer(), integer(), integer()}.
-file("src/anthropic/validation.gleam", 59).
?DOC(" Convert a ValidationField to a string\n").
-spec field_to_string(validation_field()) -> binary().
field_to_string(Field) ->
case Field of
messages_field ->
<<"messages"/utf8>>;
model_field ->
<<"model"/utf8>>;
max_tokens_field ->
<<"max_tokens"/utf8>>;
temperature_field ->
<<"temperature"/utf8>>;
top_p_field ->
<<"top_p"/utf8>>;
top_k_field ->
<<"top_k"/utf8>>;
system_field ->
<<"system"/utf8>>;
stop_sequences_field ->
<<"stop_sequences"/utf8>>;
tools_field ->
<<"tools"/utf8>>;
tool_choice_field ->
<<"tool_choice"/utf8>>
end.
-file("src/anthropic/validation.gleam", 87).
?DOC(" Create a validation error\n").
-spec validation_error(validation_field(), binary()) -> validation_error().
validation_error(Field, Message) ->
{validation_error, Field, Message, none}.
-file("src/anthropic/validation.gleam", 95).
?DOC(" Create a validation error with value context\n").
-spec validation_error_with_value(validation_field(), binary(), binary()) -> validation_error().
validation_error_with_value(Field, Message, Value) ->
{validation_error, Field, Message, {some, Value}}.
-file("src/anthropic/validation.gleam", 104).
?DOC(" Convert a ValidationError to a string\n").
-spec error_to_string(validation_error()) -> binary().
error_to_string(Error) ->
Base = <<<<(field_to_string(erlang:element(2, Error)))/binary, ": "/utf8>>/binary,
(erlang:element(3, Error))/binary>>,
case erlang:element(4, Error) of
{some, V} ->
<<<<<>/binary, V/binary>>/binary,
")"/utf8>>;
none ->
Base
end.
-file("src/anthropic/validation.gleam", 114).
?DOC(" Convert a list of validation errors to a combined string\n").
-spec errors_to_string(list(validation_error())) -> binary().
errors_to_string(Errors) ->
_pipe = Errors,
_pipe@1 = gleam@list:map(_pipe, fun error_to_string/1),
gleam@string:join(_pipe@1, <<"; "/utf8>>).
-file("src/anthropic/validation.gleam", 137).
?DOC(" Get limits for a model\n").
-spec get_model_limits(binary()) -> model_limits().
get_model_limits(Model) ->
case gleam_stdlib:contains_string(Model, <<"claude-3-opus"/utf8>>) of
true ->
{model_limits, 1, 4096, 200000};
false ->
case gleam_stdlib:contains_string(Model, <<"claude-3-sonnet"/utf8>>)
orelse gleam_stdlib:contains_string(Model, <<"claude-sonnet"/utf8>>) of
true ->
{model_limits, 1, 8192, 200000};
false ->
case gleam_stdlib:contains_string(
Model,
<<"claude-3-haiku"/utf8>>
)
orelse gleam_stdlib:contains_string(
Model,
<<"claude-3-5-haiku"/utf8>>
) of
true ->
{model_limits, 1, 8192, 200000};
false ->
{model_limits, 1, 8192, 200000}
end
end
end.
-file("src/anthropic/validation.gleam", 237).
-spec check_alternation(
list(anthropic@types@message:message()),
anthropic@types@message:role()
) -> {ok, nil} | {error, validation_error()}.
check_alternation(Messages, Expected_role) ->
case Messages of
[] ->
{ok, nil};
[Msg | Rest] ->
case erlang:element(2, Msg) =:= Expected_role of
true ->
Next_role = case Expected_role of
user ->
assistant;
assistant ->
user
end,
check_alternation(Rest, Next_role);
false ->
{error,
validation_error(
messages_field,
<<"messages must alternate between user and assistant roles"/utf8>>
)}
end
end.
-file("src/anthropic/validation.gleam", 220).
?DOC(" Validate that messages alternate correctly\n").
-spec validate_message_alternation(list(anthropic@types@message:message())) -> {ok,
nil} |
{error, validation_error()}.
validate_message_alternation(Messages) ->
case Messages of
[] ->
{ok, nil};
[First | _] ->
case erlang:element(2, First) of
user ->
check_alternation(Messages, user);
assistant ->
{error,
validation_error(
messages_field,
<<"conversation must start with a user message"/utf8>>
)}
end
end.
-file("src/anthropic/validation.gleam", 177).
?DOC(" Validate the messages list\n").
-spec validate_messages(list(anthropic@types@message:message())) -> {ok, nil} |
{error, list(validation_error())}.
validate_messages(Messages) ->
Errors = [],
Errors@1 = case gleam@list:is_empty(Messages) of
true ->
[validation_error(
messages_field,
<<"messages list cannot be empty"/utf8>>
) |
Errors];
false ->
Errors
end,
Errors@2 = gleam@list:index_fold(
Messages,
Errors@1,
fun(Acc, Msg, Idx) ->
case gleam@list:is_empty(erlang:element(3, Msg)) of
true ->
[validation_error_with_value(
messages_field,
<<"message content cannot be empty"/utf8>>,
<<<<"message["/utf8,
(erlang:integer_to_binary(Idx))/binary>>/binary,
"]"/utf8>>
) |
Acc];
false ->
Acc
end
end
),
Errors@3 = case validate_message_alternation(Messages) of
{ok, _} ->
Errors@2;
{error, Err} ->
[Err | Errors@2]
end,
case Errors@3 of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end.
-file("src/anthropic/validation.gleam", 308).
-spec is_alphanumeric(binary()) -> boolean().
is_alphanumeric(Char) ->
Lower = <<"abcdefghijklmnopqrstuvwxyz"/utf8>>,
Upper = <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ"/utf8>>,
Digits = <<"0123456789"/utf8>>,
(gleam_stdlib:contains_string(Lower, Char) orelse gleam_stdlib:contains_string(
Upper,
Char
))
orelse gleam_stdlib:contains_string(Digits, Char).
-file("src/anthropic/validation.gleam", 299).
?DOC(" Check if a model name has valid format\n").
-spec is_valid_model_name(binary()) -> boolean().
is_valid_model_name(Name) ->
_pipe = Name,
_pipe@1 = gleam@string:to_graphemes(_pipe),
gleam@list:all(
_pipe@1,
fun(Char) ->
((is_alphanumeric(Char) orelse (Char =:= <<"."/utf8>>)) orelse (Char
=:= <<"-"/utf8>>))
orelse (Char =:= <<"_"/utf8>>)
end
).
-file("src/anthropic/validation.gleam", 262).
?DOC(" Validate the model name\n").
-spec validate_model(binary()) -> {ok, nil} | {error, list(validation_error())}.
validate_model(Model) ->
Trimmed = gleam@string:trim(Model),
Errors = [],
Errors@1 = case gleam@string:is_empty(Trimmed) of
true ->
[validation_error(
model_field,
<<"model name cannot be empty"/utf8>>
) |
Errors];
false ->
Errors
end,
Errors@2 = case gleam@string:is_empty(Trimmed) of
true ->
Errors@1;
false ->
case is_valid_model_name(Trimmed) of
true ->
Errors@1;
false ->
[validation_error_with_value(
model_field,
<<"model name contains invalid characters"/utf8>>,
Trimmed
) |
Errors@1]
end
end,
case Errors@2 of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end.
-file("src/anthropic/validation.gleam", 318).
?DOC(" Validate max_tokens\n").
-spec validate_max_tokens(integer(), binary()) -> {ok, nil} |
{error, list(validation_error())}.
validate_max_tokens(Max_tokens, Model) ->
Limits = get_model_limits(Model),
Errors = [],
Errors@1 = case Max_tokens < erlang:element(2, Limits) of
true ->
[validation_error_with_value(
max_tokens_field,
<<"max_tokens must be at least "/utf8,
(erlang:integer_to_binary(erlang:element(2, Limits)))/binary>>,
erlang:integer_to_binary(Max_tokens)
) |
Errors];
false ->
Errors
end,
Errors@2 = case Max_tokens > erlang:element(3, Limits) of
true ->
[validation_error_with_value(
max_tokens_field,
<<"max_tokens cannot exceed "/utf8,
(erlang:integer_to_binary(erlang:element(3, Limits)))/binary>>,
erlang:integer_to_binary(Max_tokens)
) |
Errors@1];
false ->
Errors@1
end,
case Errors@2 of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end.
-file("src/anthropic/validation.gleam", 356).
?DOC(" Validate temperature\n").
-spec validate_temperature(gleam@option:option(float())) -> {ok, nil} |
{error, list(validation_error())}.
validate_temperature(Temperature) ->
case Temperature of
none ->
{ok, nil};
{some, T} ->
case (T >= +0.0) andalso (T =< 1.0) of
true ->
{ok, nil};
false ->
{error,
[validation_error_with_value(
temperature_field,
<<"temperature must be between 0.0 and 1.0"/utf8>>,
gleam_stdlib:float_to_string(T)
)]}
end
end.
-file("src/anthropic/validation.gleam", 377).
?DOC(" Validate top_p\n").
-spec validate_top_p(gleam@option:option(float())) -> {ok, nil} |
{error, list(validation_error())}.
validate_top_p(Top_p) ->
case Top_p of
none ->
{ok, nil};
{some, P} ->
case (P >= +0.0) andalso (P =< 1.0) of
true ->
{ok, nil};
false ->
{error,
[validation_error_with_value(
top_p_field,
<<"top_p must be between 0.0 and 1.0"/utf8>>,
gleam_stdlib:float_to_string(P)
)]}
end
end.
-file("src/anthropic/validation.gleam", 398).
?DOC(" Validate top_k\n").
-spec validate_top_k(gleam@option:option(integer())) -> {ok, nil} |
{error, list(validation_error())}.
validate_top_k(Top_k) ->
case Top_k of
none ->
{ok, nil};
{some, K} ->
case K > 0 of
true ->
{ok, nil};
false ->
{error,
[validation_error_with_value(
top_k_field,
<<"top_k must be a positive integer"/utf8>>,
erlang:integer_to_binary(K)
)]}
end
end.
-file("src/anthropic/validation.gleam", 417).
?DOC(" Validate system prompt\n").
-spec validate_system(gleam@option:option(binary())) -> {ok, nil} |
{error, list(validation_error())}.
validate_system(System) ->
case System of
none ->
{ok, nil};
{some, S} ->
case gleam@string:is_empty(gleam@string:trim(S)) of
true ->
{error,
[validation_error(
system_field,
<<"system prompt cannot be empty when provided"/utf8>>
)]};
false ->
{ok, nil}
end
end.
-file("src/anthropic/validation.gleam", 437).
?DOC(" Validate stop sequences\n").
-spec validate_stop_sequences(gleam@option:option(list(binary()))) -> {ok, nil} |
{error, list(validation_error())}.
validate_stop_sequences(Sequences) ->
case Sequences of
none ->
{ok, nil};
{some, Seqs} ->
Errors = begin
_pipe = Seqs,
gleam@list:index_fold(
_pipe,
[],
fun(Acc, Seq, Idx) -> case gleam@string:is_empty(Seq) of
true ->
[validation_error_with_value(
stop_sequences_field,
<<"stop sequence cannot be empty"/utf8>>,
<<<<"stop_sequences["/utf8,
(erlang:integer_to_binary(Idx))/binary>>/binary,
"]"/utf8>>
) |
Acc];
false ->
Acc
end end
)
end,
case Errors of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end
end.
-file("src/anthropic/validation.gleam", 524).
?DOC(" Check if a tool name is valid\n").
-spec is_valid_tool_name(binary()) -> boolean().
is_valid_tool_name(Name) ->
Len = string:length(Name),
((Len >= 1) andalso (Len =< 64)) andalso begin
_pipe = Name,
_pipe@1 = gleam@string:to_graphemes(_pipe),
gleam@list:all(
_pipe@1,
fun(Char) ->
(is_alphanumeric(Char) orelse (Char =:= <<"_"/utf8>>)) orelse (Char
=:= <<"-"/utf8>>)
end
)
end.
-file("src/anthropic/validation.gleam", 490).
?DOC(" Validate a single tool definition\n").
-spec validate_single_tool(anthropic@types@tool:tool(), integer()) -> list(validation_error()).
validate_single_tool(T, Index) ->
Prefix = <<<<"tools["/utf8, (erlang:integer_to_binary(Index))/binary>>/binary,
"]"/utf8>>,
Errors = [],
Errors@1 = case gleam@string:is_empty(
gleam@string:trim(erlang:element(2, T))
) of
true ->
[validation_error_with_value(
tools_field,
<<"tool name cannot be empty"/utf8>>,
Prefix
) |
Errors];
false ->
Errors
end,
Errors@2 = case is_valid_tool_name(erlang:element(2, T)) of
true ->
Errors@1;
false ->
[validation_error_with_value(
tools_field,
<<"tool name must match ^[a-zA-Z0-9_-]{1,64}$"/utf8>>,
<<<>/binary,
(erlang:element(2, T))/binary>>
) |
Errors@1]
end,
Errors@2.
-file("src/anthropic/validation.gleam", 468).
?DOC(" Validate tool definitions\n").
-spec validate_tools(gleam@option:option(list(anthropic@types@tool:tool()))) -> {ok,
nil} |
{error, list(validation_error())}.
validate_tools(Tools) ->
case Tools of
none ->
{ok, nil};
{some, Tool_list} ->
Errors = begin
_pipe = Tool_list,
gleam@list:index_fold(
_pipe,
[],
fun(Acc, T, Idx) -> _pipe@1 = validate_single_tool(T, Idx),
lists:append(_pipe@1, Acc) end
)
end,
case Errors of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end
end.
-file("src/anthropic/validation.gleam", 567).
?DOC(" Helper to collect errors from a validation result\n").
-spec collect_errors(
list(validation_error()),
{ok, nil} | {error, list(validation_error())}
) -> list(validation_error()).
collect_errors(Existing, Result) ->
case Result of
{ok, _} ->
Existing;
{error, New_errors} ->
lists:append(New_errors, Existing)
end.
-file("src/anthropic/validation.gleam", 544).
?DOC(
" Validate a complete CreateMessageRequest\n"
"\n"
" Returns Ok(Nil) if the request is valid, or Error with a list of all\n"
" validation errors found.\n"
).
-spec validate_request(anthropic@types@request:create_message_request()) -> {ok,
nil} |
{error, list(validation_error())}.
validate_request(Request) ->
Errors = begin
_pipe = [],
_pipe@1 = collect_errors(
_pipe,
validate_messages(erlang:element(3, Request))
),
_pipe@2 = collect_errors(
_pipe@1,
validate_model(erlang:element(2, Request))
),
_pipe@3 = collect_errors(
_pipe@2,
validate_max_tokens(
erlang:element(4, Request),
erlang:element(2, Request)
)
),
_pipe@4 = collect_errors(
_pipe@3,
validate_temperature(erlang:element(6, Request))
),
_pipe@5 = collect_errors(
_pipe@4,
validate_top_p(erlang:element(7, Request))
),
_pipe@6 = collect_errors(
_pipe@5,
validate_top_k(erlang:element(8, Request))
),
_pipe@7 = collect_errors(
_pipe@6,
validate_system(erlang:element(5, Request))
),
_pipe@8 = collect_errors(
_pipe@7,
validate_stop_sequences(erlang:element(9, Request))
),
collect_errors(_pipe@8, validate_tools(erlang:element(12, Request)))
end,
case Errors of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end.
-file("src/anthropic/validation.gleam", 582).
?DOC(" Quick check if a request is valid (returns Bool)\n").
-spec is_valid(anthropic@types@request:create_message_request()) -> boolean().
is_valid(Request) ->
case validate_request(Request) of
{ok, _} ->
true;
{error, _} ->
false
end.
-file("src/anthropic/validation.gleam", 590).
?DOC(" Validate a request and return an AnthropicError if invalid\n").
-spec validate_or_error(anthropic@types@request:create_message_request()) -> {ok,
anthropic@types@request:create_message_request()} |
{error, anthropic@types@error:anthropic_error()}.
validate_or_error(Request) ->
case validate_request(Request) of
{ok, _} ->
{ok, Request};
{error, Errors} ->
{error,
anthropic@types@error:invalid_request_error(
<<"Request validation failed: "/utf8,
(errors_to_string(Errors))/binary>>
)}
end.
-file("src/anthropic/validation.gleam", 607).
?DOC(" Validate that content blocks are non-empty\n").
-spec validate_content_blocks(list(anthropic@types@message:content_block())) -> {ok,
nil} |
{error, list(validation_error())}.
validate_content_blocks(Blocks) ->
case gleam@list:is_empty(Blocks) of
true ->
{error,
[validation_error(
messages_field,
<<"content blocks cannot be empty"/utf8>>
)]};
false ->
Errors = begin
_pipe = Blocks,
gleam@list:index_fold(
_pipe,
[],
fun(Acc, Block, Idx) -> case Block of
{text_block, Text} ->
case gleam@string:is_empty(
gleam@string:trim(Text)
) of
true ->
[validation_error_with_value(
messages_field,
<<"text content cannot be empty"/utf8>>,
<<<<"content["/utf8,
(erlang:integer_to_binary(
Idx
))/binary>>/binary,
"]"/utf8>>
) |
Acc];
false ->
Acc
end;
_ ->
Acc
end end
)
end,
case Errors of
[] ->
{ok, nil};
Errs ->
{error, lists:reverse(Errs)}
end
end.