···11-// https://docs.djangoproject.com/en/5.0/topics/forms/
22-// https://github.com/nakaixo/nakai
33-// date time handling https://hexdocs.pm/birl/index.html
44-55-// TODO
66-// - list fields
77-// - form sets
88-// - csrf token
99-// - data order doesn't matter
1010-// - required/option
1111-1212-import gleam/list
1313-import gleam/option.{type Option, None, Some}
1414-import gleam/result
1515-1616-pub type HasDecoder
1717-1818-pub type NoDecoder
1919-2020-pub type FieldsWithErrors(format) =
2121- List(Field(format))
2222-2323-pub opaque type Form(format, output, decoder, has_decoder) {
2424- Form(
2525- fields: List(Field(format)),
2626- parse_with: fn(List(Field(format)), decoder) ->
2727- Result(output, FieldsWithErrors(format)),
2828- decoder: Option(decoder),
2929- )
3030-}
3131-3232-pub type Field(format) {
3333- Field(
3434- name: String,
3535- label: String,
3636- help_text: String,
3737- render: fn(Field(format)) -> format,
3838- value: String,
3939- )
4040- InvalidField(
4141- name: String,
4242- label: String,
4343- help_text: String,
4444- render: fn(Field(format)) -> format,
4545- value: String,
4646- error: String,
4747- )
4848-}
4949-5050-pub type Input(output, format) {
5151- Input(
5252- render: fn(Field(format)) -> format,
5353- validate: fn(String) -> Result(output, String),
5454- )
5555-}
5656-5757-pub fn new() -> Form(format, a, a, NoDecoder) {
5858- Form([], fn(_, output) { Ok(output) }, None)
5959-}
6060-6161-pub fn add(
6262- form: Form(format, fn(b) -> c, a, has_decoder),
6363- thing: #(Field(format), fn(String) -> Result(b, String)),
6464-) -> Form(format, c, a, has_decoder) {
6565- let #(field, validate) = thing
6666- case form {
6767- Form(fields, parse_with, decoder) -> {
6868- // create new form with the new field and update the parse
6969- // function to handle the new details from the type of the
7070- // field
7171- Form(
7272- fields: [field, ..fields],
7373- parse_with: fn(fields, decoder: a) {
7474- let assert [field, ..rest] = fields
7575- case parse_with(rest, decoder), validate(field.value) {
7676- // the form we've already parsed has no errors and the field
7777- // we just parsed has no errors. so we can move on to the next
7878- Ok(next), Ok(value) -> Ok(next(value))
7979-8080- // the form already has errors even though this one succeeded.
8181- // so add this to the list and stop anymore parsing
8282- Error(fields), Ok(_value) -> Error([field, ..fields])
8383-8484- // form was good so far, but this field errored, so need to
8585- // mark this field as invalid and return all the fields we've got
8686- // so far
8787- Ok(_), Error(error) ->
8888- Error([
8989- InvalidField(
9090- name: field.name,
9191- label: field.label,
9292- help_text: field.help_text,
9393- render: field.render,
9494- value: field.value,
9595- error: error,
9696- ),
9797- ..rest
9898- ])
9999-100100- // form already has errors and this field errored, so add this field
101101- // to the list
102102- Error(fields), Error(error) ->
103103- Error([
104104- InvalidField(
105105- name: field.name,
106106- label: field.label,
107107- help_text: field.help_text,
108108- render: field.render,
109109- value: field.value,
110110- error: error,
111111- ),
112112- ..fields
113113- ])
114114- }
115115- },
116116- decoder:,
117117- )
118118- }
119119- // FormWithErrors(fields) -> FormWithErrors([field, ..fields])
120120- }
121121-}
122122-123123-pub fn data(
124124- form: Form(a, b, format, has_decoder),
125125- input: List(#(String, String)),
126126-) -> Form(a, b, format, has_decoder) {
127127- case form {
128128- Form(fields, parse_with, decoder) -> {
129129- fields
130130- // we always prepend fields, so reverse to get correct order
131131- // TODO I think we're going to make it so order doesn't matter
132132- |> list.reverse
133133- |> do_add_input_data(input, [])
134134- |> Form(parse_with, decoder)
135135- }
136136- // FormWithErrors(..) -> form
137137- }
138138-}
139139-140140-fn do_add_input_data(
141141- fields: List(Field(format)),
142142- data: List(#(String, String)),
143143- acc: List(Field(format)),
144144-) {
145145- case fields, data {
146146- // no more fields, we've return all the fields with data we have accumulated
147147- [], _ -> acc
148148- // no more data! return all the fields we have left plus the ones we accumulated
149149- _, [] -> list.append(fields, acc)
150150- // we have a field and data, and the names match. update field to have data
151151- [Field(name: field_name, ..) as field, ..fields_rest],
152152- [#(data_name, value), ..data_rest]
153153- if field_name == data_name
154154- ->
155155- do_add_input_data(fields_rest, data_rest, [
156156- Field(
157157- name: field_name,
158158- label: field.label,
159159- help_text: field.help_text,
160160- render: field.render,
161161- value: value,
162162- ),
163163- ..acc
164164- ])
165165- // at this point we still have fields and data left, but the first
166166- // field and first data don't match. so we decide we've got no data
167167- // for the first field and move on to the next. but we need to add
168168- // this field without data to the accumulator
169169- [field, ..fields_rest], _ ->
170170- do_add_input_data(fields_rest, data, [field, ..acc])
171171- }
172172-}
173173-174174-pub fn decodes(
175175- form: Form(format, output, decoder, has_decoder),
176176- decoder: decoder,
177177-) -> Form(format, output, decoder, HasDecoder) {
178178- // can use let assert here because you can't have errors if you
179179- // haven't tried to parse and decode, which you can't do without
180180- // a decoder
181181- let Form(fields, parse_with, _) = form
182182-183183- Form(fields, parse_with, Some(decoder))
184184-}
185185-186186-pub fn decoded(
187187- form: Form(format, output, decoder, HasDecoder),
188188-) -> Result(output, Form(format, output, decoder, HasDecoder)) {
189189- let assert Form(fields, parse_with, Some(decoder)) = form
190190- case parse_with(fields, decoder) {
191191- Ok(output) -> Ok(output)
192192- Error(fields) -> Error(Form(fields, parse_with, Some(decoder)))
193193- }
194194-}
195195-196196-pub fn decode_and_try(
197197- form: Form(format, output, decoder, HasDecoder),
198198- apply fun: fn(output, Form(format, output, decoder, HasDecoder)) ->
199199- Result(c, Form(format, output, decoder, HasDecoder)),
200200-) -> Result(c, Form(format, output, decoder, HasDecoder)) {
201201- decoded(form) |> result.try(fun(_, form))
202202-}
203203-204204-pub fn get_fields(form: Form(format, a, b, has_decoder)) -> List(Field(format)) {
205205- form.fields |> list.reverse
206206-}
207207-208208-pub fn set_field_error(
209209- form: Form(format, output, decoder, has_decoder),
210210- name: String,
211211- error: String,
212212-) -> Form(format, output, decoder, has_decoder) {
213213- let updated =
214214- form.fields
215215- |> list.map(fn(field) {
216216- case field.name == name {
217217- True ->
218218- InvalidField(
219219- name: field.name,
220220- label: field.label,
221221- help_text: field.help_text,
222222- render: field.render,
223223- value: field.value,
224224- error: error,
225225- )
226226- False -> field
227227- }
228228- })
229229- Form(updated, form.parse_with, form.decoder)
230230-}
···11+// https://docs.djangoproject.com/en/5.0/topics/forms/
22+// https://github.com/nakaixo/nakai
33+// date time handling https://hexdocs.pm/birl/index.html
44+55+// TODO
66+// - list fields
77+// - form sets
88+// - csrf token
99+// - required/option
1010+1111+import forma/field.{type Field, Field}
1212+import forma/input.{type Input}
1313+import gleam/list
1414+import gleam/option.{type Option, None, Some}
1515+import gleam/result
1616+1717+pub type HasDecoder
1818+1919+pub type NoDecoder
2020+2121+pub type FieldsWithErrors(format) =
2222+ List(Field(format))
2323+2424+pub opaque type Form(format, output, decoder, has_decoder) {
2525+ Form(
2626+ fields: List(Field(format)),
2727+ parse_with: fn(List(Field(format)), decoder) ->
2828+ Result(output, FieldsWithErrors(format)),
2929+ decoder: Option(decoder),
3030+ )
3131+}
3232+3333+pub fn new() -> Form(format, a, a, NoDecoder) {
3434+ Form([], fn(_, output) { Ok(output) }, None)
3535+}
3636+3737+pub fn add(
3838+ form: Form(
3939+ format,
4040+ fn(decoder_step_input) -> decoder_step_output,
4141+ form_output,
4242+ has_decoder,
4343+ ),
4444+ definition: Input(format, decoder_step_input),
4545+) -> Form(format, decoder_step_output, form_output, has_decoder) {
4646+ let Form(fields, parse_with, decoder) = form
4747+4848+ // create new form with the new field and update the parse
4949+ // function to handle the new details from the type of the
5050+ // field
5151+ Form(
5252+ fields: [definition.field, ..fields],
5353+ parse_with: fn(fields, decoder: form_output) {
5454+ // can do let assert because we know there's at least one field since
5555+ // we just added one
5656+ let assert [field, ..rest] = fields
5757+ case parse_with(rest, decoder), definition.transform(field.value) {
5858+ // the form we've already parsed has no errors and the field
5959+ // we just parsed has no errors. so we can move on to the next
6060+ Ok(next), Ok(value) -> Ok(next(value))
6161+6262+ // the form already has errors even though this one succeeded.
6363+ // so add this to the list and stop anymore parsing
6464+ Error(fields), Ok(_value) -> Error([field, ..fields])
6565+6666+ // form was good so far, but this field errored, so need to
6767+ // mark this field as invalid and return all the fields we've got
6868+ // so far
6969+ Ok(_), Error(error) -> Error([field.set_error(field, error), ..rest])
7070+7171+ // form already has errors and this field errored, so add this field
7272+ // to the list
7373+ Error(fields), Error(error) ->
7474+ Error([field.set_error(field, error), ..fields])
7575+ }
7676+ },
7777+ decoder:,
7878+ )
7979+}
8080+8181+pub fn data(
8282+ form: Form(a, b, format, has_decoder),
8383+ input: List(#(String, String)),
8484+) -> Form(a, b, format, has_decoder) {
8585+ case form {
8686+ Form(fields, parse_with, decoder) -> {
8787+ fields
8888+ // we always prepend fields, so reverse to get correct order
8989+ // TODO I think we're going to make it so order doesn't matter
9090+ |> list.reverse
9191+ |> do_add_input_data(input, [])
9292+ |> Form(parse_with, decoder)
9393+ }
9494+ // FormWithErrors(..) -> form
9595+ }
9696+}
9797+9898+fn do_add_input_data(
9999+ fields: List(Field(format)),
100100+ data: List(#(String, String)),
101101+ acc: List(Field(format)),
102102+) {
103103+ case fields, data {
104104+ // no more fields, we've return all the fields with data we have accumulated
105105+ [], _ -> acc
106106+ // no more data! return all the fields we have left plus the ones we accumulated
107107+ _, [] -> list.append(fields, acc)
108108+ // we have a field and data, and the names match. update field to have data
109109+ [Field(name: field_name, ..) as field, ..fields_rest],
110110+ [#(data_name, value), ..data_rest]
111111+ if field_name == data_name
112112+ ->
113113+ do_add_input_data(fields_rest, data_rest, [
114114+ field.set_value(field, value),
115115+ ..acc
116116+ ])
117117+ // at this point we still have fields and data left, but the first
118118+ // field and first data don't match. so we decide we've got no data
119119+ // for the first field and move on to the next. but we need to add
120120+ // this field without data to the accumulator
121121+ [field, ..fields_rest], _ ->
122122+ do_add_input_data(fields_rest, data, [field, ..acc])
123123+ }
124124+}
125125+126126+pub fn decodes(
127127+ form: Form(format, output, decoder, has_decoder),
128128+ decoder: decoder,
129129+) -> Form(format, output, decoder, HasDecoder) {
130130+ let Form(fields, parse_with, _) = form
131131+ Form(fields, parse_with, Some(decoder))
132132+}
133133+134134+pub fn parse(
135135+ form: Form(format, output, decoder, HasDecoder),
136136+) -> Result(output, Form(format, output, decoder, HasDecoder)) {
137137+ // we've tagged that we have a decoder with out has_decoder phantom type
138138+ // so we can get away with let assert here
139139+ let assert Form(fields, parse_with, Some(decoder)) = form
140140+ case parse_with(fields, decoder) {
141141+ Ok(output) -> Ok(output)
142142+ Error(fields) -> Error(Form(fields, parse_with, Some(decoder)))
143143+ }
144144+}
145145+146146+pub fn parse_and_try(
147147+ form: Form(format, output, decoder, HasDecoder),
148148+ apply fun: fn(output, Form(format, output, decoder, HasDecoder)) ->
149149+ Result(c, Form(format, output, decoder, HasDecoder)),
150150+) -> Result(c, Form(format, output, decoder, HasDecoder)) {
151151+ parse(form) |> result.try(fun(_, form))
152152+}
153153+154154+pub fn get_fields(form: Form(format, a, b, has_decoder)) -> List(Field(format)) {
155155+ form.fields |> list.reverse
156156+}
157157+158158+pub fn set_field_error(
159159+ form: Form(format, output, decoder, has_decoder),
160160+ name: String,
161161+ error: String,
162162+) -> Form(format, output, decoder, has_decoder) {
163163+ let updated =
164164+ form.fields
165165+ |> list.map(fn(field) {
166166+ case field.name == name {
167167+ True -> field.set_error(field, error)
168168+ False -> field
169169+ }
170170+ })
171171+ Form(updated, form.parse_with, form.decoder)
172172+}
173173+174174+pub fn field_update(
175175+ form: Form(format, output, decoder, has_decoder),
176176+ name: String,
177177+ fun: fn(Field(format)) -> Field(format),
178178+) -> Form(format, output, decoder, has_decoder) {
179179+ form.fields
180180+ |> list.map(fn(field) {
181181+ case field.name == name {
182182+ True -> fun(field)
183183+ False -> field
184184+ }
185185+ })
186186+ |> Form(form.parse_with, form.decoder)
187187+}
+114
forma/src/forma/forma_use/forma.gleam
···11+/////// field related functions
22+33+import forma/field.{type Field, Field}
44+import forma/input.{type Input}
55+import gleam/dict
66+import gleam/list
77+import gleam/result
88+99+pub opaque type Form(format, output) {
1010+ Form(
1111+ fields: List(Field(format)),
1212+ parse: fn(List(Field(format))) -> Result(output, List(Field(format))),
1313+ )
1414+}
1515+1616+pub fn with(
1717+ definition: Input(format, input_output),
1818+ fun: fn(input_output) -> Form(format, form_output),
1919+) -> Form(format, form_output) {
2020+ let field = definition.field
2121+ let next = fun(definition.default)
2222+ Form([field, ..next.fields], parse: fn(fields) {
2323+ // pull out the latest version of this field to get latest data
2424+ let assert [field, ..next_fields] = fields
2525+2626+ let input_output = definition.transform(field.value)
2727+2828+ let next_form = fun(input_output |> result.unwrap(definition.default))
2929+ let form_output = next_form.parse(next_fields)
3030+3131+ case form_output, input_output {
3232+ // everything is good! pass along the output
3333+ Ok(_), Ok(_) -> form_output
3434+3535+ // form has errors, but this field was good, so add it to the list
3636+ // of fields as is.
3737+ Error(fields), Ok(_value) -> Error([field, ..fields])
3838+3939+ // form was good so far, but this field errored, so need to
4040+ // mark this field as invalid and return all the fields we've got
4141+ // so far
4242+ Ok(_), Error(error) ->
4343+ field |> field.set_error(error) |> list.prepend(next_fields, _) |> Error
4444+4545+ // form already has errors and this field errored, so add this field
4646+ // to the list of errors
4747+ Error(fields), Error(error) ->
4848+ field |> field.set_error(error) |> list.prepend(fields, _) |> Error
4949+ }
5050+ })
5151+}
5252+5353+pub fn data(
5454+ form: Form(format, output),
5555+ input: List(#(String, String)),
5656+) -> Form(format, output) {
5757+ let data = dict.from_list(input)
5858+ let Form(fields, parse) = form
5959+ fields
6060+ |> list.map(fn(field) {
6161+ case dict.get(data, field.name) {
6262+ Ok(value) ->
6363+ Field(
6464+ name: field.name,
6565+ label: field.label,
6666+ help_text: field.help_text,
6767+ render: field.render,
6868+ value: value,
6969+ )
7070+ Error(_) -> field
7171+ }
7272+ })
7373+ |> Form(parse)
7474+}
7575+7676+pub fn create_form(thing: thing) -> Form(format, thing) {
7777+ Form([], fn(_) { Ok(thing) })
7878+}
7979+8080+pub fn parse(form: Form(format, output)) -> Result(output, Form(format, output)) {
8181+ // we've tagged that we have a decoder with out has_decoder phantom type
8282+ // so we can get away with let assert here
8383+ let Form(fields, parse) = form
8484+ case parse(fields) {
8585+ Ok(output) -> Ok(output)
8686+ Error(fields) -> Error(Form(fields, parse))
8787+ }
8888+}
8989+9090+pub fn parse_and_try(
9191+ form: Form(format, output),
9292+ apply fun: fn(output, Form(format, output)) -> Result(c, Form(format, output)),
9393+) -> Result(c, Form(format, output)) {
9494+ parse(form) |> result.try(fun(_, form))
9595+}
9696+9797+pub fn get_fields(form: Form(format, ouput)) -> List(Field(format)) {
9898+ form.fields
9999+}
100100+101101+pub fn field_update(
102102+ form: Form(format, output),
103103+ name: String,
104104+ fun: fn(Field(format)) -> Field(format),
105105+) -> Form(format, output) {
106106+ form.fields
107107+ |> list.map(fn(field) {
108108+ case field.name == name {
109109+ True -> fun(field)
110110+ False -> field
111111+ }
112112+ })
113113+ |> Form(form.parse)
114114+}