Repodb.Changeset

repodb · API reference

type change = 
  | StringChange of string
  | IntChange of int
  | FloatChange of float
  | BoolChange of bool
  | NullChange

Phoenix/Ecto-style changesets for casting, validation, and constraints.

A changeset tracks proposed changes to a record, validation errors, and the intended action. Use it at boundaries where untrusted input becomes trusted domain data: HTTP forms, JSON payloads, CLI arguments, imports, and tests.

Basic flow:

let create_user params =
    User.empty
    |> Changeset.for_insert
    |> Changeset.cast params ~fields:[ User.email; User.name ]
    |> Changeset.validate_required [ User.email; User.name ]
    |> Changeset.validate_format User.email ~pattern:"^[^@]+@[^@]+$"
    |> Changeset.unique_constraint User.email ~name:"users_email_key"

cast only accepts named fields and ignores unknown parameters. Validation functions add structured Error.validation_error values without raising. Use apply_action or Repo.validate_changeset to turn a valid changeset into data or a validation error result.

Constraint helpers such as unique_constraint, foreign_key_constraint, and check_constraint document expected database errors. Repos can use that metadata to convert backend constraint failures into user-facing validation errors instead of opaque SQL errors.

Changes currently store scalar form values as strings plus a few primitive variants. For rich domain parsing, parse values before calling put_change or write a model-specific changeset function that converts the boundary representation into a typed record.

module StringMap : sig ... end
module StringSet : sig ... end
type 'a t = {
  data : 'a;
  changes : change StringMap.t;
  errors : Error.validation_error list;
  valid : bool;
  action : action option;
  constraints : constraint_def list;
}
and action = 
  | Insert
  | Update
  | Delete
and constraint_def = {
  constraint_name : string;
  constraint_field : string;
  constraint_type : constraint_type;
}
and constraint_type = 
  | UniqueConstraint
  | ForeignKeyConstraint of {
    table : string;
    column : string;
  }
  | CheckConstraint of string
val create : 'a -> 'a t
val change : 'a -> 'a t
val for_insert : 'a -> 'a t
val for_update : 'a -> 'a t
val cast : 
  (StringSet.elt * string) list ->
  fields:('a, 'b) Field.t list ->
  'c t ->
  'c t
val cast_assoc : 
  (StringSet.elt * change) list ->
  fields:('a, 'b) Field.t list ->
  'c t ->
  'c t
val put_change : ('a, 'b) Field.t -> string -> 'c t -> 'c t
val delete_change : ('a, 'b) Field.t -> 'c t -> 'c t
val get_change : 'a t -> ('b, 'c) Field.t -> string option
val get_field : 'a t -> ('a, 'b) Field.t -> 'b
val add_error : 
  field:string ->
  message:string ->
  validation:string ->
  'a t ->
  'a t
val validate_required : ('a, 'b) Field.t list -> 'c t -> 'c t
val validate_format : ('a, 'b) Field.t -> pattern:string -> 'c t -> 'c t
val validate_length : 
  ('a, 'b) Field.t ->
  ?min:int ->
  ?max:int ->
  ?is:int ->
  'c t ->
  'c t
val validate_inclusion : ('a, 'b) Field.t -> values:string list -> 'c t -> 'c t
val validate_exclusion : ('a, 'b) Field.t -> values:string list -> 'c t -> 'c t
val validate_number : 
  ('a, 'b) Field.t ->
  ?greater_than:int ->
  ?less_than:int ->
  ?greater_than_or_equal:int ->
  ?less_than_or_equal:int ->
  'c t ->
  'c t
val validate_acceptance : ('a, 'b) Field.t -> 'c t -> 'c t
val validate_confirmation : 
  ('a, 'b) Field.t ->
  confirmation_field:('c, 'd) Field.t ->
  'e t ->
  'e t
val validate_change : 
  ('a, 'b) Field.t ->
  (string -> (unit, string) result) ->
  'c t ->
  'c t
val validate : 'a -> ('a -> 'b) -> 'b
val unique_constraint : ('a, 'b) Field.t -> 'c t -> 'c t
val foreign_key_constraint : 
  ('a, 'b) Field.t ->
  references:(string * string) ->
  'c t ->
  'c t
val check_constraint : 
  name:string ->
  ('a, 'b) Field.t ->
  expression:string ->
  'c t ->
  'c t
val is_valid : 'a t -> bool
val errors : 'a t -> Error.validation_error list
val data : 'a t -> 'a
val changes : 'a t -> (StringMap.key * change) list
val changes_map : 'a t -> change StringMap.t
val action : 'a t -> action option
val get_error : 'a t -> ('b, 'c) Field.t -> Error.validation_error option
val has_error : 'a t -> ('b, 'c) Field.t -> bool
val traverse_errors : 'a t -> (string -> string -> unit) -> unit
val error_messages : 'a t -> string list
val apply_action : 'a t -> ('a, Error.validation_error list) result
type 'a assoc_changeset = {
  assoc_name : string;
  assoc_changesets : 'a t list;
  on_replace : [ `Raise | `Mark_as_invalid | `Delete | `Update ];
}
val cast_assoc_one : 
  assoc_name:string ->
  params:(string * 'a) list ->
  cast_fn:('a -> 'b t) ->
  'c t ->
  'c t
val cast_assoc_many : 
  assoc_name:string ->
  params_list:'a list ->
  cast_fn:('a -> 'b t) ->
  'c t ->
  'c t
val put_assoc : assoc_name:StringMap.key -> json_string:string -> 'a t -> 'a t
val cast_embed : 
  embed_name:string ->
  params:(string * 'a) list ->
  parse:('a -> ('b, string) result) ->
  'c t ->
  'c t
val put_embed : embed_name:StringMap.key -> json_string:string -> 'a t -> 'a t
val merge_errors : 'a t -> 'b t -> prefix:string -> 'a t