123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657moduletypeCONFORMIST=sigtypeerror_msg(** {1 Fields}
Every member of the list in the example is a field. Use the provided
[fold_left] to traverse the list of fiels. Helper functions are provided
that operate on fields. *)moduleField:sig(** A field of type [('meta, 'a) t] represents the static type ['a] and it
can hold arbitrary meta data of type ['meta]. That meta data can be used
to build functionality on top of conformist. *)type('meta,'a)ttype(_,_,_)list=|[]:('meta,'ty,'ty)list|(::):('meta,'a)t*('meta,'b,'ty)list->('meta,'a->'b,'ty)list(** A [list] is a list of fields. Note that this is not the list from
[List.t] so make sure to open this scope locally when defining a
list of fields. *)type_any_field=AnyField:('meta,'a)t->'metaany_field(** [meta field] returns an optional meta data of a [field]. This can be
used to store arbitrary meta data in each field. Note that the type of
the meta data has to be the same for all fields. *)valmeta:'aany_field->'aoption(** [name field] returns the name of the [field], which uniquely identifies
the field within one schema. *)valname:'aany_field->string(** [validate field values] decodes [values] and runs the [field]'s
validation logic on the decoded values. Both decoding and validation
might fail, which results in an error string. *)valvalidate:'aany_field->stringList.t->error_msgoption(** [optional field] returns [true] if the [field] is optional and [false]
otherwise. *)valoptional:'aany_field->bool[@@deprecated"Please use is_optional instead"](** [is_optional field] returns [true] if the [field] is optional and
[false] otherwise. *)valis_optional:'aany_field->bool(** [type_ field] returns a string representation of the type of [field]. *)valtype_:'aany_field->string(** [encode_default field] tries to encode the default value if present and
to return it as string. *)valencode_default:'aany_field->stringList.tend(** A ['a decoder] tries to turn values into a value of type ['a]. It returns
a descriptive errors message upon failure. *)type'adecoder=stringlist->('a,error_msg)result(** A ['a encoder] encodes a value of type ['a] into a list of strings. *)type'aencoder='a->stringlist(** A ['a validator] takes something of type ['a] and returns an error string
if validation fails, [None] if everything is ok *)type'avalidator='a->error_msgoption(** Use [custom decoder encoder ?default ?type_ ?meta ?validator field_name]
to create a field with a custom type that is not supported out-of-the box.
Provide a custom [decoder] with a descriptive error message so conformist
knows how to turn a string into your custom value.
A string representation of the static [type_] can also be provided, by
default the [field_name] is taken.
A [default] value can be provided. *)valcustom:'adecoder->'aencoder->?default:'a->?type_:string->?meta:'b->?validator:'avalidator->string->('b,'a)Field.t(** [optional ?meta field] turns a [field] into an optional field. If the
field does not exist in the input data or if the associated value in the
input data is an empty list, the value is [None]. If the data is not
provided in the input at all, no validation logic is executed.
Example:
{[
let make name address = { name; address } in
let schema =
Conformist.(make [ string "name"; optional (string "address") ] make)
in
(* Decoding fails *)
let decoded = Conformist.decode schema [] in
(* Validation fails *)
let validated = Conformist.validate [] in
(* Decoding succeeds, address is [None] *)
let decoded = Conformist.decode schema [ "name", [ "Walter" ] ] in
let decoded =
Conformist.decode schema [ "name", [ "Walter" ]; "address", [] ]
in
(* Validation succeeds *)
let validated = Conformist.validate [ "name", [ "Walter" ] ] in
()
]} *)valoptional:?meta:'a->('b,'c)Field.t->('a,'coption)Field.t(** [list ?default ?meta field] returns a field that decodes to a list of
[field].
[default] is an optional default value for the field.
[meta] is optional meta data that is attached to the field. This is useful
when implementing features on top of conformist. *)vallist:?default:'clist->?meta:'a->('b,'c)Field.t->('a,'clist)Field.t(** [bool ?default ?meta ?msg field_name] returns a field with name
[field_name] that decodes to a [bool].
[default] is an optional default value for the field.
[meta] is optional meta data that is attached to the field. This is useful
when implementing features on top of conformist.
[msg] is the decode error message that is returned if {!decode} fails. *)valbool:?default:bool->?meta:'a->?msg:error_msg->string->('a,bool)Field.t(** [float ?default ?meta ?msg ?validator field_name] returns a field with
name [field_name] that decodes to [float].
[default] is an optional default value for the field.
[meta] is optional meta data that is attached to the field. This is useful
when implementing features on top of conformist.
[msg] is the decode error message that is returned if {!decode} fails.
[validator] is an optional validator that is run when calling {!validate}.
By default, no validation logic is executed. This means that if a value
decodes, it is valid. *)valfloat:?default:float->?meta:'a->?msg:error_msg->?validator:floatvalidator->string->('a,float)Field.t(** [int ?meta ?msg ?validator field_name] returns a field with name
[field_name] that decodes to [int].
[default] is an optional default value for the field.
[meta] is optional meta data that is attached to the field. This is useful
when implementing features on top of conformist.
[msg] is the decode error message that is returned if {!decode} fails.
[validator] is an optional validator that is run when calling {!validate}.
By default, no validation logic is executed. This means that if a value
decodes, it is valid. *)valint:?default:int->?meta:'a->?msg:error_msg->?validator:intvalidator->string->('a,int)Field.t(** [string ?meta ?validator field_name] return a field with name [field_name]
that decodes to [string].
[default] is an optional default value for the field.
[meta] is optional meta data that is attached to the field. This is useful
when implementing features on top of conformist.
[msg] is the decode error message that is returned if {!decode} fails.
[validator] is an optional validator that is run when calling {!validate}.
By default, no validation logic is executed. This means that if a value
decodes, it is valid. *)valstring:?default:string->?meta:'a->?msg:error_msg->?validator:stringvalidator->string->('a,string)Field.t(** Don't use [date], use {!datetime} instead.*)valdate:?default:Ptime.date->?meta:'a->?msg:error_msg->?validator:(int*int*int)validator->string->('a,Ptime.date)Field.t[@@ocaml.deprecated"Use [Conformist.datetime] instead."](** [datetime ?default ?meta ?validator field_name] returns a field with name
[field_name] that decodes to [datetime].
[default] is an optional default value for the field.
[meta] is optional meta data that is attached to the field. This is useful
when implementing features on top of conformist.
[msg] is the decode error message that is returned if {!decode} fails.
[validator] is an optional validator that is run when calling {!validate}.
By default, no validation logic is executed. This means that if a value
decodes, it is valid. *)valdatetime:?default:Ptime.t->?meta:'a->?msg:error_msg->?validator:Ptime.tvalidator->string->('a,Ptime.t)Field.t(** {1 Schema}
A schema is a list of fields. Input data can be decoded and validated
using a schema. *)(** [t] is a conformist schema. *)type('meta,'ctor,'ty)t(** [empty] creates an empty schema. *)valempty:('a,unit,unit)t(** [make fields constructor] create a schema. *)valmake:('a,'b,'c)Field.list->'b->('a,'b,'c)t(** [fold_left ~f ~init schema] traverses the list of fields of [schema]. Use
the functions in {!Field} to work with a generic field. *)valfold_left:f:('res->'metaField.any_field->'res)->init:'res->('meta,'args,'ty)t->'res(** An error [(field, value, error_message)] is used to for decoding errors
and validation errors.
[field] is the field name of the input that failed to decode or validate,
[values] are the input values (if they were provided) and [error_message]
is the decoding or validation error message.
An empty list of [error] means that the schema is valid. *)typeerror=string*stringlist*error_msg(** The [input] represents unsafe data that needs to be decoded and validated.
This is typically some user input. *)typeinput=(string*stringlist)list(** [decode schema input] returns the decoded value of type ['ty] by decoding
the [input] using the [schema].
No validation logic is executed in this step. *)valdecode:('meta,'ctor,'ty)t->input->('ty,error)result(** [validate schema input] returns a list of validation errors by running the
validators defined in [schema] on the [input] data. An empty list implies
that there are no validation errors and that the input is valid according
to the schema.
Note that [input] that has no validation errors might still fail to
decode, depending on the validation functions specified in [schema]. *)valvalidate:('meta,'ctor,'ty)t->input->errorlist(** [decode_and_validate schema input] returns the decoded and validated value
of type ['ty] by decoding the [input] using the [schema] and running its
validators.
Use [decode_and_validate] to combine the functions [decode] and [validate]
and to either end up with the decoded value or all errors that happened
during the decoding and validation steps. *)valdecode_and_validate:('meta,'ctor,'ty)t->input->('ty,errorlist)Result.tendmoduletypeERROR=sigtypeerrorvalinvalid_bool:errorvalinvalid_float:errorvalinvalid_int:errorvalinvalid_string:errorvalinvalid_date:errorvalinvalid_datetime:errorvalno_value:errorvalof_string:string->errorendmoduleMake(Error:ERROR)=structtypeerror_msg=Error.errortype'adecoder=stringlist->('a,Error.error)resulttype'aencoder='a->stringlisttype'avalidator='a->Error.erroroptionletalways_valid_=NonemoduleField=structtype('meta,'a)t={name:string;meta:'metaoption;default:'aoption;decoder:'adecoder;encoder:'aencoder;type_:string;validator:'avalidator;optional:bool}type(_,_,_)list=|[]:('meta,'ty,'ty)list|(::):('meta,'a)t*('meta,'b,'ty)list->('meta,'a->'b,'ty)listtype_any_field=AnyField:('meta,'a)t->'metaany_fieldletmeta(AnyFieldfield)=field.metaletname(AnyFieldfield)=field.nameletvalidate(AnyFieldfield)input=matchfield.decoderinputwith|Okvalue->field.validatorvalue|Errormsg->Somemsg;;letoptional(AnyFieldfield)=field.optionalletis_optional(AnyFieldfield)=field.optionallettype_(AnyFieldfield)=field.type_letencode_default(AnyFieldfield):stringList.t=matchfield.defaultwith|Somev->field.encoderv|None->[];;letmakenamemetadecoderencoderdefaulttype_validatoroptional={name;meta;default;decoder;encoder;type_;validator;optional};;letmake_customdecoderencoder?default?type_?meta?(validator=always_valid)name=lettype_=Option.valuetype_~default:nameinmakenamemetadecoderencoderdefaulttype_validatorfalse;;letmake_optional?metafield=letdecoderstrings=matchfield.decoderstrings,stringswith(* Decoding succeeds with nothing when no strings provided *)|_,[]->OkNone|Okresult,_->Ok(Someresult)|Errormsg,_->Errormsginletvalidatora=matchawith|Somea->field.validatora|None->Noneinletencodera=matchawith|Somea->field.encodera|None->["None"]inletdefault=matchfield.defaultwith|Somed->Some(Somed)|None->Noneinmakefield.namemetadecoderencoderdefaultfield.type_validatortrue;;letmake_list?default?metafield=letdecoder(l:stringList.t):('aList.t,Error.error)result=List.fold_left(funres(el:string)->matchres,field.decoder[el]with|Okresult,Okel->Ok(List.conselresult)|Ok_,Errormsg->Errormsg|Errormsg,_->Errormsg)(Ok[])l|>Result.mapList.revinletvalidatorl=List.fold_left(funresel->matchres,field.validatorelwith|None,None->None|None,Somemsg->Somemsg|Somemsg,_->Somemsg)Nonelinletencoder(a:'aList.t)=List.mapfield.encodera|>List.concatinmakefield.namemetadecoderencoderdefaultfield.type_validatortrue;;letmake_bool?default?meta?(msg=Error.invalid_bool)name=letdecoderinput=tryOk(bool_of_string(List.hdinput))with|_->Errormsginletencoderinput=List.[string_of_boolinput]inmakenamemetadecoderencoderdefault"bool"always_validfalse;;letmake_float?default?meta?(msg=Error.invalid_float)?(validator=always_valid)name=letdecoderstring=tryOk(float_of_string(List.hdstring))with|_->Errormsginletencoderinput=List.[string_of_floatinput]inmakenamemetadecoderencoderdefault"float"validatorfalse;;letmake_int?default?meta?(msg=Error.invalid_int)?(validator=always_valid)name=letdecoderstring=tryOk(int_of_string(List.hdstring))with|_->Errormsginletencoderinput=List.[string_of_intinput]inmakenamemetadecoderencoderdefault"int"validatorfalse;;letmake_string?default?meta?(msg=Error.invalid_string)?(validator=always_valid)name=letdecoderinput=tryOk(List.hdinput)with|_->Errormsginletencoderid=List.[id]inmakenamemetadecoderencoderdefault"string"validatorfalse;;letmake_date?default?meta?(msg=Error.invalid_date)?(validator=always_valid)name=letdecoderinput=trymatchString.split_on_char'-'(List.hdinput)with|[y;m;d]->(matchint_of_string_opty,int_of_string_optm,int_of_string_optdwith|Somey,Somem,Somed->Ok(y,m,d)|_->Errormsg)|_->Errormsgwith|_->Errormsginletencoder(y,m,d)=List.[Format.sprintf"%d-%d-%d"ymd]inmakenamemetadecoderencoderdefault"date"validatorfalse;;letmake_datetime?default?meta?(msg=Error.invalid_datetime)?(validator=always_valid)name=letdecoderstring=trymatchPtime.of_rfc3339(List.hdstring)with|Ok(timestamp,_,_)->Oktimestamp|Error(`RFC3339(_,_))->Errormsgwith|_->Errormsginletencoderptime=List.[Ptime.to_rfc3339ptime]inmakenamemetadecoderencoderdefault"time"validatorfalse;;endletcustom=Field.make_customletoptional=Field.make_optionalletlist=Field.make_listletbool=Field.make_boolletfloat=Field.make_floatletint=Field.make_intletstring=Field.make_stringletdate=Field.make_dateletdatetime=Field.make_datetimetype('meta,'ctor,'ty)t={fields:('meta,'ctor,'ty)Field.list;ctor:'ctor}letempty={fields=Field.[];ctor=()}letmakefieldsctor={fields;ctor}letrecfold_left':typetyargs.f:('res->'metaField.any_field->'res)->init:'res->('meta,args,ty)Field.list->'res=fun~f~initfields->matchfieldswith|[]->init|field::fields->fold_left'~f~init:(finit(AnyFieldfield))fields;;letfold_left~f~initschema=fold_left'~f~initschema.fieldstypeerror=string*stringlist*Error.errortypeinput=(string*stringlist)listletvalidate(schema:('meta,'ctor,'ty)t)(input:(string*stringlist)list):errorlist=letferrorsfield=letname=Field.namefieldinmatchList.assocnameinputwith|values->(matchField.validatefieldvalueswith|Somemsg->List.cons(name,values,msg)errors|None->errors)|exceptionNot_found->(matchField.is_optionalfield,Field.encode_defaultfieldwith|true,List.[]->errors|false,List.[]->List.cons(name,[],Error.no_value)errors|_,default->(matchField.validatefielddefaultwith|Somemsg->List.cons(name,[],msg)errors|None->errors))infold_left~f~init:[]schema|>List.rev;;letrecdecode:typemetactorty.(meta,ctor,ty)t->(string*stringlist)list->(ty,error)Result.t=fun{fields;ctor}fields_assoc->letopen!Fieldinmatchfieldswith|[]->Okctor|field::fields->lethandle_missing()=matchfield.decoder[]with|Okvalue->(matchctorvaluewith|ctor->decode{fields;ctor}fields_assoc|exceptionexn->letmsg=Error.of_string(Printexc.to_stringexn)inError(field.name,[],msg))|Errormsg->Error(field.name,[],msg)in(matchList.assocfield.namefields_assocwith|[]->(matchfield.defaultwith|Somevalue->(matchctorvaluewith|ctor->decode{fields;ctor}fields_assoc|exceptionexn->letmsg=Error.of_string(Printexc.to_stringexn)inError(field.name,[],msg))|None->handle_missing())|values->(matchfield.decodervalueswith|Okvalue->(matchctorvaluewith|ctor->decode{fields;ctor}fields_assoc|exceptionexn->letmsg=Error.of_string(Printexc.to_stringexn)inError(field.name,values,msg))|Errormsg->Error(field.name,values,msg))|exceptionNot_found->(matchfield.default,Field.is_optional@@AnyFieldfieldwith|Somevalue,_->(matchctorvaluewith|ctor->decode{fields;ctor}fields_assoc|exceptionexn->letmsg=Error.of_string(Printexc.to_stringexn)inletvalues=matchfield.defaultwith|Somedefault->field.encoderdefault|None->[]inError(field.name,values,msg))|None,false->Error(field.name,[],Error.no_value)|None,true->handle_missing()));;letdecode_and_validateschemainput=letvalidation_errors=validateschemainputinmatchdecodeschemainput,validation_errorswith|Okvalue,[]->Okvalue|Ok_,validation_errors->Errorvalidation_errors|Error(field_name,value,msg),validation_errors->validation_errors|>List.filter(fun(name,_,_)->not(String.equalnamefield_name))|>List.cons(field_name,value,msg)|>Result.error;;end