-module(sextant). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sextant.gleam"). -export([additional_properties/2, map/2, try_map/3, pattern/2, int_min/2, int_max/2, int_exclusive_min/2, int_exclusive_max/2, int_multiple_of/2, float_min/2, float_max/2, float_exclusive_min/2, float_exclusive_max/2, min_items/2, max_items/2, unique_items/1, run_with_options/3, describe/2, title/2, examples/2, default/2, float_multiple_of/2, boolean/0, null/0, success/1, tuple2/2, tuple3/3, tuple4/4, tuple5/5, tuple6/6, optional/1, dict/1, enum/2, one_of/2, any_of/2, field/3, optional_field/3, string/0, error_to_string/1, uuid/0, timestamp/0, uri/0, const_value/3, min_length/2, max_length/2, format/2, integer/0, number/0, array/1, run/2, to_json/1]). -export_type([schema_definition/0, property/0, metadata/0, string_constraints/0, int_constraints/0, float_constraints/0, array_constraints/0, string_format/0, string_constraint_violation/0, number_constraint_violation/0, array_constraint_violation/0, constraint_violation/0, validation_error/0, options/0, json_schema/1]). -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( " Sextant - A Gleam library for JSON Schema generation and validation.\n" "\n" " This library provides a `use`-based API for defining JSON schemas that can\n" " both generate JSON Schema 2020-12 documents and validate dynamic data.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import sextant\n" " import gleam/option.{type Option}\n" "\n" " type User {\n" " User(name: String, age: Int, email: Option(String))\n" " }\n" "\n" " fn user_schema() -> sextant.JsonSchema(User) {\n" " use name <- sextant.field(\"name\", sextant.string() |> sextant.min_length(1))\n" " use age <- sextant.field(\"age\", sextant.integer() |> sextant.int_min(0))\n" " use email <- sextant.optional_field(\"email\", sextant.string())\n" " sextant.success(User(name:, age:, email:))\n" " }\n" "\n" " // Generate JSON Schema\n" " let schema_json = sextant.to_json(user_schema())\n" "\n" " // Validate data\n" " let result = sextant.run(dynamic_data, user_schema())\n" " ```\n" ). -type schema_definition() :: {string_schema, string_constraints(), metadata()} | {integer_schema, int_constraints(), metadata()} | {number_schema, float_constraints(), metadata()} | {boolean_schema, metadata()} | {null_schema, metadata()} | {array_schema, schema_definition(), array_constraints(), metadata()} | {object_schema, list(property()), list(binary()), boolean(), metadata()} | {dict_schema, schema_definition(), metadata()} | {nullable_schema, schema_definition(), metadata()} | {one_of_schema, list(schema_definition()), metadata()} | {any_of_schema, list(schema_definition()), metadata()} | {enum_schema, list(binary()), metadata()} | {const_schema, gleam@json:json(), metadata()} | {tuple_schema, list(schema_definition()), metadata()}. -type property() :: {property, binary(), schema_definition()}. -type metadata() :: {metadata, gleam@option:option(binary()), gleam@option:option(binary()), list(gleam@json:json()), gleam@option:option(gleam@json:json())}. -type string_constraints() :: {string_constraints, gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(binary()), gleam@option:option(string_format())}. -type int_constraints() :: {int_constraints, gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(integer())}. -type float_constraints() :: {float_constraints, gleam@option:option(float()), gleam@option:option(float()), gleam@option:option(float()), gleam@option:option(float()), gleam@option:option(float())}. -type array_constraints() :: {array_constraints, gleam@option:option(integer()), gleam@option:option(integer()), boolean()}. -type string_format() :: email | uri | date_time | date | time | uuid | hostname | ipv4 | ipv6. -type string_constraint_violation() :: {string_too_short, integer(), integer()} | {string_too_long, integer(), integer()} | {pattern_mismatch, binary(), binary()} | {invalid_pattern, binary(), binary()} | {invalid_format, binary(), binary()}. -type number_constraint_violation() :: {number_too_small, float(), boolean(), float()} | {number_too_large, float(), boolean(), float()} | {not_multiple_of, float(), float()}. -type array_constraint_violation() :: {array_too_short, integer(), integer()} | {array_too_long, integer(), integer()} | items_not_unique. -type constraint_violation() :: {string_violation, string_constraint_violation()} | {number_violation, number_constraint_violation()} | {array_violation, array_constraint_violation()} | {custom_violation, binary()}. -type validation_error() :: {type_error, binary(), binary(), list(binary())} | {constraint_error, constraint_violation(), list(binary())} | {missing_field, binary(), list(binary())} | {unknown_variant, binary(), list(binary()), list(binary())} | {const_mismatch, binary(), binary(), list(binary())}. -type options() :: {options, boolean()}. -opaque json_schema(HZV) :: {json_schema, schema_definition(), fun((gleam@dynamic:dynamic_(), options()) -> {HZV, list(validation_error())}), HZV}. -file("src/sextant.gleam", 266). -spec string_violation_to_string(string_constraint_violation()) -> binary(). string_violation_to_string(Violation) -> case Violation of {string_too_short, Min, Actual} -> <<<<<<<<"String too short (minimum: "/utf8, (erlang:integer_to_binary(Min))/binary>>/binary, ", got: "/utf8>>/binary, (erlang:integer_to_binary(Actual))/binary>>/binary, ")"/utf8>>; {string_too_long, Max, Actual@1} -> <<<<<<<<"String too long (maximum: "/utf8, (erlang:integer_to_binary(Max))/binary>>/binary, ", got: "/utf8>>/binary, (erlang:integer_to_binary(Actual@1))/binary>>/binary, ")"/utf8>>; {pattern_mismatch, Pattern, Actual@2} -> <<<<<<<<"String '"/utf8, Actual@2/binary>>/binary, "' does not match pattern '"/utf8>>/binary, Pattern/binary>>/binary, "'"/utf8>>; {invalid_pattern, Pattern@1, Error} -> <<<<<<"Invalid regex pattern '"/utf8, Pattern@1/binary>>/binary, "': "/utf8>>/binary, Error/binary>>; {invalid_format, Format_name, Actual@3} -> <<<<<<"String '"/utf8, Actual@3/binary>>/binary, "' is not a valid "/utf8>>/binary, Format_name/binary>> end. -file("src/sextant.gleam", 289). -spec number_violation_to_string(number_constraint_violation()) -> binary(). number_violation_to_string(Violation) -> case Violation of {number_too_small, Minimum, Exclusive, Actual} -> Op = case Exclusive of true -> <<"greater than"/utf8>>; false -> <<"at least"/utf8>> end, <<<<<<<<<<"Number must be "/utf8, Op/binary>>/binary, " "/utf8>>/binary, (gleam_stdlib:float_to_string(Minimum))/binary>>/binary, ", got: "/utf8>>/binary, (gleam_stdlib:float_to_string(Actual))/binary>>; {number_too_large, Maximum, Exclusive@1, Actual@1} -> Op@1 = case Exclusive@1 of true -> <<"less than"/utf8>>; false -> <<"at most"/utf8>> end, <<<<<<<<<<"Number must be "/utf8, Op@1/binary>>/binary, " "/utf8>>/binary, (gleam_stdlib:float_to_string(Maximum))/binary>>/binary, ", got: "/utf8>>/binary, (gleam_stdlib:float_to_string(Actual@1))/binary>>; {not_multiple_of, Multiple, Actual@2} -> <<<<<<"Number "/utf8, (gleam_stdlib:float_to_string(Actual@2))/binary>>/binary, " is not a multiple of "/utf8>>/binary, (gleam_stdlib:float_to_string(Multiple))/binary>> end. -file("src/sextant.gleam", 323). -spec array_violation_to_string(array_constraint_violation()) -> binary(). array_violation_to_string(Violation) -> case Violation of {array_too_short, Min, Actual} -> <<<<<<<<"Array too short (minimum: "/utf8, (erlang:integer_to_binary(Min))/binary>>/binary, " items, got: "/utf8>>/binary, (erlang:integer_to_binary(Actual))/binary>>/binary, ")"/utf8>>; {array_too_long, Max, Actual@1} -> <<<<<<<<"Array too long (maximum: "/utf8, (erlang:integer_to_binary(Max))/binary>>/binary, " items, got: "/utf8>>/binary, (erlang:integer_to_binary(Actual@1))/binary>>/binary, ")"/utf8>>; items_not_unique -> <<"Array items are not unique"/utf8>> end. -file("src/sextant.gleam", 257). -spec constraint_violation_to_string(constraint_violation()) -> binary(). constraint_violation_to_string(Violation) -> case Violation of {string_violation, V} -> string_violation_to_string(V); {number_violation, V@1} -> number_violation_to_string(V@1); {custom_violation, Msg} -> Msg; {array_violation, V@2} -> array_violation_to_string(V@2) end. -file("src/sextant.gleam", 369). ?DOC( " Check if errors contain a type error (vs only constraint errors).\n" " Type errors mean we can't run further constraints on the value.\n" ). -spec has_type_error(list(validation_error())) -> boolean(). has_type_error(Errors) -> gleam@list:any(Errors, fun(E) -> case E of {type_error, _, _, _} -> true; {missing_field, _, _} -> true; _ -> false end end). -file("src/sextant.gleam", 689). ?DOC( " Allow additional properties in the generated JSON Schema.\n" "\n" " By default, object schemas set `additionalProperties: false` to enforce\n" " strict validation. Use this combinator to allow extra properties that\n" " aren't defined in the schema.\n" "\n" " Note: This only affects the generated JSON Schema document. Sextant's\n" " decoder always ignores additional properties during validation.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " fn user_schema() -> sextant.JsonSchema(User) {\n" " use name <- sextant.field(\"name\", sextant.string())\n" " sextant.success(User(name:))\n" " }\n" " |> sextant.additional_properties(True)\n" " ```\n" ). -spec additional_properties(json_schema(IAI), boolean()) -> json_schema(IAI). additional_properties(Schema, Allow) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {object_schema, Properties, Required, _, Meta} -> {object_schema, Properties, Required, Allow, Meta}; _ -> Def end, {json_schema, New_def, Decoder, Zero}. -file("src/sextant.gleam", 1142). ?DOC( " Transform the decoded value using a function.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let uppercase_string = sextant.string() |> sextant.map(string.uppercase)\n" " ```\n" ). -spec map(json_schema(IDP), fun((IDP) -> IDR)) -> json_schema(IDR). map(Schema, Transform) -> {json_schema, erlang:element(2, Schema), fun(Data, Opts) -> {Value, Errors} = (erlang:element(3, Schema))(Data, Opts), {Transform(Value), Errors} end, Transform(erlang:element(4, Schema))}. -file("src/sextant.gleam", 1180). ?DOC( " Transform the decoded value using a fallible function.\n" "\n" " If the transform returns `Error`, it's converted to a `ConstraintError`\n" " using the provided error message. The `default` value is used as the\n" " zero value for schema extraction and as the fallback when the transform fails.\n" "\n" " Use this for validations that can't be expressed with built-in constraints,\n" " such as parsing strings into custom types or cross-field validation.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " pub type Slug { Slug(String) }\n" "\n" " fn parse_slug(s: String) -> Result(Slug, String) {\n" " let is_valid = string.length(s) > 0\n" " && string.lowercase(s) == s\n" " && !string.contains(s, \" \")\n" " case is_valid {\n" " True -> Ok(Slug(s))\n" " False -> Error(\"must be lowercase with no spaces\")\n" " }\n" " }\n" "\n" " let slug_schema = sextant.string()\n" " |> sextant.try_map(parse_slug, default: Slug(\"default\"))\n" " ```\n" ). -spec try_map( json_schema(IDT), fun((IDT) -> {ok, IDV} | {error, binary()}), IDV ) -> json_schema(IDV). try_map(Schema, Transform, Default) -> {json_schema, erlang:element(2, Schema), fun(Data, Opts) -> {Value, Errors} = (erlang:element(3, Schema))(Data, Opts), case has_type_error(Errors) of true -> {Default, Errors}; false -> case Transform(Value) of {ok, Transformed} -> {Transformed, Errors}; {error, Msg} -> {Default, lists:append( Errors, [{constraint_error, {custom_violation, Msg}, []}] )} end end end, Default}. -file("src/sextant.gleam", 1353). ?DOC( " Set a regex pattern the string must match.\n" "\n" " If the pattern is not a valid regex, validation will return an\n" " `InvalidPattern` constraint error.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let slug_schema = sextant.string() |> sextant.pattern(\"^[a-z0-9-]+$\")\n" " ```\n" ). -spec pattern(json_schema(binary()), binary()) -> json_schema(binary()). pattern(Schema, Pattern_string) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {string_schema, Constraints, Meta} -> {string_schema, {string_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), {some, Pattern_string}, erlang:element(5, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case gleam@regexp:from_string(Pattern_string) of {error, {compile_error, Err, _}} -> {Value, lists:append( Errors, [{constraint_error, {string_violation, {invalid_pattern, Pattern_string, Err}}, []}] )}; {ok, Regex} -> case gleam@regexp:check(Regex, Value) of true -> {Value, Errors}; false -> {Value, lists:append( Errors, [{constraint_error, {string_violation, {pattern_mismatch, Pattern_string, Value}}, []}] )} end end end end, Zero}. -file("src/sextant.gleam", 1470). ?DOC( " Set inclusive minimum value for integers.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let age_schema = sextant.integer() |> sextant.int_min(0)\n" " ```\n" ). -spec int_min(json_schema(integer()), integer()) -> json_schema(integer()). int_min(Schema, Min) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {integer_schema, Constraints, Meta} -> {integer_schema, {int_constraints, {some, Min}, erlang:element(3, Constraints), erlang:element(4, Constraints), erlang:element(5, Constraints), erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value < Min of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_small, erlang:float(Min), false, erlang:float(Value)}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1513). ?DOC( " Set inclusive maximum value for integers.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let age_schema = sextant.integer() |> sextant.int_max(150)\n" " ```\n" ). -spec int_max(json_schema(integer()), integer()) -> json_schema(integer()). int_max(Schema, Max) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {integer_schema, Constraints, Meta} -> {integer_schema, {int_constraints, erlang:element(2, Constraints), {some, Max}, erlang:element(4, Constraints), erlang:element(5, Constraints), erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value > Max of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_large, erlang:float(Max), false, erlang:float(Value)}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1556). ?DOC( " Set exclusive minimum value for integers.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let positive_schema = sextant.integer() |> sextant.int_exclusive_min(0)\n" " ```\n" ). -spec int_exclusive_min(json_schema(integer()), integer()) -> json_schema(integer()). int_exclusive_min(Schema, Min) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {integer_schema, Constraints, Meta} -> {integer_schema, {int_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), {some, Min}, erlang:element(5, Constraints), erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value =< Min of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_small, erlang:float(Min), true, erlang:float(Value)}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1602). ?DOC( " Set exclusive maximum value for integers.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let under_100 = sextant.integer() |> sextant.int_exclusive_max(100)\n" " ```\n" ). -spec int_exclusive_max(json_schema(integer()), integer()) -> json_schema(integer()). int_exclusive_max(Schema, Max) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {integer_schema, Constraints, Meta} -> {integer_schema, {int_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), erlang:element(4, Constraints), {some, Max}, erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value >= Max of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_large, erlang:float(Max), true, erlang:float(Value)}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1651). ?DOC( " Set that the integer must be a multiple of a given value.\n" "\n" " Note: If `multiple` is 0, all values will be considered valid since\n" " `x % 0 = 0` in Gleam.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let even_schema = sextant.integer() |> sextant.int_multiple_of(2)\n" " ```\n" ). -spec int_multiple_of(json_schema(integer()), integer()) -> json_schema(integer()). int_multiple_of(Schema, Multiple) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {integer_schema, Constraints, Meta} -> {integer_schema, {int_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), erlang:element(4, Constraints), erlang:element(5, Constraints), {some, Multiple}}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case (case Multiple of 0 -> 0; Gleam@denominator -> Value rem Gleam@denominator end) =:= 0 of true -> {Value, Errors}; false -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {not_multiple_of, erlang:float(Multiple), erlang:float(Value)}}, []}] )} end end end, Zero}. -file("src/sextant.gleam", 1703). ?DOC( " Set inclusive minimum value for floats.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let price_schema = sextant.number() |> sextant.float_min(0.0)\n" " ```\n" ). -spec float_min(json_schema(float()), float()) -> json_schema(float()). float_min(Schema, Min) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {number_schema, Constraints, Meta} -> {number_schema, {float_constraints, {some, Min}, erlang:element(3, Constraints), erlang:element(4, Constraints), erlang:element(5, Constraints), erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value < Min of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_small, Min, false, Value}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1746). ?DOC( " Set inclusive maximum value for floats.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let percentage_schema = sextant.number() |> sextant.float_max(100.0)\n" " ```\n" ). -spec float_max(json_schema(float()), float()) -> json_schema(float()). float_max(Schema, Max) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {number_schema, Constraints, Meta} -> {number_schema, {float_constraints, erlang:element(2, Constraints), {some, Max}, erlang:element(4, Constraints), erlang:element(5, Constraints), erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value > Max of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_large, Max, false, Value}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1789). ?DOC( " Set exclusive minimum value for floats.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let positive_schema = sextant.number() |> sextant.float_exclusive_min(0.0)\n" " ```\n" ). -spec float_exclusive_min(json_schema(float()), float()) -> json_schema(float()). float_exclusive_min(Schema, Min) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {number_schema, Constraints, Meta} -> {number_schema, {float_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), {some, Min}, erlang:element(5, Constraints), erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value =< Min of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_small, Min, true, Value}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1838). ?DOC( " Set exclusive maximum value for floats.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let under_100 = sextant.number() |> sextant.float_exclusive_max(100.0)\n" " ```\n" ). -spec float_exclusive_max(json_schema(float()), float()) -> json_schema(float()). float_exclusive_max(Schema, Max) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {number_schema, Constraints, Meta} -> {number_schema, {float_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), erlang:element(4, Constraints), {some, Max}, erlang:element(6, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> case Value >= Max of true -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {number_too_large, Max, true, Value}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1944). ?DOC( " Set minimum number of items in an array.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let tags_schema = sextant.array(sextant.string()) |> sextant.min_items(1)\n" " ```\n" ). -spec min_items(json_schema(list(IFE)), integer()) -> json_schema(list(IFE)). min_items(Schema, Min) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {array_schema, Items, Constraints, Meta} -> {array_schema, Items, {array_constraints, {some, Min}, erlang:element(3, Constraints), erlang:element(4, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> Len = erlang:length(Value), case Len < Min of true -> {Value, lists:append( Errors, [{constraint_error, {array_violation, {array_too_short, Min, Len}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1989). ?DOC( " Set maximum number of items in an array.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let tags_schema = sextant.array(sextant.string()) |> sextant.max_items(10)\n" " ```\n" ). -spec max_items(json_schema(list(IFJ)), integer()) -> json_schema(list(IFJ)). max_items(Schema, Max) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {array_schema, Items, Constraints, Meta} -> {array_schema, Items, {array_constraints, erlang:element(2, Constraints), {some, Max}, erlang:element(4, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> Len = erlang:length(Value), case Len > Max of true -> {Value, lists:append( Errors, [{constraint_error, {array_violation, {array_too_long, Max, Len}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 2034). ?DOC( " Require all array items to be unique.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let unique_tags = sextant.array(sextant.string()) |> sextant.unique_items()\n" " ```\n" ). -spec unique_items(json_schema(list(IFO))) -> json_schema(list(IFO)). unique_items(Schema) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {array_schema, Items, Constraints, Meta} -> {array_schema, Items, {array_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), true}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> Unique_count = begin _pipe = gleam@list:unique(Value), erlang:length(_pipe) end, Total_count = erlang:length(Value), case Unique_count =:= Total_count of true -> {Value, Errors}; false -> {Value, lists:append( Errors, [{constraint_error, {array_violation, items_not_unique}, []}] )} end end end, Zero}. -file("src/sextant.gleam", 2154). ?DOC( " Validate and decode dynamic data with custom options.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let opts = sextant.Options(validate_formats: True)\n" " case sextant.run_with_options(data, email_schema, opts) {\n" " Ok(email) -> // email format was validated\n" " Error(errors) -> // handle validation errors\n" " }\n" " ```\n" ). -spec run_with_options(gleam@dynamic:dynamic_(), json_schema(IGL), options()) -> {ok, IGL} | {error, list(validation_error())}. run_with_options(Data, Schema, Options) -> {Value, Errors} = (erlang:element(3, Schema))(Data, Options), case Errors of [] -> {ok, Value}; _ -> {error, Errors} end. -file("src/sextant.gleam", 2375). -spec add_examples(list({binary(), gleam@json:json()}), list(gleam@json:json())) -> list({binary(), gleam@json:json()}). add_examples(Fields, Examples) -> case Examples of [] -> Fields; _ -> [{<<"examples"/utf8>>, gleam@json:array(Examples, fun(X) -> X end)} | Fields] end. -file("src/sextant.gleam", 2385). -spec add_optional_int( list({binary(), gleam@json:json()}), binary(), gleam@option:option(integer()) ) -> list({binary(), gleam@json:json()}). add_optional_int(Fields, Name, Value) -> case Value of {some, V} -> [{Name, gleam@json:int(V)} | Fields]; none -> Fields end. -file("src/sextant.gleam", 2325). -spec add_int_constraints( list({binary(), gleam@json:json()}), int_constraints() ) -> list({binary(), gleam@json:json()}). add_int_constraints(Fields, Constraints) -> _pipe = Fields, _pipe@1 = add_optional_int( _pipe, <<"minimum"/utf8>>, erlang:element(2, Constraints) ), _pipe@2 = add_optional_int( _pipe@1, <<"maximum"/utf8>>, erlang:element(3, Constraints) ), _pipe@3 = add_optional_int( _pipe@2, <<"exclusiveMinimum"/utf8>>, erlang:element(4, Constraints) ), _pipe@4 = add_optional_int( _pipe@3, <<"exclusiveMaximum"/utf8>>, erlang:element(5, Constraints) ), add_optional_int( _pipe@4, <<"multipleOf"/utf8>>, erlang:element(6, Constraints) ). -file("src/sextant.gleam", 2349). -spec add_array_constraints( list({binary(), gleam@json:json()}), array_constraints() ) -> list({binary(), gleam@json:json()}). add_array_constraints(Fields, Constraints) -> Fields@1 = begin _pipe = Fields, _pipe@1 = add_optional_int( _pipe, <<"minItems"/utf8>>, erlang:element(2, Constraints) ), add_optional_int( _pipe@1, <<"maxItems"/utf8>>, erlang:element(3, Constraints) ) end, case erlang:element(4, Constraints) of true -> [{<<"uniqueItems"/utf8>>, gleam@json:bool(true)} | Fields@1]; false -> Fields@1 end. -file("src/sextant.gleam", 2396). -spec add_optional_float( list({binary(), gleam@json:json()}), binary(), gleam@option:option(float()) ) -> list({binary(), gleam@json:json()}). add_optional_float(Fields, Name, Value) -> case Value of {some, V} -> [{Name, gleam@json:float(V)} | Fields]; none -> Fields end. -file("src/sextant.gleam", 2337). -spec add_float_constraints( list({binary(), gleam@json:json()}), float_constraints() ) -> list({binary(), gleam@json:json()}). add_float_constraints(Fields, Constraints) -> _pipe = Fields, _pipe@1 = add_optional_float( _pipe, <<"minimum"/utf8>>, erlang:element(2, Constraints) ), _pipe@2 = add_optional_float( _pipe@1, <<"maximum"/utf8>>, erlang:element(3, Constraints) ), _pipe@3 = add_optional_float( _pipe@2, <<"exclusiveMinimum"/utf8>>, erlang:element(4, Constraints) ), _pipe@4 = add_optional_float( _pipe@3, <<"exclusiveMaximum"/utf8>>, erlang:element(5, Constraints) ), add_optional_float( _pipe@4, <<"multipleOf"/utf8>>, erlang:element(6, Constraints) ). -file("src/sextant.gleam", 2407). -spec add_optional_string( list({binary(), gleam@json:json()}), binary(), gleam@option:option(binary()) ) -> list({binary(), gleam@json:json()}). add_optional_string(Fields, Name, Value) -> case Value of {some, V} -> [{Name, gleam@json:string(V)} | Fields]; none -> Fields end. -file("src/sextant.gleam", 2418). -spec add_optional_json( list({binary(), gleam@json:json()}), binary(), gleam@option:option(gleam@json:json()) ) -> list({binary(), gleam@json:json()}). add_optional_json(Fields, Name, Value) -> case Value of {some, V} -> [{Name, V} | Fields]; none -> Fields end. -file("src/sextant.gleam", 2364). -spec add_metadata(list({binary(), gleam@json:json()}), metadata()) -> list({binary(), gleam@json:json()}). add_metadata(Fields, Meta) -> _pipe = Fields, _pipe@1 = add_optional_string( _pipe, <<"title"/utf8>>, erlang:element(2, Meta) ), _pipe@2 = add_optional_string( _pipe@1, <<"description"/utf8>>, erlang:element(3, Meta) ), _pipe@3 = add_optional_json( _pipe@2, <<"default"/utf8>>, erlang:element(5, Meta) ), add_examples(_pipe@3, erlang:element(4, Meta)). -file("src/sextant.gleam", 2681). -spec try_decoders( gleam@dynamic:dynamic_(), options(), list(json_schema(IJS)), json_schema(IJS), binary() ) -> {IJS, list(validation_error())}. try_decoders(Data, Opts, Schemas, Fallback, Type_name) -> case Schemas of [] -> {Value, _} = (erlang:element(3, Fallback))(Data, Opts), {Value, [{type_error, Type_name, gleam_stdlib:classify_dynamic(Data), []}]}; [Sch | Rest] -> {Value@1, Errors} = (erlang:element(3, Sch))(Data, Opts), case Errors of [] -> {Value@1, []}; _ -> try_decoders(Data, Opts, Rest, Fallback, Type_name) end end. -file("src/sextant.gleam", 2703). -spec prepend_path(list(validation_error()), binary()) -> list(validation_error()). prepend_path(Errors, Segment) -> gleam@list:map(Errors, fun(Error) -> case Error of {type_error, Expected, Found, Path} -> {type_error, Expected, Found, [Segment | Path]}; {constraint_error, Violation, Path@1} -> {constraint_error, Violation, [Segment | Path@1]}; {missing_field, Fld, Path@2} -> {missing_field, Fld, [Segment | Path@2]}; {unknown_variant, Value, Expected@1, Path@3} -> {unknown_variant, Value, Expected@1, [Segment | Path@3]}; {const_mismatch, Expected@2, Actual, Path@4} -> {const_mismatch, Expected@2, Actual, [Segment | Path@4]} end end). -file("src/sextant.gleam", 2443). -spec decode_array(gleam@dynamic:dynamic_(), json_schema(IHV), options()) -> {list(IHV), list(validation_error())}. decode_array(Data, Inner, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, Items} -> {Values@1, Errors@1} = gleam@list:index_fold( Items, {[], []}, fun(Acc, Item, Index) -> {Values, Errors} = Acc, {Value, Item_errors} = (erlang:element(3, Inner))( Item, Opts ), Item_errors@1 = prepend_path( Item_errors, erlang:integer_to_binary(Index) ), {[Value | Values], lists:append(Errors, Item_errors@1)} end ), {lists:reverse(Values@1), Errors@1}; {error, _} -> {[], [{type_error, <<"Array"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end. -file("src/sextant.gleam", 2468). -spec decode_tuple2( gleam@dynamic:dynamic_(), json_schema(IHZ), json_schema(IIB), options() ) -> {{IHZ, IIB}, list(validation_error())}. decode_tuple2(Data, First, Second, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, Items} -> case Items of [Item0, Item1] -> {V0, E0} = (erlang:element(3, First))(Item0, Opts), E0@1 = prepend_path(E0, <<"0"/utf8>>), {V1, E1} = (erlang:element(3, Second))(Item1, Opts), E1@1 = prepend_path(E1, <<"1"/utf8>>), {{V0, V1}, lists:append(E0@1, E1@1)}; _ -> {{erlang:element(4, First), erlang:element(4, Second)}, [{type_error, <<"Tuple[2]"/utf8>>, <<<<"Array["/utf8, (erlang:integer_to_binary( erlang:length(Items) ))/binary>>/binary, "]"/utf8>>, []}]} end; {error, _} -> {{erlang:element(4, First), erlang:element(4, Second)}, [{type_error, <<"Tuple[2]"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end. -file("src/sextant.gleam", 2500). -spec decode_tuple3( gleam@dynamic:dynamic_(), json_schema(IIE), json_schema(IIG), json_schema(III), options() ) -> {{IIE, IIG, III}, list(validation_error())}. decode_tuple3(Data, First, Second, Third, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, Items} -> case Items of [Item0, Item1, Item2] -> {V0, E0} = (erlang:element(3, First))(Item0, Opts), E0@1 = prepend_path(E0, <<"0"/utf8>>), {V1, E1} = (erlang:element(3, Second))(Item1, Opts), E1@1 = prepend_path(E1, <<"1"/utf8>>), {V2, E2} = (erlang:element(3, Third))(Item2, Opts), E2@1 = prepend_path(E2, <<"2"/utf8>>), {{V0, V1, V2}, lists:append([E0@1, E1@1, E2@1])}; _ -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third)}, [{type_error, <<"Tuple[3]"/utf8>>, <<<<"Array["/utf8, (erlang:integer_to_binary( erlang:length(Items) ))/binary>>/binary, "]"/utf8>>, []}]} end; {error, _} -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third)}, [{type_error, <<"Tuple[3]"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end. -file("src/sextant.gleam", 2535). -spec decode_tuple4( gleam@dynamic:dynamic_(), json_schema(IIL), json_schema(IIN), json_schema(IIP), json_schema(IIR), options() ) -> {{IIL, IIN, IIP, IIR}, list(validation_error())}. decode_tuple4(Data, First, Second, Third, Fourth, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, Items} -> case Items of [Item0, Item1, Item2, Item3] -> {V0, E0} = (erlang:element(3, First))(Item0, Opts), E0@1 = prepend_path(E0, <<"0"/utf8>>), {V1, E1} = (erlang:element(3, Second))(Item1, Opts), E1@1 = prepend_path(E1, <<"1"/utf8>>), {V2, E2} = (erlang:element(3, Third))(Item2, Opts), E2@1 = prepend_path(E2, <<"2"/utf8>>), {V3, E3} = (erlang:element(3, Fourth))(Item3, Opts), E3@1 = prepend_path(E3, <<"3"/utf8>>), {{V0, V1, V2, V3}, lists:append([E0@1, E1@1, E2@1, E3@1])}; _ -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth)}, [{type_error, <<"Tuple[4]"/utf8>>, <<<<"Array["/utf8, (erlang:integer_to_binary( erlang:length(Items) ))/binary>>/binary, "]"/utf8>>, []}]} end; {error, _} -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth)}, [{type_error, <<"Tuple[4]"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end. -file("src/sextant.gleam", 2573). -spec decode_tuple5( gleam@dynamic:dynamic_(), json_schema(IIU), json_schema(IIW), json_schema(IIY), json_schema(IJA), json_schema(IJC), options() ) -> {{IIU, IIW, IIY, IJA, IJC}, list(validation_error())}. decode_tuple5(Data, First, Second, Third, Fourth, Fifth, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, Items} -> case Items of [Item0, Item1, Item2, Item3, Item4] -> {V0, E0} = (erlang:element(3, First))(Item0, Opts), E0@1 = prepend_path(E0, <<"0"/utf8>>), {V1, E1} = (erlang:element(3, Second))(Item1, Opts), E1@1 = prepend_path(E1, <<"1"/utf8>>), {V2, E2} = (erlang:element(3, Third))(Item2, Opts), E2@1 = prepend_path(E2, <<"2"/utf8>>), {V3, E3} = (erlang:element(3, Fourth))(Item3, Opts), E3@1 = prepend_path(E3, <<"3"/utf8>>), {V4, E4} = (erlang:element(3, Fifth))(Item4, Opts), E4@1 = prepend_path(E4, <<"4"/utf8>>), {{V0, V1, V2, V3, V4}, lists:append([E0@1, E1@1, E2@1, E3@1, E4@1])}; _ -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth), erlang:element(4, Fifth)}, [{type_error, <<"Tuple[5]"/utf8>>, <<<<"Array["/utf8, (erlang:integer_to_binary( erlang:length(Items) ))/binary>>/binary, "]"/utf8>>, []}]} end; {error, _} -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth), erlang:element(4, Fifth)}, [{type_error, <<"Tuple[5]"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end. -file("src/sextant.gleam", 2617). -spec decode_tuple6( gleam@dynamic:dynamic_(), json_schema(IJF), json_schema(IJH), json_schema(IJJ), json_schema(IJL), json_schema(IJN), json_schema(IJP), options() ) -> {{IJF, IJH, IJJ, IJL, IJN, IJP}, list(validation_error())}. decode_tuple6(Data, First, Second, Third, Fourth, Fifth, Sixth, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:list( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, Items} -> case Items of [Item0, Item1, Item2, Item3, Item4, Item5] -> {V0, E0} = (erlang:element(3, First))(Item0, Opts), E0@1 = prepend_path(E0, <<"0"/utf8>>), {V1, E1} = (erlang:element(3, Second))(Item1, Opts), E1@1 = prepend_path(E1, <<"1"/utf8>>), {V2, E2} = (erlang:element(3, Third))(Item2, Opts), E2@1 = prepend_path(E2, <<"2"/utf8>>), {V3, E3} = (erlang:element(3, Fourth))(Item3, Opts), E3@1 = prepend_path(E3, <<"3"/utf8>>), {V4, E4} = (erlang:element(3, Fifth))(Item4, Opts), E4@1 = prepend_path(E4, <<"4"/utf8>>), {V5, E5} = (erlang:element(3, Sixth))(Item5, Opts), E5@1 = prepend_path(E5, <<"5"/utf8>>), {{V0, V1, V2, V3, V4, V5}, lists:append([E0@1, E1@1, E2@1, E3@1, E4@1, E5@1])}; _ -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth), erlang:element(4, Fifth), erlang:element(4, Sixth)}, [{type_error, <<"Tuple[6]"/utf8>>, <<<<"Array["/utf8, (erlang:integer_to_binary( erlang:length(Items) ))/binary>>/binary, "]"/utf8>>, []}]} end; {error, _} -> {{erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth), erlang:element(4, Fifth), erlang:element(4, Sixth)}, [{type_error, <<"Tuple[6]"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end. -file("src/sextant.gleam", 2722). -spec build_object_schema(binary(), json_schema(any()), json_schema(any())) -> schema_definition(). build_object_schema(Name, Field_schema, Next_schema) -> case erlang:element(2, Next_schema) of {object_schema, Properties, Required, Additional, Meta} -> New_properties = [{property, Name, erlang:element(2, Field_schema)} | Properties], New_required = [Name | Required], {object_schema, New_properties, New_required, Additional, Meta}; _ -> erlang:element(2, Next_schema) end. -file("src/sextant.gleam", 2737). -spec build_object_schema_optional( binary(), json_schema(any()), json_schema(any()) ) -> schema_definition(). build_object_schema_optional(Name, Field_schema, Next_schema) -> case erlang:element(2, Next_schema) of {object_schema, Properties, Required, Additional, Meta} -> New_properties = [{property, Name, erlang:element(2, Field_schema)} | Properties], {object_schema, New_properties, Required, Additional, Meta}; _ -> erlang:element(2, Next_schema) end. -file("src/sextant.gleam", 2761). -spec update_metadata(json_schema(IKK), fun((metadata()) -> metadata())) -> json_schema(IKK). update_metadata(Schema, Update) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {string_schema, Constraints, Meta} -> {string_schema, Constraints, Update(Meta)}; {integer_schema, Constraints@1, Meta@1} -> {integer_schema, Constraints@1, Update(Meta@1)}; {number_schema, Constraints@2, Meta@2} -> {number_schema, Constraints@2, Update(Meta@2)}; {boolean_schema, Meta@3} -> {boolean_schema, Update(Meta@3)}; {null_schema, Meta@4} -> {null_schema, Update(Meta@4)}; {array_schema, Items, Constraints@3, Meta@5} -> {array_schema, Items, Constraints@3, Update(Meta@5)}; {object_schema, Properties, Required, Additional, Meta@6} -> {object_schema, Properties, Required, Additional, Update(Meta@6)}; {dict_schema, Value_schema, Meta@7} -> {dict_schema, Value_schema, Update(Meta@7)}; {nullable_schema, Inner, Meta@8} -> {nullable_schema, Inner, Update(Meta@8)}; {one_of_schema, Variants, Meta@9} -> {one_of_schema, Variants, Update(Meta@9)}; {any_of_schema, Variants@1, Meta@10} -> {any_of_schema, Variants@1, Update(Meta@10)}; {enum_schema, Values, Meta@11} -> {enum_schema, Values, Update(Meta@11)}; {const_schema, Value, Meta@12} -> {const_schema, Value, Update(Meta@12)}; {tuple_schema, Items@1, Meta@13} -> {tuple_schema, Items@1, Update(Meta@13)} end, {json_schema, New_def, Decoder, Zero}. -file("src/sextant.gleam", 2081). ?DOC( " Add a description to the schema.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let name_schema = sextant.string() |> sextant.describe(\"The user's name\")\n" " ```\n" ). -spec describe(json_schema(IFT), binary()) -> json_schema(IFT). describe(Schema, Description) -> update_metadata( Schema, fun(M) -> {metadata, erlang:element(2, M), {some, Description}, erlang:element(4, M), erlang:element(5, M)} end ). -file("src/sextant.gleam", 2094). ?DOC( " Add a title to the schema.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let name_schema = sextant.string() |> sextant.title(\"User Name\")\n" " ```\n" ). -spec title(json_schema(IFW), binary()) -> json_schema(IFW). title(Schema, Title_text) -> update_metadata( Schema, fun(M) -> {metadata, {some, Title_text}, erlang:element(3, M), erlang:element(4, M), erlang:element(5, M)} end ). -file("src/sextant.gleam", 2106). ?DOC( " Add examples to the schema.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let name_schema = sextant.string()\n" " |> sextant.examples([json.string(\"Alice\"), json.string(\"Bob\")])\n" " ```\n" ). -spec examples(json_schema(IFZ), list(gleam@json:json())) -> json_schema(IFZ). examples(Schema, Ex) -> update_metadata( Schema, fun(M) -> {metadata, erlang:element(2, M), erlang:element(3, M), Ex, erlang:element(5, M)} end ). -file("src/sextant.gleam", 2118). ?DOC( " Add a default value to the schema.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let count_schema = sextant.integer()\n" " |> sextant.default(json.int(0))\n" " ```\n" ). -spec default(json_schema(IGD), gleam@json:json()) -> json_schema(IGD). default(Schema, Def) -> update_metadata( Schema, fun(M) -> {metadata, erlang:element(2, M), erlang:element(3, M), erlang:element(4, M), {some, Def}} end ). -file("src/sextant.gleam", 2976). ?DOC(" Check if a year is a leap year\n"). -spec is_leap_year(integer()) -> boolean(). is_leap_year(Year) -> (((Year rem 4) =:= 0) andalso ((Year rem 100) /= 0)) orelse ((Year rem 400) =:= 0). -file("src/sextant.gleam", 2959). ?DOC(" Check if a date is valid\n"). -spec is_valid_date(integer(), integer(), integer()) -> boolean(). is_valid_date(Year, Month, Day) -> case Month of 1 -> (Day >= 1) andalso (Day =< 31); 3 -> (Day >= 1) andalso (Day =< 31); 5 -> (Day >= 1) andalso (Day =< 31); 7 -> (Day >= 1) andalso (Day =< 31); 8 -> (Day >= 1) andalso (Day =< 31); 10 -> (Day >= 1) andalso (Day =< 31); 12 -> (Day >= 1) andalso (Day =< 31); 4 -> (Day >= 1) andalso (Day =< 30); 6 -> (Day >= 1) andalso (Day =< 30); 9 -> (Day >= 1) andalso (Day =< 30); 11 -> (Day >= 1) andalso (Day =< 30); 2 -> Is_leap = is_leap_year(Year), Max_day = case Is_leap of true -> 29; false -> 28 end, (Day >= 1) andalso (Day =< Max_day); _ -> false end. -file("src/sextant.gleam", 2945). ?DOC(" Validate date format (YYYY-MM-DD) with actual date validation\n"). -spec validate_date_format(binary()) -> boolean(). validate_date_format(Value) -> Re@1 = case gleam@regexp:from_string( <<"^(\\d{4})-(\\d{2})-(\\d{2})$"/utf8>> ) of {ok, Re} -> Re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"sextant"/utf8>>, function => <<"validate_date_format"/utf8>>, line => 2946, value => _assert_fail, start => 82170, 'end' => 82240, pattern_start => 82181, pattern_end => 82187}) end, case gleam@regexp:scan(Re@1, Value) of [{match, _, [{some, Year_str}, {some, Month_str}, {some, Day_str}]}] -> case {gleam_stdlib:parse_int(Year_str), gleam_stdlib:parse_int(Month_str), gleam_stdlib:parse_int(Day_str)} of {{ok, Year}, {ok, Month}, {ok, Day}} -> is_valid_date(Year, Month, Day); {_, _, _} -> false end; _ -> false end. -file("src/sextant.gleam", 2981). ?DOC(" Validate time format (HH:MM:SS or HH:MM:SS.sss) with actual time validation\n"). -spec validate_time_format(binary()) -> boolean(). validate_time_format(Value) -> Re@1 = case gleam@regexp:from_string( <<"^(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?$"/utf8>> ) of {ok, Re} -> Re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"sextant"/utf8>>, function => <<"validate_time_format"/utf8>>, line => 2982, value => _assert_fail, start => 83220, 'end' => 83304, pattern_start => 83231, pattern_end => 83237}) end, case gleam@regexp:scan(Re@1, Value) of [{match, _, [{some, Hour_str}, {some, Minute_str}, {some, Second_str} | _]}] -> case {gleam_stdlib:parse_int(Hour_str), gleam_stdlib:parse_int(Minute_str), gleam_stdlib:parse_int(Second_str)} of {{ok, Hour}, {ok, Minute}, {ok, Second}} -> (((((Hour >= 0) andalso (Hour =< 23)) andalso (Minute >= 0)) andalso (Minute =< 59)) andalso (Second >= 0)) andalso (Second =< 60); {_, _, _} -> false end; _ -> false end. -file("src/sextant.gleam", 3002). -spec float_modulo(float(), float()) -> float(). float_modulo(A, B) -> A - (erlang:float(erlang:trunc(case B of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> A / Gleam@denominator end)) * B). -file("src/sextant.gleam", 1891). ?DOC( " Set that the float must be a multiple of a given value.\n" "\n" " Note: Due to floating-point precision, a small tolerance (1e-7) is used\n" " when checking the remainder. If `multiple` is 0.0, all values will be\n" " considered valid since `x %.. 0.0 = 0.0` in Gleam.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let quarter_schema = sextant.number() |> sextant.float_multiple_of(0.25)\n" " ```\n" ). -spec float_multiple_of(json_schema(float()), float()) -> json_schema(float()). float_multiple_of(Schema, Multiple) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {number_schema, Constraints, Meta} -> {number_schema, {float_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), erlang:element(4, Constraints), erlang:element(5, Constraints), {some, Multiple}}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> Remainder = float_modulo(Value, Multiple), case (Remainder =:= +0.0) orelse (gleam@float:absolute_value( Remainder ) < 0.0000001) of true -> {Value, Errors}; false -> {Value, lists:append( Errors, [{constraint_error, {number_violation, {not_multiple_of, Multiple, Value}}, []}] )} end end end, Zero}. -file("src/sextant.gleam", 3021). ?DOC(" Check if dynamic data is null/nil/undefined\n"). -spec is_null(gleam@dynamic:dynamic_()) -> boolean(). is_null(Data) -> case gleam@dynamic@decode:run( Data, gleam@dynamic@decode:optional( {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ) of {ok, none} -> true; _ -> false end. -file("src/sextant.gleam", 3029). ?DOC(" Convert StringFormat to JSON Schema format string\n"). -spec string_format_to_string(string_format()) -> binary(). string_format_to_string(Fmt) -> case Fmt of email -> <<"email"/utf8>>; uri -> <<"uri"/utf8>>; date_time -> <<"date-time"/utf8>>; date -> <<"date"/utf8>>; time -> <<"time"/utf8>>; uuid -> <<"uuid"/utf8>>; hostname -> <<"hostname"/utf8>>; ipv4 -> <<"ipv4"/utf8>>; ipv6 -> <<"ipv6"/utf8>> end. -file("src/sextant.gleam", 2429). -spec add_optional_format( list({binary(), gleam@json:json()}), gleam@option:option(string_format()) ) -> list({binary(), gleam@json:json()}). add_optional_format(Fields, Fmt) -> case Fmt of {some, F} -> [{<<"format"/utf8>>, gleam@json:string(string_format_to_string(F))} | Fields]; none -> Fields end. -file("src/sextant.gleam", 2314). -spec add_string_constraints( list({binary(), gleam@json:json()}), string_constraints() ) -> list({binary(), gleam@json:json()}). add_string_constraints(Fields, Constraints) -> _pipe = Fields, _pipe@1 = add_optional_int( _pipe, <<"minLength"/utf8>>, erlang:element(2, Constraints) ), _pipe@2 = add_optional_int( _pipe@1, <<"maxLength"/utf8>>, erlang:element(3, Constraints) ), _pipe@3 = add_optional_string( _pipe@2, <<"pattern"/utf8>>, erlang:element(4, Constraints) ), add_optional_format(_pipe@3, erlang:element(5, Constraints)). -file("src/sextant.gleam", 475). ?DOC( " Create a schema for JSON booleans.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let active_schema = sextant.boolean()\n" " ```\n" ). -spec boolean() -> json_schema(boolean()). boolean() -> {json_schema, {boolean_schema, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_bool/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, B} -> {B, []}; {error, _} -> {false, [{type_error, <<"Bool"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, false}. -file("src/sextant.gleam", 497). ?DOC( " Create a schema for JSON null.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let null_schema = sextant.null()\n" " ```\n" ). -spec null() -> json_schema(nil). null() -> {json_schema, {null_schema, {metadata, none, none, [], none}}, fun(Data, _) -> case is_null(Data) of true -> {nil, []}; false -> {nil, [{type_error, <<"Null"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, nil}. -file("src/sextant.gleam", 658). ?DOC( " Finalise a schema with a successfully constructed value.\n" "\n" " This is the terminal function in a use-chain for object schemas.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " use name <- sextant.field(\"name\", sextant.string())\n" " sextant.success(User(name:))\n" " ```\n" ). -spec success(IAG) -> json_schema(IAG). success(Value) -> {json_schema, {object_schema, [], [], false, {metadata, none, none, [], none}}, fun(_, _) -> {Value, []} end, Value}. -file("src/sextant.gleam", 841). ?DOC( " Create a schema for a fixed-length array with 2 elements of different types.\n" "\n" " This generates a JSON Schema with `prefixItems` and exact length constraints.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // A point as [x, y] coordinates\n" " let point_schema = sextant.tuple2(sextant.number(), sextant.number())\n" "\n" " // A key-value pair as [string, int]\n" " let pair_schema = sextant.tuple2(sextant.string(), sextant.integer())\n" " ```\n" ). -spec tuple2(json_schema(IBA), json_schema(IBC)) -> json_schema({IBA, IBC}). tuple2(First, Second) -> {json_schema, {tuple_schema, [erlang:element(2, First), erlang:element(2, Second)], {metadata, none, none, [], none}}, fun(Data, Opts) -> decode_tuple2(Data, First, Second, Opts) end, {erlang:element(4, First), erlang:element(4, Second)}}. -file("src/sextant.gleam", 866). ?DOC( " Create a schema for a fixed-length array with 3 elements of different types.\n" "\n" " This generates a JSON Schema with `prefixItems` and exact length constraints.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // RGB colour as [r, g, b]\n" " let rgb_schema = sextant.tuple3(\n" " sextant.integer() |> sextant.int_min(0) |> sextant.int_max(255),\n" " sextant.integer() |> sextant.int_min(0) |> sextant.int_max(255),\n" " sextant.integer() |> sextant.int_min(0) |> sextant.int_max(255),\n" " )\n" " ```\n" ). -spec tuple3(json_schema(IBF), json_schema(IBH), json_schema(IBJ)) -> json_schema({IBF, IBH, IBJ}). tuple3(First, Second, Third) -> {json_schema, {tuple_schema, [erlang:element(2, First), erlang:element(2, Second), erlang:element(2, Third)], {metadata, none, none, [], none}}, fun(Data, Opts) -> decode_tuple3(Data, First, Second, Third, Opts) end, {erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third)}}. -file("src/sextant.gleam", 896). ?DOC( " Create a schema for a fixed-length array with 4 elements of different types.\n" "\n" " This generates a JSON Schema with `prefixItems` and exact length constraints.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // RGBA colour as [r, g, b, a]\n" " let rgba_schema = sextant.tuple4(\n" " sextant.integer(),\n" " sextant.integer(),\n" " sextant.integer(),\n" " sextant.number(),\n" " )\n" " ```\n" ). -spec tuple4( json_schema(IBM), json_schema(IBO), json_schema(IBQ), json_schema(IBS) ) -> json_schema({IBM, IBO, IBQ, IBS}). tuple4(First, Second, Third, Fourth) -> {json_schema, {tuple_schema, [erlang:element(2, First), erlang:element(2, Second), erlang:element(2, Third), erlang:element(2, Fourth)], {metadata, none, none, [], none}}, fun(Data, Opts) -> decode_tuple4(Data, First, Second, Third, Fourth, Opts) end, {erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth)}}. -file("src/sextant.gleam", 917). ?DOC( " Create a schema for a fixed-length array with 5 elements of different types.\n" "\n" " This generates a JSON Schema with `prefixItems` and exact length constraints.\n" ). -spec tuple5( json_schema(IBV), json_schema(IBX), json_schema(IBZ), json_schema(ICB), json_schema(ICD) ) -> json_schema({IBV, IBX, IBZ, ICB, ICD}). tuple5(First, Second, Third, Fourth, Fifth) -> {json_schema, {tuple_schema, [erlang:element(2, First), erlang:element(2, Second), erlang:element(2, Third), erlang:element(2, Fourth), erlang:element(2, Fifth)], {metadata, none, none, [], none}}, fun(Data, Opts) -> decode_tuple5(Data, First, Second, Third, Fourth, Fifth, Opts) end, {erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth), erlang:element(4, Fifth)}}. -file("src/sextant.gleam", 939). ?DOC( " Create a schema for a fixed-length array with 6 elements of different types.\n" "\n" " This generates a JSON Schema with `prefixItems` and exact length constraints.\n" ). -spec tuple6( json_schema(ICG), json_schema(ICI), json_schema(ICK), json_schema(ICM), json_schema(ICO), json_schema(ICQ) ) -> json_schema({ICG, ICI, ICK, ICM, ICO, ICQ}). tuple6(First, Second, Third, Fourth, Fifth, Sixth) -> {json_schema, {tuple_schema, [erlang:element(2, First), erlang:element(2, Second), erlang:element(2, Third), erlang:element(2, Fourth), erlang:element(2, Fifth), erlang:element(2, Sixth)], {metadata, none, none, [], none}}, fun(Data, Opts) -> decode_tuple6( Data, First, Second, Third, Fourth, Fifth, Sixth, Opts ) end, {erlang:element(4, First), erlang:element(4, Second), erlang:element(4, Third), erlang:element(4, Fourth), erlang:element(4, Fifth), erlang:element(4, Sixth)}}. -file("src/sextant.gleam", 982). ?DOC( " Create a schema that allows null values.\n" "\n" " Returns `Some(value)` if the value is present and valid, `None` if null.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let optional_name = sextant.optional(sextant.string())\n" " ```\n" ). -spec optional(json_schema(ICT)) -> json_schema(gleam@option:option(ICT)). optional(Inner) -> {json_schema, {nullable_schema, erlang:element(2, Inner), {metadata, none, none, [], none}}, fun(Data, Opts) -> case is_null(Data) of true -> {none, []}; false -> {Value, Errors} = (erlang:element(3, Inner))(Data, Opts), {{some, Value}, Errors} end end, none}. -file("src/sextant.gleam", 1006). ?DOC( " Create a schema for JSON objects with arbitrary string keys.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let scores_schema = sextant.dict(sextant.integer())\n" " // Validates: {\"alice\": 100, \"bob\": 85}\n" " ```\n" ). -spec dict(json_schema(ICX)) -> json_schema(gleam@dict:dict(binary(), ICX)). dict(Value_schema) -> {json_schema, {dict_schema, erlang:element(2, Value_schema), {metadata, none, none, [], none}}, fun(Data, Opts) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:dict( {decoder, fun gleam@dynamic@decode:decode_string/1}, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, D} -> gleam@dict:fold( D, {maps:new(), []}, fun(Acc, Key, Value) -> {Result_dict, Errors} = Acc, {Decoded_value, Value_errors} = (erlang:element( 3, Value_schema ))(Value, Opts), Value_errors@1 = prepend_path(Value_errors, Key), {gleam@dict:insert(Result_dict, Key, Decoded_value), lists:append(Errors, Value_errors@1)} end ); {error, _} -> {maps:new(), [{type_error, <<"Object"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, maps:new()}. -file("src/sextant.gleam", 1055). ?DOC( " Create a schema for a string enum that maps to Gleam values.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " type Role {\n" " Admin\n" " Member\n" " Guest\n" " }\n" "\n" " let role_schema = sextant.enum(#(\"admin\", Admin), [\n" " #(\"member\", Member),\n" " #(\"guest\", Guest),\n" " ])\n" " ```\n" ). -spec enum({binary(), IDC}, list({binary(), IDC})) -> json_schema(IDC). enum(First, Rest) -> Variants = [First | Rest], Enum_values = gleam@list:map(Variants, fun(V) -> erlang:element(1, V) end), {_, Zero_val} = First, {json_schema, {enum_schema, Enum_values, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_string/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, Str} -> case gleam@list:find( Variants, fun(V@1) -> erlang:element(1, V@1) =:= Str end ) of {ok, {_, Value}} -> {Value, []}; {error, _} -> {Zero_val, [{unknown_variant, Str, Enum_values, []}]} end; {error, _} -> {Zero_val, [{type_error, <<"String"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, Zero_val}. -file("src/sextant.gleam", 1095). ?DOC( " Create a schema where exactly one of the variants must match.\n" "\n" " Note: At runtime, `one_of` and `any_of` have identical validation behaviour -\n" " the first matching schema is used. The distinction only affects the generated\n" " JSON Schema output (`oneOf` vs `anyOf`).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let string_or_int = sextant.one_of(\n" " sextant.string() |> sextant.map(StringValue),\n" " [sextant.integer() |> sextant.map(IntValue)],\n" " )\n" " ```\n" ). -spec one_of(json_schema(IDF), list(json_schema(IDF))) -> json_schema(IDF). one_of(First, Rest) -> All_schemas = [First | Rest], Schema_defs = gleam@list:map( All_schemas, fun(S) -> erlang:element(2, S) end ), {json_schema, {one_of_schema, Schema_defs, {metadata, none, none, [], none}}, fun(Data, Opts) -> try_decoders(Data, Opts, All_schemas, First, <<"OneOf"/utf8>>) end, erlang:element(4, First)}. -file("src/sextant.gleam", 1122). ?DOC( " Create a schema where at least one of the variants must match.\n" "\n" " Note: At runtime, `one_of` and `any_of` have identical validation behaviour -\n" " the first matching schema is used. The distinction only affects the generated\n" " JSON Schema output (`oneOf` vs `anyOf`).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let flexible_schema = sextant.any_of(\n" " sextant.string() |> sextant.map(process_string),\n" " [sextant.integer() |> sextant.map(process_int)],\n" " )\n" " ```\n" ). -spec any_of(json_schema(IDK), list(json_schema(IDK))) -> json_schema(IDK). any_of(First, Rest) -> All_schemas = [First | Rest], Schema_defs = gleam@list:map( All_schemas, fun(S) -> erlang:element(2, S) end ), {json_schema, {any_of_schema, Schema_defs, {metadata, none, none, [], none}}, fun(Data, Opts) -> try_decoders(Data, Opts, All_schemas, First, <<"AnyOf"/utf8>>) end, erlang:element(4, First)}. -file("src/sextant.gleam", 2751). -spec get_field(gleam@dynamic:dynamic_(), binary()) -> {ok, gleam@option:option(gleam@dynamic:dynamic_())} | {error, nil}. get_field(Data, Name) -> case begin _pipe = gleam@dynamic@decode:run( Data, gleam@dynamic@decode:dict( {decoder, fun gleam@dynamic@decode:decode_string/1}, {decoder, fun gleam@dynamic@decode:decode_dynamic/1} ) ), gleam@result:replace_error(_pipe, nil) end of {ok, D} -> {ok, begin _pipe@1 = gleam_stdlib:map_get(D, Name), gleam@option:from_result(_pipe@1) end}; {error, _} -> {error, nil} end. -file("src/sextant.gleam", 714). ?DOC( " Extract a required field from an object.\n" "\n" " The field must be present in the JSON object, otherwise a `MissingField`\n" " error is returned.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " use name <- sextant.field(\"name\", sextant.string())\n" " use age <- sextant.field(\"age\", sextant.integer())\n" " sextant.success(User(name:, age:))\n" " ```\n" ). -spec field(binary(), json_schema(IAL), fun((IAL) -> json_schema(IAN))) -> json_schema(IAN). field(Name, Field_schema, Next) -> Next_schema = Next(erlang:element(4, Field_schema)), {json_schema, build_object_schema(Name, Field_schema, Next_schema), fun(Data, Opts) -> Field_result = get_field(Data, Name), case Field_result of {ok, {some, Field_data}} -> {Value, Field_errors} = (erlang:element(3, Field_schema))( Field_data, Opts ), Field_errors@1 = prepend_path(Field_errors, Name), Next_schema@1 = Next(Value), {Final_value, Next_errors} = (erlang:element( 3, Next_schema@1 ))(Data, Opts), {Final_value, lists:append(Field_errors@1, Next_errors)}; {ok, none} -> Next_schema@2 = Next(erlang:element(4, Field_schema)), {Final_value@1, Next_errors@1} = (erlang:element( 3, Next_schema@2 ))(Data, Opts), {Final_value@1, [{missing_field, Name, []} | Next_errors@1]}; {error, _} -> Next_schema@3 = Next(erlang:element(4, Field_schema)), {Final_value@2, _} = (erlang:element(3, Next_schema@3))( Data, Opts ), {Final_value@2, [{type_error, <<"Object"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, erlang:element(4, Next_schema)}. -file("src/sextant.gleam", 763). ?DOC( " Extract an optional field from an object.\n" "\n" " Returns `Some(value)` if the field is present, `None` if missing.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " use name <- sextant.field(\"name\", sextant.string())\n" " use email <- sextant.optional_field(\"email\", sextant.string())\n" " sextant.success(User(name:, email:))\n" " ```\n" ). -spec optional_field( binary(), json_schema(IAQ), fun((gleam@option:option(IAQ)) -> json_schema(IAT)) ) -> json_schema(IAT). optional_field(Name, Field_schema, Next) -> Next_schema = Next(none), {json_schema, build_object_schema_optional(Name, Field_schema, Next_schema), fun(Data, Opts) -> Field_result = get_field(Data, Name), case Field_result of {ok, {some, Field_data}} -> case is_null(Field_data) of true -> Next_schema@1 = Next(none), (erlang:element(3, Next_schema@1))(Data, Opts); false -> {Value, Field_errors} = (erlang:element( 3, Field_schema ))(Field_data, Opts), Field_errors@1 = prepend_path(Field_errors, Name), Next_schema@2 = Next({some, Value}), {Final_value, Next_errors} = (erlang:element( 3, Next_schema@2 ))(Data, Opts), {Final_value, lists:append(Field_errors@1, Next_errors)} end; {ok, none} -> Next_schema@3 = Next(none), (erlang:element(3, Next_schema@3))(Data, Opts); {error, _} -> Next_schema@4 = Next(none), {Final_value@1, _} = (erlang:element(3, Next_schema@4))( Data, Opts ), {Final_value@1, [{type_error, <<"Object"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, erlang:element(4, Next_schema)}. -file("src/sextant.gleam", 404). ?DOC( " Create a schema for JSON strings.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let name_schema = sextant.string()\n" " ```\n" ). -spec string() -> json_schema(binary()). string() -> {json_schema, {string_schema, {string_constraints, none, none, none, none}, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_string/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, S} -> {S, []}; {error, _} -> {<<""/utf8>>, [{type_error, <<"String"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, <<""/utf8>>}. -file("src/sextant.gleam", 250). -spec format_path(list(binary())) -> binary(). format_path(Path) -> case Path of [] -> <<""/utf8>>; _ -> <<<<" at '"/utf8, (gleam@string:join(Path, <<"."/utf8>>))/binary>>/binary, "'"/utf8>> end. -file("src/sextant.gleam", 227). ?DOC(" Convert a validation error to a human-readable string.\n"). -spec error_to_string(validation_error()) -> binary(). error_to_string(Error) -> case Error of {type_error, Expected, Found, Path} -> <<<<<<<<"Expected "/utf8, Expected/binary>>/binary, ", got "/utf8>>/binary, Found/binary>>/binary, (format_path(Path))/binary>>; {constraint_error, Violation, Path@1} -> <<(constraint_violation_to_string(Violation))/binary, (format_path(Path@1))/binary>>; {missing_field, Field, Path@2} -> <<<<<<"Missing required field '"/utf8, Field/binary>>/binary, "'"/utf8>>/binary, (format_path(Path@2))/binary>>; {unknown_variant, Value, Expected@1, Path@3} -> <<<<<<<<"Unknown variant '"/utf8, Value/binary>>/binary, "', expected one of: "/utf8>>/binary, (gleam@string:join(Expected@1, <<", "/utf8>>))/binary>>/binary, (format_path(Path@3))/binary>>; {const_mismatch, Expected@2, Actual, Path@4} -> <<<<<<<<"Expected const value "/utf8, Expected@2/binary>>/binary, ", got "/utf8>>/binary, Actual/binary>>/binary, (format_path(Path@4))/binary>> end. -file("src/sextant.gleam", 528). ?DOC( " Create a schema for UUIDs that decodes to `youid/uuid.Uuid`.\n" "\n" " This validates and parses the string as a UUID, returning the proper\n" " `Uuid` type from the `youid` library. Use this instead of\n" " `string() |> format(Uuid)` when you want a typed UUID value.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " use id <- sextant.field(\"id\", sextant.uuid())\n" " sextant.success(User(id:, ...))\n" " ```\n" ). -spec uuid() -> json_schema(youid@uuid:uuid()). uuid() -> Zero_uuid = youid@uuid:v4(), {json_schema, {string_schema, {string_constraints, erlang:element(2, {string_constraints, none, none, none, none}), erlang:element(3, {string_constraints, none, none, none, none}), erlang:element(4, {string_constraints, none, none, none, none}), {some, uuid}}, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_string/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, S} -> case youid@uuid:from_string(S) of {ok, Id} -> {Id, []}; {error, _} -> {Zero_uuid, [{constraint_error, {string_violation, {invalid_format, <<"uuid"/utf8>>, S}}, []}]} end; {error, _} -> {Zero_uuid, [{type_error, <<"String"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, Zero_uuid}. -file("src/sextant.gleam", 568). ?DOC( " Create a schema for RFC 3339 timestamps that decodes to `gleam/time/timestamp.Timestamp`.\n" "\n" " This validates and parses the string as an RFC 3339 datetime, returning\n" " the proper `Timestamp` type from `gleam_time`. Use this instead of\n" " `string() |> format(DateTime)` when you want a typed timestamp value.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " use created_at <- sextant.field(\"created_at\", sextant.timestamp())\n" " sextant.success(Event(created_at:, ...))\n" " ```\n" ). -spec timestamp() -> json_schema(gleam@time@timestamp:timestamp()). timestamp() -> {json_schema, {string_schema, {string_constraints, erlang:element(2, {string_constraints, none, none, none, none}), erlang:element(3, {string_constraints, none, none, none, none}), erlang:element(4, {string_constraints, none, none, none, none}), {some, date_time}}, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_string/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, S} -> case gleam@time@timestamp:parse_rfc3339(S) of {ok, Ts} -> {Ts, []}; {error, _} -> {{timestamp, 0, 0}, [{constraint_error, {string_violation, {invalid_format, <<"date-time"/utf8>>, S}}, []}]} end; {error, _} -> {{timestamp, 0, 0}, [{type_error, <<"String"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, {timestamp, 0, 0}}. -file("src/sextant.gleam", 607). ?DOC( " Create a schema for URIs that decodes to `gleam/uri.Uri`.\n" "\n" " This validates and parses the string as a URI, returning the proper\n" " `Uri` type from `gleam_stdlib`. Use this instead of\n" " `string() |> format(Uri)` when you want a typed URI value.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " use website <- sextant.optional_field(\"website\", sextant.uri())\n" " sextant.success(Profile(website:, ...))\n" " ```\n" ). -spec uri() -> json_schema(gleam@uri:uri()). uri() -> {json_schema, {string_schema, {string_constraints, erlang:element(2, {string_constraints, none, none, none, none}), erlang:element(3, {string_constraints, none, none, none, none}), erlang:element(4, {string_constraints, none, none, none, none}), {some, uri}}, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_string/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, S} -> case gleam_stdlib:uri_parse(S) of {ok, U} -> case erlang:element(2, U) of {some, _} -> {U, []}; none -> {{uri, none, none, none, none, <<""/utf8>>, none, none}, [{constraint_error, {string_violation, {invalid_format, <<"uri"/utf8>>, S}}, []}]} end; {error, _} -> {{uri, none, none, none, none, <<""/utf8>>, none, none}, [{constraint_error, {string_violation, {invalid_format, <<"uri"/utf8>>, S}}, []}]} end; {error, _} -> {{uri, none, none, none, none, <<""/utf8>>, none, none}, [{type_error, <<"String"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, {uri, none, none, none, none, <<""/utf8>>, none, none}}. -file("src/sextant.gleam", 1219). ?DOC( " Constrain a schema to accept only a specific constant value.\n" "\n" " The `to_json` function converts the value to its JSON representation\n" " for schema generation. For primitives, use the corresponding `json` function.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let version_schema = sextant.string() |> sextant.const_value(\"v1\", json.string)\n" " let answer_schema = sextant.integer() |> sextant.const_value(42, json.int)\n" " let enabled_schema = sextant.boolean() |> sextant.const_value(True, json.bool)\n" " ```\n" ). -spec const_value(json_schema(IDZ), IDZ, fun((IDZ) -> gleam@json:json())) -> json_schema(IDZ). const_value(Schema, Value, To_json) -> {json_schema, _, Decoder, _} = Schema, {json_schema, {const_schema, To_json(Value), {metadata, none, none, [], none}}, fun(Data, Opts) -> {Decoded, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Decoded, Errors}; false -> case Decoded =:= Value of true -> {Decoded, Errors}; false -> {Value, lists:append( Errors, [{const_mismatch, gleam@string:inspect(Value), gleam@string:inspect(Decoded), []}] )} end end end, Value}. -file("src/sextant.gleam", 1262). ?DOC( " Set minimum string length.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let name_schema = sextant.string() |> sextant.min_length(1)\n" " ```\n" ). -spec min_length(json_schema(binary()), integer()) -> json_schema(binary()). min_length(Schema, Min) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {string_schema, Constraints, Meta} -> {string_schema, {string_constraints, {some, Min}, erlang:element(3, Constraints), erlang:element(4, Constraints), erlang:element(5, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> Len = string:length(Value), case Len < Min of true -> {Value, lists:append( Errors, [{constraint_error, {string_violation, {string_too_short, Min, Len}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 1306). ?DOC( " Set maximum string length.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let name_schema = sextant.string() |> sextant.max_length(100)\n" " ```\n" ). -spec max_length(json_schema(binary()), integer()) -> json_schema(binary()). max_length(Schema, Max) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {string_schema, Constraints, Meta} -> {string_schema, {string_constraints, erlang:element(2, Constraints), {some, Max}, erlang:element(4, Constraints), erlang:element(5, Constraints)}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case has_type_error(Errors) of true -> {Value, Errors}; false -> Len = string:length(Value), case Len > Max of true -> {Value, lists:append( Errors, [{constraint_error, {string_violation, {string_too_long, Max, Len}}, []}] )}; false -> {Value, Errors} end end end, Zero}. -file("src/sextant.gleam", 2861). -spec is_valid_ipv6_segment(binary()) -> boolean(). is_valid_ipv6_segment(Segment) -> Len = string:length(Segment), case (Len >= 1) andalso (Len =< 4) of false -> false; true -> _pipe = Segment, _pipe@1 = string:lowercase(_pipe), _pipe@2 = gleam@string:to_graphemes(_pipe@1), gleam@list:all(_pipe@2, fun(C) -> case C of <<"0"/utf8>> -> true; <<"1"/utf8>> -> true; <<"2"/utf8>> -> true; <<"3"/utf8>> -> true; <<"4"/utf8>> -> true; <<"5"/utf8>> -> true; <<"6"/utf8>> -> true; <<"7"/utf8>> -> true; <<"8"/utf8>> -> true; <<"9"/utf8>> -> true; <<"a"/utf8>> -> true; <<"b"/utf8>> -> true; <<"c"/utf8>> -> true; <<"d"/utf8>> -> true; <<"e"/utf8>> -> true; <<"f"/utf8>> -> true; _ -> false end end) end. -file("src/sextant.gleam", 2880). -spec validate_ipv4_format(binary()) -> boolean(). validate_ipv4_format(Value) -> Parts = gleam@string:split(Value, <<"."/utf8>>), case erlang:length(Parts) =:= 4 of false -> false; true -> gleam@list:all(Parts, fun(P) -> case gleam_stdlib:parse_int(P) of {ok, N} -> (N >= 0) andalso (N =< 255); {error, _} -> false end end) end. -file("src/sextant.gleam", 2898). -spec count_substring_loop(binary(), binary(), integer()) -> integer(). count_substring_loop(Haystack, Needle, Count) -> case gleam@string:split_once(Haystack, Needle) of {error, _} -> Count; {ok, {_, Rest}} -> count_substring_loop(Rest, Needle, Count + 1) end. -file("src/sextant.gleam", 2894). -spec count_substring(binary(), binary()) -> integer(). count_substring(Haystack, Needle) -> count_substring_loop(Haystack, Needle, 0). -file("src/sextant.gleam", 2802). -spec validate_ipv6_pure(binary()) -> boolean(). validate_ipv6_pure(Value) -> Parts = gleam@string:split(Value, <<":"/utf8>>), Part_count = erlang:length(Parts), Has_compression = gleam_stdlib:contains_string(Value, <<"::"/utf8>>), case Has_compression of true -> case count_substring(Value, <<"::"/utf8>>) > 1 of true -> false; false -> Non_empty = gleam@list:filter( Parts, fun(P) -> P /= <<""/utf8>> end ), (erlang:length(Non_empty) =< 7) andalso gleam@list:all( Non_empty, fun is_valid_ipv6_segment/1 ) end; false -> (Part_count =:= 8) andalso gleam@list:all( Parts, fun is_valid_ipv6_segment/1 ) end. -file("src/sextant.gleam", 2827). -spec validate_ipv6_mixed(binary()) -> boolean(). validate_ipv6_mixed(Value) -> Parts = gleam@string:split(Value, <<":"/utf8>>), Part_count = erlang:length(Parts), case Part_count >= 2 of false -> false; true -> Ipv4_part@1 = case gleam@list:last(Parts) of {ok, Ipv4_part} -> Ipv4_part; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"sextant"/utf8>>, function => <<"validate_ipv6_mixed"/utf8>>, line => 2833, value => _assert_fail, start => 79149, 'end' => 79192, pattern_start => 79160, pattern_end => 79173}) end, Ipv6_parts = gleam@list:take(Parts, Part_count - 1), case validate_ipv4_format(Ipv4_part@1) of false -> false; true -> Has_compression = gleam_stdlib:contains_string( Value, <<"::"/utf8>> ), Non_empty_ipv6 = gleam@list:filter( Ipv6_parts, fun(P) -> P /= <<""/utf8>> end ), case Has_compression of true -> case count_substring(Value, <<"::"/utf8>>) > 1 of true -> false; false -> (erlang:length(Non_empty_ipv6) =< 5) andalso gleam@list:all( Non_empty_ipv6, fun is_valid_ipv6_segment/1 ) end; false -> (erlang:length(Ipv6_parts) =:= 6) andalso gleam@list:all( Ipv6_parts, fun is_valid_ipv6_segment/1 ) end end end. -file("src/sextant.gleam", 2789). ?DOC( " Validate IPv6 address format\n" " Handles full, compressed (::), and mixed IPv4 formats\n" ). -spec validate_ipv6(binary()) -> boolean(). validate_ipv6(Value) -> case Value of <<""/utf8>> -> false; _ -> case gleam_stdlib:contains_string(Value, <<"."/utf8>>) of true -> validate_ipv6_mixed(Value); false -> validate_ipv6_pure(Value) end end. -file("src/sextant.gleam", 3008). ?DOC( " Validate URI format - requires valid URI structure with a scheme.\n" " Used by both uri() schema and format(Uri) validator for consistency.\n" ). -spec validate_uri(binary()) -> boolean(). validate_uri(Value) -> case gleam_stdlib:uri_parse(Value) of {ok, Parsed} -> case erlang:element(2, Parsed) of {some, _} -> true; none -> false end; {error, _} -> false end. -file("src/sextant.gleam", 2905). -spec validate_format(binary(), string_format()) -> boolean(). validate_format(Value, Fmt) -> case Fmt of email -> Re@1 = case gleam@regexp:from_string( <<"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"/utf8>> ) of {ok, Re} -> Re; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"sextant"/utf8>>, function => <<"validate_format"/utf8>>, line => 2908, value => _assert_fail, start => 81126, 'end' => 81198, pattern_start => 81137, pattern_end => 81143}) end, gleam@regexp:check(Re@1, Value); uri -> validate_uri(Value); date_time -> case gleam@time@timestamp:parse_rfc3339(Value) of {ok, _} -> true; {error, _} -> false end; date -> validate_date_format(Value); time -> validate_time_format(Value); uuid -> case youid@uuid:from_string(Value) of {ok, _} -> true; {error, _} -> false end; hostname -> Re@3 = case gleam@regexp:from_string( <<"^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$"/utf8>> ) of {ok, Re@2} -> Re@2; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"sextant"/utf8>>, function => <<"validate_format"/utf8>>, line => 2927, value => _assert_fail@1, start => 81607, 'end' => 81762, pattern_start => 81618, pattern_end => 81624}) end, gleam@regexp:check(Re@3, Value); ipv4 -> Re@5 = case gleam@regexp:from_string( <<"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"/utf8>> ) of {ok, Re@4} -> Re@4; _assert_fail@2 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"sextant"/utf8>>, function => <<"validate_format"/utf8>>, line => 2934, value => _assert_fail@2, start => 81819, 'end' => 81976, pattern_start => 81830, pattern_end => 81836}) end, gleam@regexp:check(Re@5, Value); ipv6 -> validate_ipv6(Value) end. -file("src/sextant.gleam", 1419). ?DOC( " Set a string format constraint.\n" "\n" " Format validation only runs when `Options.validate_formats` is `True`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let email_schema = sextant.string() |> sextant.format(sextant.Email)\n" " ```\n" ). -spec format(json_schema(binary()), string_format()) -> json_schema(binary()). format(Schema, Fmt) -> {json_schema, Def, Decoder, Zero} = Schema, New_def = case Def of {string_schema, Constraints, Meta} -> {string_schema, {string_constraints, erlang:element(2, Constraints), erlang:element(3, Constraints), erlang:element(4, Constraints), {some, Fmt}}, Meta}; _ -> Def end, {json_schema, New_def, fun(Data, Opts) -> {Value, Errors} = Decoder(Data, Opts), case {has_type_error(Errors), erlang:element(2, Opts)} of {true, _} -> {Value, Errors}; {false, true} -> case validate_format(Value, Fmt) of true -> {Value, Errors}; false -> {Value, lists:append( Errors, [{constraint_error, {string_violation, {invalid_format, string_format_to_string(Fmt), Value}}, []}] )} end; {false, false} -> {Value, Errors} end end, Zero}. -file("src/sextant.gleam", 426). ?DOC( " Create a schema for JSON integers.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let age_schema = sextant.integer()\n" " ```\n" ). -spec integer() -> json_schema(integer()). integer() -> {json_schema, {integer_schema, {int_constraints, none, none, none, none, none}, {metadata, none, none, [], none}}, fun(Data, _) -> case begin _pipe = gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_int/1} ), gleam@result:replace_error(_pipe, nil) end of {ok, I} -> {I, []}; {error, _} -> {0, [{type_error, <<"Int"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end, 0}. -file("src/sextant.gleam", 448). ?DOC( " Create a schema for JSON numbers (floats).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let price_schema = sextant.number()\n" " ```\n" ). -spec number() -> json_schema(float()). number() -> {json_schema, {number_schema, {float_constraints, none, none, none, none, none}, {metadata, none, none, [], none}}, fun(Data, _) -> case gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_float/1} ) of {ok, F} -> {F, []}; {error, _} -> case gleam@dynamic@decode:run( Data, {decoder, fun gleam@dynamic@decode:decode_int/1} ) of {ok, I} -> {erlang:float(I), []}; {error, _} -> {+0.0, [{type_error, <<"Float"/utf8>>, gleam_stdlib:classify_dynamic(Data), []}]} end end end, +0.0}. -file("src/sextant.gleam", 820). ?DOC( " Create a schema for JSON arrays.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let tags_schema = sextant.array(of: sextant.string())\n" " ```\n" ). -spec array(json_schema(IAW)) -> json_schema(list(IAW)). array(Inner) -> {json_schema, {array_schema, erlang:element(2, Inner), {array_constraints, none, none, false}, {metadata, none, none, [], none}}, fun(Data, Opts) -> decode_array(Data, Inner, Opts) end, []}. -file("src/sextant.gleam", 2136). ?DOC( " Validate and decode dynamic data against a schema.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case sextant.run(data, user_schema()) {\n" " Ok(user) -> // use the decoded user\n" " Error(errors) -> // handle validation errors\n" " }\n" " ```\n" ). -spec run(gleam@dynamic:dynamic_(), json_schema(IGG)) -> {ok, IGG} | {error, list(validation_error())}. run(Data, Schema) -> run_with_options(Data, Schema, {options, false}). -file("src/sextant.gleam", 2185). -spec schema_definition_to_json(schema_definition()) -> gleam@json:json(). schema_definition_to_json(Def) -> gleam@json:object(schema_definition_to_fields(Def)). -file("src/sextant.gleam", 2189). -spec schema_definition_to_fields(schema_definition()) -> list({binary(), gleam@json:json()}). schema_definition_to_fields(Def) -> case Def of {string_schema, Constraints, Meta} -> Base = [{<<"type"/utf8>>, gleam@json:string(<<"string"/utf8>>)}], With_constraints = add_string_constraints(Base, Constraints), add_metadata(With_constraints, Meta); {integer_schema, Constraints@1, Meta@1} -> Base@1 = [{<<"type"/utf8>>, gleam@json:string(<<"integer"/utf8>>)}], With_constraints@1 = add_int_constraints(Base@1, Constraints@1), add_metadata(With_constraints@1, Meta@1); {number_schema, Constraints@2, Meta@2} -> Base@2 = [{<<"type"/utf8>>, gleam@json:string(<<"number"/utf8>>)}], With_constraints@2 = add_float_constraints(Base@2, Constraints@2), add_metadata(With_constraints@2, Meta@2); {boolean_schema, Meta@3} -> Base@3 = [{<<"type"/utf8>>, gleam@json:string(<<"boolean"/utf8>>)}], add_metadata(Base@3, Meta@3); {null_schema, Meta@4} -> Base@4 = [{<<"type"/utf8>>, gleam@json:string(<<"null"/utf8>>)}], add_metadata(Base@4, Meta@4); {array_schema, Items, Constraints@3, Meta@5} -> Base@5 = [{<<"type"/utf8>>, gleam@json:string(<<"array"/utf8>>)}, {<<"items"/utf8>>, schema_definition_to_json(Items)}], With_constraints@3 = add_array_constraints(Base@5, Constraints@3), add_metadata(With_constraints@3, Meta@5); {object_schema, Properties, Required, Additional_properties, Meta@6} -> Props = begin _pipe = Properties, _pipe@1 = gleam@list:map( _pipe, fun(P) -> {erlang:element(2, P), schema_definition_to_json(erlang:element(3, P))} end ), gleam@json:object(_pipe@1) end, Base@6 = case Additional_properties of true -> [{<<"type"/utf8>>, gleam@json:string(<<"object"/utf8>>)}, {<<"properties"/utf8>>, Props}]; false -> [{<<"type"/utf8>>, gleam@json:string(<<"object"/utf8>>)}, {<<"properties"/utf8>>, Props}, {<<"additionalProperties"/utf8>>, gleam@json:bool(false)}] end, With_required = case Required of [] -> Base@6; _ -> [{<<"required"/utf8>>, gleam@json:array(Required, fun gleam@json:string/1)} | Base@6] end, add_metadata(With_required, Meta@6); {dict_schema, Values, Meta@7} -> Base@7 = [{<<"type"/utf8>>, gleam@json:string(<<"object"/utf8>>)}, {<<"additionalProperties"/utf8>>, schema_definition_to_json(Values)}], add_metadata(Base@7, Meta@7); {nullable_schema, Inner, Meta@8} -> Base@8 = [{<<"oneOf"/utf8>>, gleam@json:array( [{null_schema, {metadata, none, none, [], none}}, Inner], fun schema_definition_to_json/1 )}], add_metadata(Base@8, Meta@8); {one_of_schema, Variants, Meta@9} -> Base@9 = [{<<"oneOf"/utf8>>, gleam@json:array(Variants, fun schema_definition_to_json/1)}], add_metadata(Base@9, Meta@9); {any_of_schema, Variants@1, Meta@10} -> Base@10 = [{<<"anyOf"/utf8>>, gleam@json:array( Variants@1, fun schema_definition_to_json/1 )}], add_metadata(Base@10, Meta@10); {enum_schema, Values@1, Meta@11} -> Base@11 = [{<<"type"/utf8>>, gleam@json:string(<<"string"/utf8>>)}, {<<"enum"/utf8>>, gleam@json:array(Values@1, fun gleam@json:string/1)}], add_metadata(Base@11, Meta@11); {const_schema, Value, Meta@12} -> Base@12 = [{<<"const"/utf8>>, Value}], add_metadata(Base@12, Meta@12); {tuple_schema, Items@1, Meta@13} -> Base@13 = [{<<"type"/utf8>>, gleam@json:string(<<"array"/utf8>>)}, {<<"prefixItems"/utf8>>, gleam@json:array(Items@1, fun schema_definition_to_json/1)}, {<<"items"/utf8>>, gleam@json:bool(false)}, {<<"minItems"/utf8>>, gleam@json:int(erlang:length(Items@1))}, {<<"maxItems"/utf8>>, gleam@json:int(erlang:length(Items@1))}], add_metadata(Base@13, Meta@13) end. -file("src/sextant.gleam", 2173). ?DOC( " Generate a JSON Schema 2020-12 document.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let schema_json = sextant.to_json(user_schema())\n" " ```\n" ). -spec to_json(json_schema(any())) -> gleam@json:json(). to_json(Schema) -> Base_fields = schema_definition_to_fields(erlang:element(2, Schema)), gleam@json:object( [{<<"$schema"/utf8>>, gleam@json:string( <<"https://json-schema.org/draft/2020-12/schema"/utf8>> )} | Base_fields] ).