Packages

Bidirectional, error-accumulating validation and sanitization codecs for Gleam.

Current section

Files

Jump to
pedantic src pedantic.gleam
Raw

src/pedantic.gleam

import gleam/dict.{type Dict}
import gleam/dynamic
import gleam/option.{type Option}
import pedantic/codec
import pedantic/decode
import pedantic/encode
import pedantic/json
import pedantic/report
// Re-export core types
pub type Codec(t) =
codec.Codec(t)
pub type Decoder(t) =
decode.Decoder(t)
pub type Encoder(t) =
encode.Encoder(t)
pub type Report =
report.Report
pub type Error =
report.Error
pub type Warning =
report.Warning
pub type Path =
report.Path
pub type PathSegment =
report.PathSegment
pub type ErrorKind =
report.ErrorKind
pub type WarningKind =
report.WarningKind
pub type TypeGraph =
report.TypeGraph
pub type TypeField =
report.TypeField
pub type ValidationIssue =
report.ValidationIssue
pub type ValidationReport =
report.ValidationReport
pub type ValidationWarning =
report.ValidationWarning
pub type IssueKind =
report.IssueKind
// Re-export primitive codecs (Coerced & Exact)
pub const coerced_int = codec.coerced_int
pub const exact_int = codec.exact_int
pub const coerced_float = codec.coerced_float
pub const exact_float = codec.exact_float
pub const coerced_bool = codec.coerced_bool
pub const exact_bool = codec.exact_bool
pub const coerced_string = codec.coerced_string
pub const exact_string = codec.exact_string
// Default aliases (default to lax coercion)
pub const int = codec.int
pub const string = codec.string
pub const bool = codec.bool
pub const float = codec.float
// Re-export combinator functions
pub fn list(element_codec: Codec(a)) -> Codec(List(a)) {
codec.list(element_codec)
}
pub fn optional(inner_codec: Codec(a)) -> Codec(Option(a)) {
codec.optional(inner_codec)
}
pub fn dict(value_codec: Codec(a)) -> Codec(Dict(String, a)) {
codec.dict(value_codec)
}
// Re-export object builder functions
pub fn object(name: String, constructor: constructor) {
codec.object(name, constructor)
}
pub fn field(
builder: codec.ObjectBuilder(fn(field_type) -> next, value),
name: String,
field_codec: Codec(field_type),
get: fn(value) -> field_type,
) {
codec.field(builder, name, field_codec, get)
}
pub const key_required = codec.key_required
pub const key = codec.key
pub fn key_optional(
builder: codec.ObjectBuilder(fn(Option(field_type)) -> next, value),
name: String,
field_codec: Codec(field_type),
get: fn(value) -> Option(field_type),
) {
codec.key_optional(builder, name, field_codec, get)
}
pub fn key_with_default(
builder: codec.ObjectBuilder(fn(field_type) -> next, value),
name: String,
field_codec: Codec(field_type),
default_val: field_type,
get: fn(value) -> field_type,
) {
codec.key_with_default(builder, name, field_codec, default_val, get)
}
pub fn build(builder: codec.ObjectBuilder(value, value)) -> Codec(value) {
codec.build(builder)
}
pub fn default(codec: Codec(Option(a)), default_val: a) -> Codec(a) {
codec.default(codec, default_val)
}
pub const default_to = codec.default_to
pub const unwrap_or = codec.unwrap_or
/// Creates a Codec by custom pairing a standalone Decoder and Encoder.
pub fn custom(decoder: Decoder(t), encoder: Encoder(t)) -> Codec(t) {
codec.custom(decoder, encoder)
}
// Re-export validations
pub fn refine(
codec: Codec(t),
rule: fn(t) -> Result(t, ErrorKind),
) -> Codec(t) {
codec.refine(codec, rule)
}
pub fn min(codec: Codec(Int), minimum: Int) -> Codec(Int) {
codec.min(codec, minimum)
}
pub fn max(codec: Codec(Int), maximum: Int) -> Codec(Int) {
codec.max(codec, maximum)
}
pub fn min_length(codec: Codec(String), minimum: Int) -> Codec(String) {
codec.min_length(codec, minimum)
}
pub fn max_length(codec: Codec(String), maximum: Int) -> Codec(String) {
codec.max_length(codec, maximum)
}
// Zod-aligned fluent validations
pub fn trim(codec: Codec(String)) -> Codec(String) {
codec.trim(codec)
}
pub fn lowercase(codec: Codec(String)) -> Codec(String) {
codec.lowercase(codec)
}
pub fn uppercase(codec: Codec(String)) -> Codec(String) {
codec.uppercase(codec)
}
pub fn collapse_whitespace(codec: Codec(String)) -> Codec(String) {
codec.collapse_whitespace(codec)
}
pub fn replace(
codec: Codec(String),
pattern: String,
replacement: String,
) -> Codec(String) {
codec.replace(codec, pattern, replacement)
}
pub fn remove_chars(codec: Codec(String), characters: String) -> Codec(String) {
codec.remove_chars(codec, characters)
}
pub fn allow_chars(codec: Codec(String), characters: String) -> Codec(String) {
codec.allow_chars(codec, characters)
}
pub fn strip_control_chars(codec: Codec(String)) -> Codec(String) {
codec.strip_control_chars(codec)
}
pub fn normalize_email(codec: Codec(String)) -> Codec(String) {
codec.normalize_email(codec)
}
pub fn normalize_uuid(codec: Codec(String)) -> Codec(String) {
codec.normalize_uuid(codec)
}
pub fn default_if_blank(
codec: Codec(String),
default_value: String,
) -> Codec(String) {
codec.default_if_blank(codec, default_value)
}
pub fn empty_as_none(codec: Codec(String)) -> Codec(Option(String)) {
codec.empty_as_none(codec)
}
pub fn digits_only(codec: Codec(String)) -> Codec(String) {
codec.digits_only(codec)
}
pub fn letters_only(codec: Codec(String)) -> Codec(String) {
codec.letters_only(codec)
}
pub fn remove_whitespace(codec: Codec(String)) -> Codec(String) {
codec.remove_whitespace(codec)
}
pub fn normalize_line_endings(codec: Codec(String)) -> Codec(String) {
codec.normalize_line_endings(codec)
}
pub fn strip_non_ascii(codec: Codec(String)) -> Codec(String) {
codec.strip_non_ascii(codec)
}
pub fn positive(codec: Codec(Int)) -> Codec(Int) {
codec.positive(codec)
}
pub fn negative(codec: Codec(Int)) -> Codec(Int) {
codec.negative(codec)
}
pub fn non_negative(codec: Codec(Int)) -> Codec(Int) {
codec.non_negative(codec)
}
pub fn non_positive(codec: Codec(Int)) -> Codec(Int) {
codec.non_positive(codec)
}
pub fn length(codec: Codec(String), len: Int) -> Codec(String) {
codec.length(codec, len)
}
pub fn email(codec: Codec(String), msg: String) -> Codec(String) {
codec.email(codec, msg)
}
pub fn uuid(codec: Codec(String), msg: String) -> Codec(String) {
codec.uuid(codec, msg)
}
pub fn check(
codec: Codec(t),
predicate: fn(t) -> Bool,
msg: String,
) -> Codec(t) {
codec.check(codec, predicate, msg)
}
pub fn assert_that(
codec: Codec(t),
predicate: fn(t) -> Bool,
error: ErrorKind,
) -> Codec(t) {
codec.assert_that(codec, predicate, error)
}
pub fn with_error_formatter(
codec: Codec(t),
formatter: fn(dynamic.Dynamic, IssueKind) -> String,
) -> Codec(t) {
codec.with_error_formatter(codec, formatter)
}
pub fn with_error_message(codec: Codec(t), msg: String) -> Codec(t) {
codec.with_error_message(codec, msg)
}
pub fn with_template(codec: Codec(t), template: String) -> Codec(t) {
codec.with_template(codec, template)
}
pub fn min_size(codec: Codec(List(a)), minimum: Int) -> Codec(List(a)) {
codec.min_size(codec, minimum)
}
pub fn max_size(codec: Codec(List(a)), maximum: Int) -> Codec(List(a)) {
codec.max_size(codec, maximum)
}
pub fn non_empty_list(element_codec: Codec(a)) -> Codec(List(a)) {
codec.non_empty_list(element_codec)
}
// Re-export metadata builders
pub fn title(codec: Codec(t), name: String) -> Codec(t) {
codec.title(codec, name)
}
pub fn describe(codec: Codec(t), desc: String) -> Codec(t) {
codec.describe(codec, desc)
}
pub fn example(codec: Codec(t), ex: dynamic.Dynamic) -> Codec(t) {
codec.example(codec, ex)
}
// Re-export JSON runners
pub fn decode_json(codec: Codec(t), json_string: String) -> Result(t, Report) {
json.decode_json(codec, json_string)
}
pub fn encode_json(codec: Codec(t), val: t) -> String {
json.encode_json(codec, val)
}
// Zod-aligned entrypoints
pub fn safe_parse(codec: Codec(t), val: dynamic.Dynamic) -> Result(t, Report) {
codec.decoder.function(val)
}
pub fn safe_parse_json(
codec: Codec(t),
json_string: String,
) -> Result(t, Report) {
json.decode_json(codec, json_string)
}
pub fn any_of(decoders: List(Decoder(t))) -> Decoder(t) {
decode.any_of(decoders)
}
pub fn map(decoder: Decoder(a), transform_fn: fn(a) -> b) -> Decoder(b) {
decode.map(decoder, transform_fn)
}