this repo has no description
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

`use` version

this makes for more succinct code where you can’t mess up order of arguments (as easily)

+1026 -552
-230
forma/src/forma.gleam
··· 1 - // https://docs.djangoproject.com/en/5.0/topics/forms/ 2 - // https://github.com/nakaixo/nakai 3 - // date time handling https://hexdocs.pm/birl/index.html 4 - 5 - // TODO 6 - // - list fields 7 - // - form sets 8 - // - csrf token 9 - // - data order doesn't matter 10 - // - required/option 11 - 12 - import gleam/list 13 - import gleam/option.{type Option, None, Some} 14 - import gleam/result 15 - 16 - pub type HasDecoder 17 - 18 - pub type NoDecoder 19 - 20 - pub type FieldsWithErrors(format) = 21 - List(Field(format)) 22 - 23 - pub opaque type Form(format, output, decoder, has_decoder) { 24 - Form( 25 - fields: List(Field(format)), 26 - parse_with: fn(List(Field(format)), decoder) -> 27 - Result(output, FieldsWithErrors(format)), 28 - decoder: Option(decoder), 29 - ) 30 - } 31 - 32 - pub type Field(format) { 33 - Field( 34 - name: String, 35 - label: String, 36 - help_text: String, 37 - render: fn(Field(format)) -> format, 38 - value: String, 39 - ) 40 - InvalidField( 41 - name: String, 42 - label: String, 43 - help_text: String, 44 - render: fn(Field(format)) -> format, 45 - value: String, 46 - error: String, 47 - ) 48 - } 49 - 50 - pub type Input(output, format) { 51 - Input( 52 - render: fn(Field(format)) -> format, 53 - validate: fn(String) -> Result(output, String), 54 - ) 55 - } 56 - 57 - pub fn new() -> Form(format, a, a, NoDecoder) { 58 - Form([], fn(_, output) { Ok(output) }, None) 59 - } 60 - 61 - pub fn add( 62 - form: Form(format, fn(b) -> c, a, has_decoder), 63 - thing: #(Field(format), fn(String) -> Result(b, String)), 64 - ) -> Form(format, c, a, has_decoder) { 65 - let #(field, validate) = thing 66 - case form { 67 - Form(fields, parse_with, decoder) -> { 68 - // create new form with the new field and update the parse 69 - // function to handle the new details from the type of the 70 - // field 71 - Form( 72 - fields: [field, ..fields], 73 - parse_with: fn(fields, decoder: a) { 74 - let assert [field, ..rest] = fields 75 - case parse_with(rest, decoder), validate(field.value) { 76 - // the form we've already parsed has no errors and the field 77 - // we just parsed has no errors. so we can move on to the next 78 - Ok(next), Ok(value) -> Ok(next(value)) 79 - 80 - // the form already has errors even though this one succeeded. 81 - // so add this to the list and stop anymore parsing 82 - Error(fields), Ok(_value) -> Error([field, ..fields]) 83 - 84 - // form was good so far, but this field errored, so need to 85 - // mark this field as invalid and return all the fields we've got 86 - // so far 87 - Ok(_), Error(error) -> 88 - Error([ 89 - InvalidField( 90 - name: field.name, 91 - label: field.label, 92 - help_text: field.help_text, 93 - render: field.render, 94 - value: field.value, 95 - error: error, 96 - ), 97 - ..rest 98 - ]) 99 - 100 - // form already has errors and this field errored, so add this field 101 - // to the list 102 - Error(fields), Error(error) -> 103 - Error([ 104 - InvalidField( 105 - name: field.name, 106 - label: field.label, 107 - help_text: field.help_text, 108 - render: field.render, 109 - value: field.value, 110 - error: error, 111 - ), 112 - ..fields 113 - ]) 114 - } 115 - }, 116 - decoder:, 117 - ) 118 - } 119 - // FormWithErrors(fields) -> FormWithErrors([field, ..fields]) 120 - } 121 - } 122 - 123 - pub fn data( 124 - form: Form(a, b, format, has_decoder), 125 - input: List(#(String, String)), 126 - ) -> Form(a, b, format, has_decoder) { 127 - case form { 128 - Form(fields, parse_with, decoder) -> { 129 - fields 130 - // we always prepend fields, so reverse to get correct order 131 - // TODO I think we're going to make it so order doesn't matter 132 - |> list.reverse 133 - |> do_add_input_data(input, []) 134 - |> Form(parse_with, decoder) 135 - } 136 - // FormWithErrors(..) -> form 137 - } 138 - } 139 - 140 - fn do_add_input_data( 141 - fields: List(Field(format)), 142 - data: List(#(String, String)), 143 - acc: List(Field(format)), 144 - ) { 145 - case fields, data { 146 - // no more fields, we've return all the fields with data we have accumulated 147 - [], _ -> acc 148 - // no more data! return all the fields we have left plus the ones we accumulated 149 - _, [] -> list.append(fields, acc) 150 - // we have a field and data, and the names match. update field to have data 151 - [Field(name: field_name, ..) as field, ..fields_rest], 152 - [#(data_name, value), ..data_rest] 153 - if field_name == data_name 154 - -> 155 - do_add_input_data(fields_rest, data_rest, [ 156 - Field( 157 - name: field_name, 158 - label: field.label, 159 - help_text: field.help_text, 160 - render: field.render, 161 - value: value, 162 - ), 163 - ..acc 164 - ]) 165 - // at this point we still have fields and data left, but the first 166 - // field and first data don't match. so we decide we've got no data 167 - // for the first field and move on to the next. but we need to add 168 - // this field without data to the accumulator 169 - [field, ..fields_rest], _ -> 170 - do_add_input_data(fields_rest, data, [field, ..acc]) 171 - } 172 - } 173 - 174 - pub fn decodes( 175 - form: Form(format, output, decoder, has_decoder), 176 - decoder: decoder, 177 - ) -> Form(format, output, decoder, HasDecoder) { 178 - // can use let assert here because you can't have errors if you 179 - // haven't tried to parse and decode, which you can't do without 180 - // a decoder 181 - let Form(fields, parse_with, _) = form 182 - 183 - Form(fields, parse_with, Some(decoder)) 184 - } 185 - 186 - pub fn decoded( 187 - form: Form(format, output, decoder, HasDecoder), 188 - ) -> Result(output, Form(format, output, decoder, HasDecoder)) { 189 - let assert Form(fields, parse_with, Some(decoder)) = form 190 - case parse_with(fields, decoder) { 191 - Ok(output) -> Ok(output) 192 - Error(fields) -> Error(Form(fields, parse_with, Some(decoder))) 193 - } 194 - } 195 - 196 - pub fn decode_and_try( 197 - form: Form(format, output, decoder, HasDecoder), 198 - apply fun: fn(output, Form(format, output, decoder, HasDecoder)) -> 199 - Result(c, Form(format, output, decoder, HasDecoder)), 200 - ) -> Result(c, Form(format, output, decoder, HasDecoder)) { 201 - decoded(form) |> result.try(fun(_, form)) 202 - } 203 - 204 - pub fn get_fields(form: Form(format, a, b, has_decoder)) -> List(Field(format)) { 205 - form.fields |> list.reverse 206 - } 207 - 208 - pub fn set_field_error( 209 - form: Form(format, output, decoder, has_decoder), 210 - name: String, 211 - error: String, 212 - ) -> Form(format, output, decoder, has_decoder) { 213 - let updated = 214 - form.fields 215 - |> list.map(fn(field) { 216 - case field.name == name { 217 - True -> 218 - InvalidField( 219 - name: field.name, 220 - label: field.label, 221 - help_text: field.help_text, 222 - render: field.render, 223 - value: field.value, 224 - error: error, 225 - ) 226 - False -> field 227 - } 228 - }) 229 - Form(updated, form.parse_with, form.decoder) 230 - }
+65 -39
forma/src/forma/field.gleam
··· 1 - import forma.{type Field, type Input, Field, Input} 2 - import forma/validation 3 - import forma/widget 4 - import justin 5 - 6 - pub fn text_field() -> Input(String, String) { 7 - Input(widget.text_input, validation.string) 1 + pub type Field(format) { 2 + Field( 3 + name: String, 4 + label: String, 5 + help_text: String, 6 + render: fn(Field(format)) -> format, 7 + value: String, 8 + ) 9 + InvalidField( 10 + name: String, 11 + label: String, 12 + help_text: String, 13 + render: fn(Field(format)) -> format, 14 + value: String, 15 + error: String, 16 + ) 8 17 } 9 18 10 - pub fn email_field() -> Input(String, String) { 11 - Input(widget.text_input, validation.email) 19 + pub fn empty_field(render: fn(Field(format)) -> format) -> Field(format) { 20 + Field("", "", "", render, "") 12 21 } 13 22 14 - pub fn integer_field() -> Input(Int, String) { 15 - Input(widget.text_input, validation.trim |> validation.and(validation.int)) 23 + pub fn set_name(field: Field(format), name: String) -> Field(format) { 24 + case field { 25 + Field(_, label, help_text, render, value) -> 26 + Field(name, label, help_text, render, value) 27 + InvalidField(_, label, help_text, render, value, error) -> 28 + InvalidField(name, label, help_text, render, value, error) 29 + } 16 30 } 17 31 18 - pub fn new( 19 - name: String, 20 - input: Input(output, format), 21 - ) -> #(Field(format), fn(String) -> Result(output, String)) { 22 - #( 23 - Field(name, justin.sentence_case(name), "", input.render, ""), 24 - input.validate, 25 - ) 32 + pub fn set_label(field: Field(format), label: String) -> Field(format) { 33 + case field { 34 + Field(name, _, help_text, render, value) -> 35 + Field(name, label, help_text, render, value) 36 + InvalidField(name, _, help_text, render, value, error) -> 37 + InvalidField(name, label, help_text, render, value, error) 38 + } 26 39 } 27 40 28 - pub fn help_text( 29 - thing: #(Field(format), fn(String) -> Result(b, String)), 30 - help_text: String, 31 - ) -> #(Field(format), fn(String) -> Result(b, String)) { 32 - let #(field, validate) = thing 33 - 34 - let field = 35 - Field(field.name, field.label, help_text, field.render, field.value) 41 + pub fn set_help_text(field: Field(format), help_text: String) -> Field(format) { 42 + case field { 43 + Field(name, label, _, render, value) -> 44 + Field(name, label, help_text, render, value) 45 + InvalidField(name, label, _, render, value, error) -> 46 + InvalidField(name, label, help_text, render, value, error) 47 + } 48 + } 36 49 37 - #(field, validate) 50 + pub fn set_render( 51 + field: Field(format), 52 + render: fn(Field(format)) -> format, 53 + ) -> Field(format) { 54 + case field { 55 + Field(name, label, help_text, _, value) -> 56 + Field(name, label, help_text, render, value) 57 + InvalidField(name, label, help_text, _, value, error) -> 58 + InvalidField(name, label, help_text, render, value, error) 59 + } 38 60 } 39 61 40 - pub fn validate( 41 - thing: #(Field(format), fn(String) -> Result(b, String)), 42 - next: fn(b) -> Result(c, String), 43 - ) -> #(Field(format), fn(String) -> Result(c, String)) { 44 - let #(field, previous) = thing 62 + pub fn set_value(field: Field(format), value: String) -> Field(format) { 63 + case field { 64 + Field(name, label, help_text, render, _) -> 65 + Field(name, label, help_text, render, value) 66 + InvalidField(name, label, help_text, render, _, error) -> 67 + InvalidField(name, label, help_text, render, value, error) 68 + } 69 + } 45 70 46 - #(field, fn(str) { 47 - case previous(str) { 48 - Ok(value) -> next(value) 49 - Error(error) -> Error(error) 50 - } 51 - }) 71 + pub fn set_error(field: Field(format), error: String) -> Field(format) { 72 + case field { 73 + Field(name, label, help_text, render, value) -> 74 + InvalidField(name, label, help_text, render, value, error) 75 + InvalidField(name, label, help_text, render, value, _) -> 76 + InvalidField(name, label, help_text, render, value, error) 77 + } 52 78 }
+187
forma/src/forma/forma_pipes/forma.gleam
··· 1 + // https://docs.djangoproject.com/en/5.0/topics/forms/ 2 + // https://github.com/nakaixo/nakai 3 + // date time handling https://hexdocs.pm/birl/index.html 4 + 5 + // TODO 6 + // - list fields 7 + // - form sets 8 + // - csrf token 9 + // - required/option 10 + 11 + import forma/field.{type Field, Field} 12 + import forma/input.{type Input} 13 + import gleam/list 14 + import gleam/option.{type Option, None, Some} 15 + import gleam/result 16 + 17 + pub type HasDecoder 18 + 19 + pub type NoDecoder 20 + 21 + pub type FieldsWithErrors(format) = 22 + List(Field(format)) 23 + 24 + pub opaque type Form(format, output, decoder, has_decoder) { 25 + Form( 26 + fields: List(Field(format)), 27 + parse_with: fn(List(Field(format)), decoder) -> 28 + Result(output, FieldsWithErrors(format)), 29 + decoder: Option(decoder), 30 + ) 31 + } 32 + 33 + pub fn new() -> Form(format, a, a, NoDecoder) { 34 + Form([], fn(_, output) { Ok(output) }, None) 35 + } 36 + 37 + pub fn add( 38 + form: Form( 39 + format, 40 + fn(decoder_step_input) -> decoder_step_output, 41 + form_output, 42 + has_decoder, 43 + ), 44 + definition: Input(format, decoder_step_input), 45 + ) -> Form(format, decoder_step_output, form_output, has_decoder) { 46 + let Form(fields, parse_with, decoder) = form 47 + 48 + // create new form with the new field and update the parse 49 + // function to handle the new details from the type of the 50 + // field 51 + Form( 52 + fields: [definition.field, ..fields], 53 + parse_with: fn(fields, decoder: form_output) { 54 + // can do let assert because we know there's at least one field since 55 + // we just added one 56 + let assert [field, ..rest] = fields 57 + case parse_with(rest, decoder), definition.transform(field.value) { 58 + // the form we've already parsed has no errors and the field 59 + // we just parsed has no errors. so we can move on to the next 60 + Ok(next), Ok(value) -> Ok(next(value)) 61 + 62 + // the form already has errors even though this one succeeded. 63 + // so add this to the list and stop anymore parsing 64 + Error(fields), Ok(_value) -> Error([field, ..fields]) 65 + 66 + // form was good so far, but this field errored, so need to 67 + // mark this field as invalid and return all the fields we've got 68 + // so far 69 + Ok(_), Error(error) -> Error([field.set_error(field, error), ..rest]) 70 + 71 + // form already has errors and this field errored, so add this field 72 + // to the list 73 + Error(fields), Error(error) -> 74 + Error([field.set_error(field, error), ..fields]) 75 + } 76 + }, 77 + decoder:, 78 + ) 79 + } 80 + 81 + pub fn data( 82 + form: Form(a, b, format, has_decoder), 83 + input: List(#(String, String)), 84 + ) -> Form(a, b, format, has_decoder) { 85 + case form { 86 + Form(fields, parse_with, decoder) -> { 87 + fields 88 + // we always prepend fields, so reverse to get correct order 89 + // TODO I think we're going to make it so order doesn't matter 90 + |> list.reverse 91 + |> do_add_input_data(input, []) 92 + |> Form(parse_with, decoder) 93 + } 94 + // FormWithErrors(..) -> form 95 + } 96 + } 97 + 98 + fn do_add_input_data( 99 + fields: List(Field(format)), 100 + data: List(#(String, String)), 101 + acc: List(Field(format)), 102 + ) { 103 + case fields, data { 104 + // no more fields, we've return all the fields with data we have accumulated 105 + [], _ -> acc 106 + // no more data! return all the fields we have left plus the ones we accumulated 107 + _, [] -> list.append(fields, acc) 108 + // we have a field and data, and the names match. update field to have data 109 + [Field(name: field_name, ..) as field, ..fields_rest], 110 + [#(data_name, value), ..data_rest] 111 + if field_name == data_name 112 + -> 113 + do_add_input_data(fields_rest, data_rest, [ 114 + field.set_value(field, value), 115 + ..acc 116 + ]) 117 + // at this point we still have fields and data left, but the first 118 + // field and first data don't match. so we decide we've got no data 119 + // for the first field and move on to the next. but we need to add 120 + // this field without data to the accumulator 121 + [field, ..fields_rest], _ -> 122 + do_add_input_data(fields_rest, data, [field, ..acc]) 123 + } 124 + } 125 + 126 + pub fn decodes( 127 + form: Form(format, output, decoder, has_decoder), 128 + decoder: decoder, 129 + ) -> Form(format, output, decoder, HasDecoder) { 130 + let Form(fields, parse_with, _) = form 131 + Form(fields, parse_with, Some(decoder)) 132 + } 133 + 134 + pub fn parse( 135 + form: Form(format, output, decoder, HasDecoder), 136 + ) -> Result(output, Form(format, output, decoder, HasDecoder)) { 137 + // we've tagged that we have a decoder with out has_decoder phantom type 138 + // so we can get away with let assert here 139 + let assert Form(fields, parse_with, Some(decoder)) = form 140 + case parse_with(fields, decoder) { 141 + Ok(output) -> Ok(output) 142 + Error(fields) -> Error(Form(fields, parse_with, Some(decoder))) 143 + } 144 + } 145 + 146 + pub fn parse_and_try( 147 + form: Form(format, output, decoder, HasDecoder), 148 + apply fun: fn(output, Form(format, output, decoder, HasDecoder)) -> 149 + Result(c, Form(format, output, decoder, HasDecoder)), 150 + ) -> Result(c, Form(format, output, decoder, HasDecoder)) { 151 + parse(form) |> result.try(fun(_, form)) 152 + } 153 + 154 + pub fn get_fields(form: Form(format, a, b, has_decoder)) -> List(Field(format)) { 155 + form.fields |> list.reverse 156 + } 157 + 158 + pub fn set_field_error( 159 + form: Form(format, output, decoder, has_decoder), 160 + name: String, 161 + error: String, 162 + ) -> Form(format, output, decoder, has_decoder) { 163 + let updated = 164 + form.fields 165 + |> list.map(fn(field) { 166 + case field.name == name { 167 + True -> field.set_error(field, error) 168 + False -> field 169 + } 170 + }) 171 + Form(updated, form.parse_with, form.decoder) 172 + } 173 + 174 + pub fn field_update( 175 + form: Form(format, output, decoder, has_decoder), 176 + name: String, 177 + fun: fn(Field(format)) -> Field(format), 178 + ) -> Form(format, output, decoder, has_decoder) { 179 + form.fields 180 + |> list.map(fn(field) { 181 + case field.name == name { 182 + True -> fun(field) 183 + False -> field 184 + } 185 + }) 186 + |> Form(form.parse_with, form.decoder) 187 + }
+114
forma/src/forma/forma_use/forma.gleam
··· 1 + /////// field related functions 2 + 3 + import forma/field.{type Field, Field} 4 + import forma/input.{type Input} 5 + import gleam/dict 6 + import gleam/list 7 + import gleam/result 8 + 9 + pub opaque type Form(format, output) { 10 + Form( 11 + fields: List(Field(format)), 12 + parse: fn(List(Field(format))) -> Result(output, List(Field(format))), 13 + ) 14 + } 15 + 16 + pub fn with( 17 + definition: Input(format, input_output), 18 + fun: fn(input_output) -> Form(format, form_output), 19 + ) -> Form(format, form_output) { 20 + let field = definition.field 21 + let next = fun(definition.default) 22 + Form([field, ..next.fields], parse: fn(fields) { 23 + // pull out the latest version of this field to get latest data 24 + let assert [field, ..next_fields] = fields 25 + 26 + let input_output = definition.transform(field.value) 27 + 28 + let next_form = fun(input_output |> result.unwrap(definition.default)) 29 + let form_output = next_form.parse(next_fields) 30 + 31 + case form_output, input_output { 32 + // everything is good! pass along the output 33 + Ok(_), Ok(_) -> form_output 34 + 35 + // form has errors, but this field was good, so add it to the list 36 + // of fields as is. 37 + Error(fields), Ok(_value) -> Error([field, ..fields]) 38 + 39 + // form was good so far, but this field errored, so need to 40 + // mark this field as invalid and return all the fields we've got 41 + // so far 42 + Ok(_), Error(error) -> 43 + field |> field.set_error(error) |> list.prepend(next_fields, _) |> Error 44 + 45 + // form already has errors and this field errored, so add this field 46 + // to the list of errors 47 + Error(fields), Error(error) -> 48 + field |> field.set_error(error) |> list.prepend(fields, _) |> Error 49 + } 50 + }) 51 + } 52 + 53 + pub fn data( 54 + form: Form(format, output), 55 + input: List(#(String, String)), 56 + ) -> Form(format, output) { 57 + let data = dict.from_list(input) 58 + let Form(fields, parse) = form 59 + fields 60 + |> list.map(fn(field) { 61 + case dict.get(data, field.name) { 62 + Ok(value) -> 63 + Field( 64 + name: field.name, 65 + label: field.label, 66 + help_text: field.help_text, 67 + render: field.render, 68 + value: value, 69 + ) 70 + Error(_) -> field 71 + } 72 + }) 73 + |> Form(parse) 74 + } 75 + 76 + pub fn create_form(thing: thing) -> Form(format, thing) { 77 + Form([], fn(_) { Ok(thing) }) 78 + } 79 + 80 + pub fn parse(form: Form(format, output)) -> Result(output, Form(format, output)) { 81 + // we've tagged that we have a decoder with out has_decoder phantom type 82 + // so we can get away with let assert here 83 + let Form(fields, parse) = form 84 + case parse(fields) { 85 + Ok(output) -> Ok(output) 86 + Error(fields) -> Error(Form(fields, parse)) 87 + } 88 + } 89 + 90 + pub fn parse_and_try( 91 + form: Form(format, output), 92 + apply fun: fn(output, Form(format, output)) -> Result(c, Form(format, output)), 93 + ) -> Result(c, Form(format, output)) { 94 + parse(form) |> result.try(fun(_, form)) 95 + } 96 + 97 + pub fn get_fields(form: Form(format, ouput)) -> List(Field(format)) { 98 + form.fields 99 + } 100 + 101 + pub fn field_update( 102 + form: Form(format, output), 103 + name: String, 104 + fun: fn(Field(format)) -> Field(format), 105 + ) -> Form(format, output) { 106 + form.fields 107 + |> list.map(fn(field) { 108 + case field.name == name { 109 + True -> fun(field) 110 + False -> field 111 + } 112 + }) 113 + |> Form(form.parse) 114 + }
+18
forma/src/forma/generator/string_input.gleam
··· 1 + import forma/generator/string_widget 2 + import forma/input 3 + 4 + pub fn text_input() { 5 + input.text_input(string_widget.text_widget) 6 + } 7 + 8 + pub fn email_input() { 9 + input.email_input(string_widget.text_widget) 10 + } 11 + 12 + pub fn integer_input() { 13 + input.integer_input(string_widget.text_widget) 14 + } 15 + 16 + pub fn number_input() { 17 + input.number_input(string_widget.text_widget) 18 + }
+94
forma/src/forma/input.gleam
··· 1 + import forma/field.{type Field, Field} 2 + import forma/validation 3 + import justin 4 + 5 + pub fn text_input(widget: fn(Field(format)) -> format) -> Input(format, String) { 6 + Input(field.empty_field(widget), "", validation.string) 7 + } 8 + 9 + pub fn email_input(widget: fn(Field(format)) -> format) -> Input(format, String) { 10 + Input(field.empty_field(widget), "", validation.email) 11 + } 12 + 13 + pub fn integer_input(widget: fn(Field(format)) -> format) -> Input(format, Int) { 14 + let transform = validation.trim |> validation.and(validation.int) 15 + Input(field.empty_field(widget), 0, transform) 16 + } 17 + 18 + pub fn number_input(widget: fn(Field(format)) -> format) -> Input(format, Float) { 19 + let transform = validation.trim |> validation.and(validation.number) 20 + Input(field.empty_field(widget), 0.0, transform) 21 + } 22 + 23 + pub type Input(format, output) { 24 + Input( 25 + field: Field(format), 26 + default: output, 27 + transform: fn(String) -> Result(output, String), 28 + ) 29 + } 30 + 31 + pub fn input( 32 + name: String, 33 + input: Input(format, output), 34 + ) -> Input(format, output) { 35 + Input( 36 + Field(name, justin.sentence_case(name), "", input.field.render, ""), 37 + input.default, 38 + input.transform, 39 + ) 40 + } 41 + 42 + pub fn full( 43 + name: String, 44 + label: String, 45 + help_text: String, 46 + input: Input(format, output), 47 + ) -> Input(format, output) { 48 + Input( 49 + Field(name, label, help_text, input.field.render, ""), 50 + input.default, 51 + input.transform, 52 + ) 53 + } 54 + 55 + pub fn name(input: Input(format, b), name: String) -> Input(format, b) { 56 + Input(..input, field: field.set_name(input.field, name)) 57 + } 58 + 59 + pub fn label(input: Input(format, b), label: String) -> Input(format, b) { 60 + Input(..input, field: field.set_label(input.field, label)) 61 + } 62 + 63 + pub fn help_text(input: Input(format, b), help_text: String) -> Input(format, b) { 64 + Input(..input, field: field.set_help_text(input.field, help_text)) 65 + } 66 + 67 + pub fn validate( 68 + input: Input(format, b), 69 + next: fn(b) -> Result(b, String), 70 + ) -> Input(format, b) { 71 + let Input(field, default, previous_transform) = input 72 + 73 + Input(field, default, fn(str) { 74 + case previous_transform(str) { 75 + Ok(value) -> next(value) 76 + Error(error) -> Error(error) 77 + } 78 + }) 79 + } 80 + 81 + pub fn transform( 82 + input: Input(format, b), 83 + next: fn(b) -> Result(c, String), 84 + default: c, 85 + ) -> Input(format, c) { 86 + let Input(field, _, previous_transform) = input 87 + 88 + Input(field, default, fn(str) { 89 + case previous_transform(str) { 90 + Ok(value) -> next(value) 91 + Error(error) -> Error(error) 92 + } 93 + }) 94 + }
+7 -1
forma/src/forma/validation.gleam
··· 1 + import gleam/float 1 2 import gleam/int 2 3 import gleam/result 3 4 import gleam/string ··· 29 30 } 30 31 31 32 pub fn int(str: String) -> Result(Int, String) { 32 - str |> int.parse |> result.replace_error("not an integer") 33 + str |> int.parse |> result.replace_error("Must be a whole number") 34 + } 35 + 36 + pub fn number(str: String) -> Result(Float, String) { 37 + // TODO accept whole numbers too 38 + str |> float.parse |> result.replace_error("Must be a number") 33 39 } 34 40 35 41 pub fn and(
+5 -5
forma/src/forma/widget.gleam forma/src/forma/generator/string_widget.gleam
··· 1 - import forma 1 + import forma/field.{type Field} 2 2 3 - pub fn checkbox_input(_f, _env) -> String { 3 + pub fn checkbox_widget(_f, _env) -> String { 4 4 "<input type=\"checkbox\">" 5 5 } 6 6 7 - pub fn password_input(_f, _env) -> String { 7 + pub fn password_widget(_f, _env) -> String { 8 8 "<input type=\"password\">" 9 9 } 10 10 11 - pub fn text_input(f: forma.Field(String)) -> String { 11 + pub fn text_widget(f: Field(String)) -> String { 12 12 let placeholder = "" 13 13 14 14 "<input name=\"" ··· 23 23 // https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/ 24 24 // https://til.simonwillison.net/css/resizing-textarea 25 25 26 - pub fn textarea_input(_f, _env) -> String { 26 + pub fn textarea_widget(_f, _env) -> String { 27 27 "<textarea></textarea>" 28 28 }
+287
forma/test/forma/forma_pipes/forma_test.gleam
··· 1 + import forma/field 2 + import forma/forma_pipes/forma 3 + import forma/generator/string_input 4 + import forma/input.{input} 5 + import gleam/list 6 + import gleeunit 7 + import gleeunit/should 8 + 9 + pub fn main() { 10 + gleeunit.main() 11 + } 12 + 13 + pub fn empty_form_test() { 14 + forma.new() 15 + |> forma.get_fields 16 + |> list.length 17 + |> should.equal(0) 18 + } 19 + 20 + pub fn parse_empty_form_test() { 21 + forma.new() 22 + |> forma.data([]) 23 + |> forma.decodes(1) 24 + |> forma.parse 25 + |> should.equal(Ok(1)) 26 + 27 + forma.new() 28 + |> forma.data([]) 29 + |> forma.decodes("Hello") 30 + |> forma.parse 31 + |> should.equal(Ok("Hello")) 32 + } 33 + 34 + pub fn parse_single_field_form_test() { 35 + forma.new() 36 + |> forma.add(input("first", string_input.text_input())) 37 + |> forma.data([#("first", "world")]) 38 + |> forma.decodes(fn(str) { "hello " <> str }) 39 + |> forma.parse 40 + |> should.equal(Ok("hello world")) 41 + 42 + forma.new() 43 + |> forma.add(input("first", string_input.text_input())) 44 + |> forma.data([]) 45 + |> forma.decodes(fn(str) { "hello " <> str }) 46 + |> forma.parse 47 + |> should.equal(Ok("hello ")) 48 + } 49 + 50 + pub fn parse_double_field_form_test() { 51 + forma.new() 52 + |> forma.add(input("first", string_input.text_input())) 53 + |> forma.add(input("second", string_input.text_input())) 54 + |> forma.data([#("first", "hello"), #("second", "world")]) 55 + |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 56 + |> forma.parse 57 + |> should.equal(Ok("hello world")) 58 + 59 + forma.new() 60 + |> forma.add(input("first", string_input.text_input())) 61 + |> forma.add(input("second", string_input.text_input())) 62 + |> forma.data([#("first", "hello")]) 63 + |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 64 + |> forma.parse 65 + |> should.equal(Ok("hello ")) 66 + } 67 + 68 + pub fn parse_double_field_form_extra_data_test() { 69 + forma.new() 70 + |> forma.add(input("first", string_input.text_input())) 71 + |> forma.add(input("second", string_input.text_input())) 72 + |> forma.data([#("first", "1"), #("second", "2")]) 73 + |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 74 + |> forma.parse 75 + |> should.equal(Ok("1 2")) 76 + 77 + forma.new() 78 + |> forma.add(input("first", string_input.text_input())) 79 + |> forma.add(input("second", string_input.text_input())) 80 + |> forma.data([#("first", "1"), #("second", "2"), #("second", "3")]) 81 + |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 82 + |> forma.parse 83 + |> should.equal(Ok("1 2")) 84 + } 85 + 86 + pub fn parse_double_field_form_missing_data_test() { 87 + forma.new() 88 + |> forma.add(input("first", string_input.text_input())) 89 + |> forma.add(input("second", string_input.text_input())) 90 + |> forma.data([#("second", "1")]) 91 + |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 92 + |> forma.parse 93 + |> should.equal(Ok(" 1")) 94 + 95 + forma.new() 96 + |> forma.add(input("first", string_input.text_input())) 97 + |> forma.add(input("second", string_input.text_input())) 98 + |> forma.data([#("first", "1"), #("first", "2")]) 99 + |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 100 + |> forma.parse 101 + |> should.equal(Ok("1 ")) 102 + } 103 + 104 + pub fn integer_field_test() { 105 + forma.new() 106 + |> forma.add(input("first", string_input.integer_input())) 107 + |> forma.data([#("first", " 1 ")]) 108 + |> forma.decodes(fn(i) { i }) 109 + |> forma.parse 110 + |> should.equal(Ok(1)) 111 + } 112 + 113 + pub fn can_decodes_in_any_order_test() { 114 + forma.new() 115 + |> forma.decodes(fn(str) { "hello " <> str }) 116 + |> forma.add(input("first", string_input.text_input())) 117 + |> forma.data([#("first", "world")]) 118 + |> forma.parse 119 + |> should.equal(Ok("hello world")) 120 + 121 + forma.new() 122 + |> forma.add(input("first", string_input.text_input())) 123 + |> forma.data([#("first", "world")]) 124 + |> forma.decodes(fn(str) { "hello " <> str }) 125 + |> forma.parse 126 + |> should.equal(Ok("hello world")) 127 + } 128 + 129 + pub fn parse_single_field_form_with_error_test() { 130 + let assert Error(f) = 131 + forma.new() 132 + |> forma.add(input("first", string_input.integer_input())) 133 + |> forma.data([#("first", "world")]) 134 + |> forma.decodes(fn(_) { 1 }) 135 + |> forma.parse 136 + 137 + let assert [field] = forma.get_fields(f) 138 + field |> should_be_field_with_error("Must be a whole number") 139 + } 140 + 141 + pub fn parse_double_field_form_with_error_test() { 142 + let form = 143 + forma.new() 144 + |> forma.add(input("a", string_input.integer_input())) 145 + |> forma.add(input("b", string_input.integer_input())) 146 + |> forma.decodes(fn(_) { fn(_) { 1 } }) 147 + 148 + let assert Error(f) = 149 + form 150 + |> forma.data([#("a", "not a number"), #("b", "2")]) 151 + |> forma.parse 152 + 153 + let assert [fielda, fieldb] = forma.get_fields(f) 154 + fielda |> should_be_field_with_error("Must be a whole number") 155 + fieldb |> should_be_field_no_error 156 + 157 + let assert Error(f) = 158 + form 159 + |> forma.data([#("a", "1"), #("b", "string")]) 160 + |> forma.parse 161 + 162 + let assert [fielda, fieldb] = forma.get_fields(f) 163 + fielda |> should_be_field_no_error 164 + fieldb |> should_be_field_with_error("Must be a whole number") 165 + 166 + let assert Error(f) = 167 + form 168 + |> forma.data([#("a", "string"), #("b", "string")]) 169 + |> forma.parse 170 + 171 + let assert [fielda, fieldb] = forma.get_fields(f) 172 + fielda |> should_be_field_with_error("Must be a whole number") 173 + fieldb |> should_be_field_with_error("Must be a whole number") 174 + } 175 + 176 + pub fn parse_triple_field_form_with_error_test() { 177 + let form = 178 + forma.new() 179 + |> forma.add(input("a", string_input.integer_input())) 180 + |> forma.add(input("b", string_input.integer_input())) 181 + |> forma.add(input("c", string_input.integer_input())) 182 + |> forma.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 183 + 184 + let assert Error(f) = 185 + form 186 + |> forma.data([#("a", "1"), #("b", "2"), #("c", "string")]) 187 + |> forma.parse 188 + let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 189 + fielda |> should_be_field_no_error 190 + fieldb |> should_be_field_no_error 191 + fieldc |> should_be_field_with_error("Must be a whole number") 192 + 193 + let assert Error(f) = 194 + form 195 + |> forma.data([#("a", "1"), #("b", "string"), #("c", "string")]) 196 + |> forma.parse 197 + let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 198 + fielda |> should_be_field_no_error 199 + fieldb |> should_be_field_with_error("Must be a whole number") 200 + fieldc |> should_be_field_with_error("Must be a whole number") 201 + 202 + let assert Error(f) = 203 + form 204 + |> forma.data([#("a", "1"), #("b", "string"), #("c", "3")]) 205 + |> forma.parse 206 + let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 207 + fielda |> should_be_field_no_error 208 + fieldb |> should_be_field_with_error("Must be a whole number") 209 + fieldc |> should_be_field_no_error 210 + 211 + let assert Error(f) = 212 + form 213 + |> forma.data([#("a", "string"), #("b", "string"), #("c", "3")]) 214 + |> forma.parse 215 + let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 216 + fielda |> should_be_field_with_error("Must be a whole number") 217 + fieldb |> should_be_field_with_error("Must be a whole number") 218 + fieldc |> should_be_field_no_error 219 + 220 + let assert Error(f) = 221 + form 222 + |> forma.data([#("a", "string"), #("b", "2"), #("c", "3")]) 223 + |> forma.parse 224 + let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 225 + fielda |> should_be_field_with_error("Must be a whole number") 226 + fieldb |> should_be_field_no_error 227 + fieldc |> should_be_field_no_error 228 + } 229 + 230 + fn should_be_field_no_error(field: field.Field(String)) { 231 + should.equal( 232 + field, 233 + field.Field( 234 + name: field.name, 235 + label: field.label, 236 + help_text: field.help_text, 237 + value: field.value, 238 + render: field.render, 239 + ), 240 + ) 241 + } 242 + 243 + fn should_be_field_with_error(field: field.Field(String), str: String) { 244 + should.equal( 245 + field, 246 + field.InvalidField( 247 + name: field.name, 248 + label: field.label, 249 + help_text: field.help_text, 250 + value: field.value, 251 + render: field.render, 252 + error: str, 253 + ), 254 + ) 255 + } 256 + 257 + pub fn parse_and_try_test() { 258 + let f = 259 + forma.new() 260 + |> forma.add(input("a", string_input.integer_input())) 261 + |> forma.add(input("b", string_input.integer_input())) 262 + |> forma.add(input("c", string_input.integer_input())) 263 + |> forma.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 264 + |> forma.data([#("a", "1"), #("b", "2"), #("c", "3")]) 265 + 266 + // can succeed 267 + forma.parse_and_try(f, fn(_, _) { Ok(3) }) 268 + |> should.equal(Ok(3)) 269 + 270 + // can change type 271 + forma.parse_and_try(f, fn(_, _) { Ok("it worked") }) 272 + |> should.equal(Ok("it worked")) 273 + 274 + // can error 275 + forma.parse_and_try(f, fn(_, form) { Error(form) }) 276 + |> should.equal(Error(f)) 277 + 278 + // can change field 279 + let assert Error(form) = 280 + forma.parse_and_try(f, fn(_, form) { 281 + Error(forma.set_field_error(form, "a", "woops")) 282 + }) 283 + let assert [fielda, fieldb, fieldc] = forma.get_fields(form) 284 + fielda |> should_be_field_with_error("woops") 285 + fieldb |> should_be_field_no_error 286 + fieldc |> should_be_field_no_error 287 + }
+247
forma/test/forma/forma_use/forma_test.gleam
··· 1 + import forma/field 2 + import forma/forma_use/forma 3 + import forma/generator/string_input 4 + import forma/input.{input} 5 + import forma/validation 6 + import gleeunit 7 + import gleeunit/should 8 + 9 + fn should_be_field_no_error(field: field.Field(String)) { 10 + should.equal( 11 + field, 12 + field.Field( 13 + name: field.name, 14 + label: field.label, 15 + help_text: field.help_text, 16 + value: field.value, 17 + render: field.render, 18 + ), 19 + ) 20 + } 21 + 22 + fn should_be_field_with_error(field: field.Field(String), str: String) { 23 + should.equal( 24 + field, 25 + field.InvalidField( 26 + name: field.name, 27 + label: field.label, 28 + help_text: field.help_text, 29 + value: field.value, 30 + render: field.render, 31 + error: str, 32 + ), 33 + ) 34 + } 35 + 36 + fn get_form_from_error_result( 37 + result: Result(output, forma.Form(format, output)), 38 + ) -> forma.Form(format, output) { 39 + let assert Error(form) = result 40 + form 41 + } 42 + 43 + pub fn main() { 44 + gleeunit.main() 45 + } 46 + 47 + fn empty_form(val) { 48 + forma.create_form(val) 49 + } 50 + 51 + fn one_field_form() { 52 + use a <- forma.with(input("a", string_input.text_input())) 53 + forma.create_form("hello " <> a) 54 + } 55 + 56 + fn two_field_form() { 57 + { 58 + use a <- forma.with(input("a", string_input.text_input())) 59 + use b <- forma.with(input("b", string_input.text_input())) 60 + 61 + forma.create_form(#(a, b)) 62 + } 63 + } 64 + 65 + fn three_field_form() { 66 + use a <- forma.with( 67 + input("a", string_input.text_input()) 68 + |> input.validate(validation.must_be_longer_than(3)), 69 + ) 70 + use b <- forma.with(input("b", string_input.integer_input())) 71 + use c <- forma.with(input.full("c", "C", "help!", string_input.number_input())) 72 + 73 + forma.create_form(#(a, b, c)) 74 + } 75 + 76 + pub fn empty_form_test() { 77 + empty_form(1) 78 + |> forma.get_fields 79 + |> should.equal([]) 80 + } 81 + 82 + pub fn parse_empty_form_test() { 83 + empty_form(1) 84 + |> forma.data([]) 85 + |> forma.parse 86 + |> should.equal(Ok(1)) 87 + 88 + empty_form("hello") 89 + |> forma.data([]) 90 + |> forma.parse 91 + |> should.equal(Ok("hello")) 92 + } 93 + 94 + pub fn parse_single_field_form_test() { 95 + one_field_form() 96 + |> forma.data([#("a", "world")]) 97 + |> forma.parse 98 + |> should.equal(Ok("hello world")) 99 + 100 + one_field_form() 101 + |> forma.data([]) 102 + |> forma.parse 103 + |> should.equal(Ok("hello ")) 104 + 105 + one_field_form() 106 + |> forma.data([#("a", "ignored"), #("a", "world")]) 107 + |> forma.parse 108 + |> should.equal(Ok("hello world")) 109 + } 110 + 111 + pub fn parse_double_field_form_test() { 112 + two_field_form() 113 + |> forma.data([#("a", "hello"), #("b", "world")]) 114 + |> forma.parse 115 + |> should.equal(Ok(#("hello", "world"))) 116 + 117 + // missing second 118 + two_field_form() 119 + |> forma.data([#("a", "hello")]) 120 + |> forma.parse 121 + |> should.equal(Ok(#("hello", ""))) 122 + 123 + // missing first 124 + two_field_form() 125 + |> forma.data([#("b", "world")]) 126 + |> forma.parse 127 + |> should.equal(Ok(#("", "world"))) 128 + 129 + // missing both 130 + two_field_form() 131 + |> forma.data([]) 132 + |> forma.parse 133 + |> should.equal(Ok(#("", ""))) 134 + 135 + // wrong order 136 + two_field_form() 137 + |> forma.data([#("b", "world"), #("a", "hello")]) 138 + |> forma.parse 139 + |> should.equal(Ok(#("hello", "world"))) 140 + 141 + // takes second 142 + two_field_form() 143 + |> forma.data([ 144 + #("a", "ignored"), 145 + #("b", "ignored"), 146 + #("b", "world"), 147 + #("a", "hello"), 148 + ]) 149 + |> forma.parse 150 + |> should.equal(Ok(#("hello", "world"))) 151 + } 152 + 153 + pub fn parse_single_field_form_with_error_test() { 154 + let assert Error(f) = 155 + { 156 + use a <- forma.with(input("a", string_input.integer_input())) 157 + forma.create_form(a) 158 + } 159 + |> forma.data([#("first", "world")]) 160 + |> forma.parse 161 + 162 + let assert [field] = forma.get_fields(f) 163 + field |> should_be_field_with_error("Must be a whole number") 164 + } 165 + 166 + pub fn parse_triple_field_form_with_error_test() { 167 + let assert [fielda, fieldb, fieldc] = 168 + three_field_form() 169 + |> forma.data([#("a", "string"), #("b", "1"), #("c", "string")]) 170 + |> forma.parse 171 + |> get_form_from_error_result 172 + |> forma.get_fields 173 + 174 + fielda |> should_be_field_no_error 175 + fieldb |> should_be_field_no_error 176 + fieldc |> should_be_field_with_error("Must be a number") 177 + 178 + let assert [fielda, fieldb, fieldc] = 179 + three_field_form() 180 + |> forma.data([#("a", "string"), #("b", "string"), #("c", "string")]) 181 + |> forma.parse 182 + |> get_form_from_error_result 183 + |> forma.get_fields 184 + fielda |> should_be_field_no_error 185 + fieldb |> should_be_field_with_error("Must be a whole number") 186 + fieldc |> should_be_field_with_error("Must be a number") 187 + 188 + let assert [fielda, fieldb, fieldc] = 189 + three_field_form() 190 + |> forma.data([#("a", "string"), #("b", "string"), #("c", "3.4")]) 191 + |> forma.parse 192 + |> get_form_from_error_result 193 + |> forma.get_fields 194 + fielda |> should_be_field_no_error 195 + fieldb |> should_be_field_with_error("Must be a whole number") 196 + fieldc |> should_be_field_no_error 197 + 198 + let assert [fielda, fieldb, fieldc] = 199 + three_field_form() 200 + |> forma.data([#("a", ""), #("b", "string"), #("c", "3.4")]) 201 + |> forma.parse 202 + |> get_form_from_error_result 203 + |> forma.get_fields 204 + fielda |> should_be_field_with_error("Must be longer than 3 characters") 205 + fieldb |> should_be_field_with_error("Must be a whole number") 206 + fieldc |> should_be_field_no_error 207 + 208 + let assert [fielda, fieldb, fieldc] = 209 + three_field_form() 210 + |> forma.data([#("a", ""), #("b", "1"), #("c", "3.4")]) 211 + |> forma.parse 212 + |> get_form_from_error_result 213 + |> forma.get_fields 214 + fielda |> should_be_field_with_error("Must be longer than 3 characters") 215 + fieldb |> should_be_field_no_error 216 + fieldc |> should_be_field_no_error 217 + } 218 + 219 + pub fn decoded_and_try_test() { 220 + let f = 221 + three_field_form() 222 + |> forma.data([#("a", "string"), #("b", "2"), #("c", "3.0")]) 223 + 224 + // can succeed 225 + forma.parse_and_try(f, fn(_, _) { Ok(3) }) 226 + |> should.equal(Ok(3)) 227 + 228 + // can change type 229 + forma.parse_and_try(f, fn(_, _) { Ok("it worked") }) 230 + |> should.equal(Ok("it worked")) 231 + 232 + // can error 233 + forma.parse_and_try(f, fn(_, form) { Error(form) }) 234 + |> should.equal(Error(f)) 235 + 236 + // can change field 237 + let assert Error(form) = 238 + forma.parse_and_try(f, fn(_, form) { 239 + form 240 + |> forma.field_update("a", fn(field) { field.set_error(field, "woops") }) 241 + |> Error 242 + }) 243 + let assert [fielda, fieldb, fieldc] = forma.get_fields(form) 244 + fielda |> should_be_field_with_error("woops") 245 + fieldb |> should_be_field_no_error 246 + fieldc |> should_be_field_no_error 247 + }
+2 -277
forma/test/forma_test.gleam
··· 1 - import forma 2 - import forma/field 3 - import gleam/list 4 1 import gleeunit 5 2 import gleeunit/should 6 3 ··· 8 5 gleeunit.main() 9 6 } 10 7 11 - pub fn empty_form_test() { 12 - forma.new() 13 - |> forma.get_fields 14 - |> list.length 15 - |> should.equal(0) 16 - } 17 - 18 - pub fn parse_empty_form_test() { 19 - forma.new() 20 - |> forma.data([]) 21 - |> forma.decodes(1) 22 - |> forma.decoded 23 - |> should.equal(Ok(1)) 24 - 25 - forma.new() 26 - |> forma.data([]) 27 - |> forma.decodes("Hello") 28 - |> forma.decoded 29 - |> should.equal(Ok("Hello")) 30 - } 31 - 32 - pub fn parse_single_field_form_test() { 33 - forma.new() 34 - |> forma.add(field.new("first", field.text_field())) 35 - |> forma.data([#("first", "world")]) 36 - |> forma.decodes(fn(str) { "hello " <> str }) 37 - |> forma.decoded 38 - |> should.equal(Ok("hello world")) 39 - 40 - forma.new() 41 - |> forma.add(field.new("first", field.text_field())) 42 - |> forma.data([]) 43 - |> forma.decodes(fn(str) { "hello " <> str }) 44 - |> forma.decoded 45 - |> should.equal(Ok("hello ")) 46 - } 47 - 48 - pub fn parse_double_field_form_test() { 49 - forma.new() 50 - |> forma.add(field.new("first", field.text_field())) 51 - |> forma.add(field.new("second", field.text_field())) 52 - |> forma.data([#("first", "hello"), #("second", "world")]) 53 - |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 54 - |> forma.decoded 55 - |> should.equal(Ok("hello world")) 56 - 57 - forma.new() 58 - |> forma.add(field.new("first", field.text_field())) 59 - |> forma.add(field.new("second", field.text_field())) 60 - |> forma.data([#("first", "hello")]) 61 - |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 62 - |> forma.decoded 63 - |> should.equal(Ok("hello ")) 64 - } 65 - 66 - pub fn parse_double_field_form_extra_data_test() { 67 - forma.new() 68 - |> forma.add(field.new("first", field.text_field())) 69 - |> forma.add(field.new("second", field.text_field())) 70 - |> forma.data([#("first", "1"), #("second", "2")]) 71 - |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 72 - |> forma.decoded 73 - |> should.equal(Ok("1 2")) 74 - 75 - forma.new() 76 - |> forma.add(field.new("first", field.text_field())) 77 - |> forma.add(field.new("second", field.text_field())) 78 - |> forma.data([#("first", "1"), #("second", "2"), #("second", "3")]) 79 - |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 80 - |> forma.decoded 81 - |> should.equal(Ok("1 2")) 82 - } 83 - 84 - pub fn parse_double_field_form_missing_data_test() { 85 - forma.new() 86 - |> forma.add(field.new("first", field.text_field())) 87 - |> forma.add(field.new("second", field.text_field())) 88 - |> forma.data([#("second", "1")]) 89 - |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 90 - |> forma.decoded 91 - |> should.equal(Ok(" 1")) 92 - 93 - forma.new() 94 - |> forma.add(field.new("first", field.text_field())) 95 - |> forma.add(field.new("second", field.text_field())) 96 - |> forma.data([#("first", "1"), #("first", "2")]) 97 - |> forma.decodes(fn(a) { fn(b) { a <> " " <> b } }) 98 - |> forma.decoded 99 - |> should.equal(Ok("1 ")) 100 - } 101 - 102 - pub fn integer_field_test() { 103 - forma.new() 104 - |> forma.add(field.new("first", field.integer_field())) 105 - |> forma.data([#("first", " 1 ")]) 106 - |> forma.decodes(fn(i) { i }) 107 - |> forma.decoded 108 - |> should.equal(Ok(1)) 109 - } 110 - 111 - pub fn can_decodes_in_any_order_test() { 112 - forma.new() 113 - |> forma.decodes(fn(str) { "hello " <> str }) 114 - |> forma.add(field.new("first", field.text_field())) 115 - |> forma.data([#("first", "world")]) 116 - |> forma.decoded 117 - |> should.equal(Ok("hello world")) 118 - 119 - forma.new() 120 - |> forma.add(field.new("first", field.text_field())) 121 - |> forma.data([#("first", "world")]) 122 - |> forma.decodes(fn(str) { "hello " <> str }) 123 - |> forma.decoded 124 - |> should.equal(Ok("hello world")) 125 - } 126 - 127 - pub fn parse_single_field_form_with_error_test() { 128 - let assert Error(f) = 129 - forma.new() 130 - |> forma.add(field.new("first", field.integer_field())) 131 - |> forma.data([#("first", "world")]) 132 - |> forma.decodes(fn(_) { 1 }) 133 - |> forma.decoded 134 - 135 - let assert [field] = forma.get_fields(f) 136 - field |> should_be_field_with_error("not an integer") 137 - } 138 - 139 - pub fn parse_double_field_form_with_error_test() { 140 - let form = 141 - forma.new() 142 - |> forma.add(field.new("a", field.integer_field())) 143 - |> forma.add(field.new("b", field.integer_field())) 144 - |> forma.decodes(fn(_) { fn(_) { 1 } }) 145 - 146 - let assert Error(f) = 147 - form 148 - |> forma.data([#("a", "not a number"), #("b", "2")]) 149 - |> forma.decoded 150 - 151 - let assert [fielda, fieldb] = forma.get_fields(f) 152 - fielda |> should_be_field_with_error("not an integer") 153 - fieldb |> should_be_field_no_error 154 - 155 - let assert Error(f) = 156 - form 157 - |> forma.data([#("a", "1"), #("b", "string")]) 158 - |> forma.decoded 159 - 160 - let assert [fielda, fieldb] = forma.get_fields(f) 161 - fielda |> should_be_field_no_error 162 - fieldb |> should_be_field_with_error("not an integer") 163 - 164 - let assert Error(f) = 165 - form 166 - |> forma.data([#("a", "string"), #("b", "string")]) 167 - |> forma.decoded 168 - 169 - let assert [fielda, fieldb] = forma.get_fields(f) 170 - fielda |> should_be_field_with_error("not an integer") 171 - fieldb |> should_be_field_with_error("not an integer") 172 - } 173 - 174 - pub fn parse_triple_field_form_with_error_test() { 175 - let form = 176 - forma.new() 177 - |> forma.add(field.new("a", field.integer_field())) 178 - |> forma.add(field.new("b", field.integer_field())) 179 - |> forma.add(field.new("c", field.integer_field())) 180 - |> forma.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 181 - 182 - let assert Error(f) = 183 - form 184 - |> forma.data([#("a", "1"), #("b", "2"), #("c", "string")]) 185 - |> forma.decoded 186 - let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 187 - fielda |> should_be_field_no_error 188 - fieldb |> should_be_field_no_error 189 - fieldc |> should_be_field_with_error("not an integer") 190 - 191 - let assert Error(f) = 192 - form 193 - |> forma.data([#("a", "1"), #("b", "string"), #("c", "string")]) 194 - |> forma.decoded 195 - let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 196 - fielda |> should_be_field_no_error 197 - fieldb |> should_be_field_with_error("not an integer") 198 - fieldc |> should_be_field_with_error("not an integer") 199 - 200 - let assert Error(f) = 201 - form 202 - |> forma.data([#("a", "1"), #("b", "string"), #("c", "3")]) 203 - |> forma.decoded 204 - let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 205 - fielda |> should_be_field_no_error 206 - fieldb |> should_be_field_with_error("not an integer") 207 - fieldc |> should_be_field_no_error 208 - 209 - let assert Error(f) = 210 - form 211 - |> forma.data([#("a", "string"), #("b", "string"), #("c", "3")]) 212 - |> forma.decoded 213 - let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 214 - fielda |> should_be_field_with_error("not an integer") 215 - fieldb |> should_be_field_with_error("not an integer") 216 - fieldc |> should_be_field_no_error 217 - 218 - let assert Error(f) = 219 - form 220 - |> forma.data([#("a", "string"), #("b", "2"), #("c", "3")]) 221 - |> forma.decoded 222 - let assert [fielda, fieldb, fieldc] = forma.get_fields(f) 223 - fielda |> should_be_field_with_error("not an integer") 224 - fieldb |> should_be_field_no_error 225 - fieldc |> should_be_field_no_error 226 - } 227 - 228 - fn should_be_field_no_error(field: forma.Field(String)) { 229 - should.equal( 230 - field, 231 - forma.Field( 232 - name: field.name, 233 - label: field.label, 234 - help_text: field.help_text, 235 - value: field.value, 236 - render: field.render, 237 - ), 238 - ) 239 - } 240 - 241 - fn should_be_field_with_error(field: forma.Field(String), str: String) { 242 - should.equal( 243 - field, 244 - forma.InvalidField( 245 - name: field.name, 246 - label: field.label, 247 - help_text: field.help_text, 248 - value: field.value, 249 - render: field.render, 250 - error: str, 251 - ), 252 - ) 253 - } 254 - 255 - pub fn decoded_and_try_test() { 256 - let f = 257 - forma.new() 258 - |> forma.add(field.new("a", field.integer_field())) 259 - |> forma.add(field.new("b", field.integer_field())) 260 - |> forma.add(field.new("c", field.integer_field())) 261 - |> forma.decodes(fn(_) { fn(_) { fn(_) { 1 } } }) 262 - |> forma.data([#("a", "1"), #("b", "2"), #("c", "3")]) 263 - 264 - // can succeed 265 - forma.decode_and_try(f, fn(_, _) { Ok(3) }) 266 - |> should.equal(Ok(3)) 267 - 268 - // can change type 269 - forma.decode_and_try(f, fn(_, _) { Ok("it worked") }) 270 - |> should.equal(Ok("it worked")) 271 - 272 - // can error 273 - forma.decode_and_try(f, fn(_, form) { Error(form) }) 274 - |> should.equal(Error(f)) 275 - 276 - // can change field 277 - let assert Error(form) = 278 - forma.decode_and_try(f, fn(_, form) { 279 - Error(forma.set_field_error(form, "a", "woops")) 280 - }) 281 - let assert [fielda, fieldb, fieldc] = forma.get_fields(form) 282 - fielda |> should_be_field_with_error("woops") 283 - fieldb |> should_be_field_no_error 284 - fieldc |> should_be_field_no_error 8 + pub fn first_test() { 9 + 1 |> should.equal(1) 285 10 }