-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>>, <<< {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.