1use std::borrow::Cow;
2use std::{fmt, str};
3
4pub use mendes_macros::{form, ToField};
5use thiserror::Error;
6
7#[cfg(feature = "uploads")]
8#[cfg_attr(docsrs, doc(cfg(feature = "uploads")))]
9pub use crate::multipart::{from_form_data, File};
10
11pub trait ToForm {
15 fn to_form() -> Form;
16}
17
18pub struct Form {
20 pub action: Option<Cow<'static, str>>,
22 pub enctype: Option<Cow<'static, str>>,
24 pub method: Option<Cow<'static, str>>,
26 pub classes: Vec<Cow<'static, str>>,
28 pub sets: Vec<FieldSet>,
30}
31
32impl Form {
33 #[doc(hidden)]
35 pub fn prepare(mut self) -> Self {
36 let multipart = self
37 .sets
38 .iter()
39 .flat_map(|s| &s.items)
40 .any(|i| i.multipart());
41 if multipart {
42 self.enctype = Some("multipart/form-data".into());
43 }
44 self
45 }
46
47 pub fn set<T: fmt::Display>(mut self, name: &str, value: T) -> Result<Self, Error> {
48 self.sets
49 .iter_mut()
50 .flat_map(|s| &mut s.items)
51 .try_fold((), |_, item| item.set(name, &value))
52 .map(|_| self)
53 }
54}
55
56impl fmt::Display for Form {
57 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(fmt, "<form")?;
59 if let Some(s) = &self.action {
60 write!(fmt, r#" action="{s}""#)?;
61 }
62 if let Some(s) = &self.enctype {
63 write!(fmt, r#" enctype="{s}""#)?;
64 }
65 if let Some(s) = &self.method {
66 write!(fmt, r#" method="{s}""#)?;
67 }
68 if !self.classes.is_empty() {
69 write!(fmt, r#" class=""#)?;
70 for (i, s) in self.classes.iter().enumerate() {
71 match i {
72 0 => write!(fmt, "{s}")?,
73 _ => write!(fmt, " {s}")?,
74 }
75 }
76 write!(fmt, "\"")?;
77 }
78 write!(fmt, ">")?;
79 for set in &self.sets {
80 write!(fmt, "{set}")?;
81 }
82 write!(fmt, "</form>")
83 }
84}
85
86pub struct FieldSet {
87 pub legend: Option<&'static str>,
88 pub items: Vec<Item>,
89}
90
91impl fmt::Display for FieldSet {
92 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
93 write!(fmt, "<fieldset>")?;
94 if let Some(s) = self.legend {
95 write!(fmt, "<legend>{s}</legend>")?;
96 }
97 for item in &self.items {
98 write!(fmt, "{item}")?;
99 }
100 write!(fmt, "</fieldset>")
101 }
102}
103
104pub struct Item {
105 pub label: Option<Cow<'static, str>>,
106 pub contents: ItemContents,
107}
108
109impl Item {
110 fn set<T: fmt::Display>(&mut self, name: &str, value: &T) -> Result<(), Error> {
111 match &mut self.contents {
112 ItemContents::Single(f) => {
113 if f.name() != Some(name) {
114 return Ok(());
115 }
116
117 match f {
118 Field::Checkbox(f) => {
119 let s = value.to_string();
120 if s == "true" || s == "1" {
121 f.checked = true;
122 Ok(())
123 } else if s == "false" || s == "0" {
124 f.checked = false;
125 Ok(())
126 } else {
127 Err(Error::SetInvalidBooleanValue)
128 }
129 }
130 Field::Date(f) => {
131 f.value = Some(value.to_string().into());
132 Ok(())
133 }
134 Field::Email(f) => {
135 f.value = Some(value.to_string().into());
136 Ok(())
137 }
138 Field::Hidden(f) => {
139 f.value = Some(value.to_string().into());
140 Ok(())
141 }
142 Field::Number(f) => {
143 f.value = Some(value.to_string().into());
144 Ok(())
145 }
146 Field::Password(f) => {
147 f.value = Some(value.to_string().into());
148 Ok(())
149 }
150 Field::Select(f) => {
151 let val = value.to_string();
152 for option in &mut f.options {
153 if option.value == val {
154 option.selected = true;
155 return Ok(());
156 }
157 }
158 Err(Error::SetOptionNotFound)
159 }
160 Field::Text(f) => {
161 f.value = Some(value.to_string().into());
162 Ok(())
163 }
164 Field::File(_) | Field::Submit(_) => Err(Error::SetUnsupportedFieldType),
165 }
166 }
167 ItemContents::Multi(items) => {
168 for item in items {
169 item.set(name, value)?;
170 }
171 Ok(())
172 }
173 }
174 }
175
176 fn multipart(&self) -> bool {
177 match &self.contents {
178 ItemContents::Single(f) => matches!(f, Field::File(_)),
179 ItemContents::Multi(items) => items.iter().any(|i| i.multipart()),
180 }
181 }
182}
183
184impl fmt::Display for Item {
185 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match (&self.contents, &self.label) {
187 (ItemContents::Single(Field::Submit(_)), None) => write!(fmt, "{}", self.contents),
188 (ItemContents::Single(f), Some(l)) => write!(
189 fmt,
190 r#"<label for="{}">{}</label>{}"#,
191 f.name().unwrap(),
192 l,
193 self.contents
194 ),
195 (_, Some(l)) => write!(fmt, r#"<label>{}</label>{}"#, l, self.contents),
196 (_, None) => write!(fmt, "{}", self.contents),
197 }
198 }
199}
200
201pub enum ItemContents {
202 Single(Field),
203 Multi(Vec<Item>),
204}
205
206impl fmt::Display for ItemContents {
207 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
208 match self {
209 ItemContents::Single(f) => write!(fmt, "{f}"),
210 ItemContents::Multi(items) => {
211 write!(fmt, r#"<div class="compound-item">"#)?;
212 for item in items {
213 write!(fmt, "{item}")?;
214 }
215 write!(fmt, "</div>")
216 }
217 }
218 }
219}
220
221pub enum Field {
222 Checkbox(Checkbox),
223 Date(Date),
224 Email(Email),
225 File(FileInput),
226 Hidden(Hidden),
227 Number(Number),
228 Password(Password),
229 Select(Select),
230 Submit(Submit),
231 Text(Text),
232}
233
234impl Field {
235 pub fn name(&self) -> Option<&str> {
236 use Field::*;
237 match self {
238 Checkbox(f) => Some(&f.name),
239 Date(f) => Some(&f.name),
240 Email(f) => Some(&f.name),
241 File(f) => Some(&f.name),
242 Hidden(f) => Some(&f.name),
243 Number(f) => Some(&f.name),
244 Password(f) => Some(&f.name),
245 Select(f) => Some(&f.name),
246 Text(f) => Some(&f.name),
247 Submit(_) => None,
248 }
249 }
250}
251
252impl fmt::Display for Field {
253 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
254 use Field::*;
255 match self {
256 Checkbox(f) => write!(fmt, "{f}"),
257 Date(f) => write!(fmt, "{f}"),
258 Email(f) => write!(fmt, "{f}"),
259 File(f) => write!(fmt, "{f}"),
260 Hidden(f) => write!(fmt, "{f}"),
261 Number(f) => write!(fmt, "{f}"),
262 Password(f) => write!(fmt, "{f}"),
263 Select(f) => write!(fmt, "{f}"),
264 Submit(f) => write!(fmt, "{f}"),
265 Text(f) => write!(fmt, "{f}"),
266 }
267 }
268}
269
270pub struct Checkbox {
271 pub name: Cow<'static, str>,
272 pub checked: bool,
273}
274
275impl fmt::Display for Checkbox {
276 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
277 write!(
278 fmt,
279 r#"<input type="checkbox" name="{}" value="true""#,
280 self.name
281 )?;
282 if self.checked {
283 write!(fmt, " checked")?;
284 }
285 write!(fmt, ">")
286 }
287}
288
289pub struct Date {
290 pub name: Cow<'static, str>,
291 pub value: Option<Cow<'static, str>>,
292}
293
294impl fmt::Display for Date {
295 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
296 write!(fmt, r#"<input type="date" name="{}""#, self.name)?;
297 if let Some(s) = &self.value {
298 write!(fmt, r#" value="{s}""#)?;
299 }
300 write!(fmt, ">")
301 }
302}
303
304pub struct Email {
305 pub name: Cow<'static, str>,
306 pub value: Option<Cow<'static, str>>,
307}
308
309impl fmt::Display for Email {
310 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
311 write!(fmt, r#"<input type="email" name="{}""#, self.name)?;
312 if let Some(s) = &self.value {
313 write!(fmt, r#" value="{s}""#)?;
314 }
315 write!(fmt, ">")
316 }
317}
318
319pub struct FileInput {
320 pub name: Cow<'static, str>,
321}
322
323impl fmt::Display for FileInput {
324 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
325 write!(fmt, r#"<input type="file" name="{}">"#, self.name)
326 }
327}
328
329pub struct Hidden {
330 pub name: Cow<'static, str>,
331 pub value: Option<Cow<'static, str>>,
332}
333
334impl Hidden {
335 fn from_params(name: Cow<'static, str>, _: &[(&str, &str)]) -> Self {
336 Self { name, value: None }
337 }
338}
339
340impl fmt::Display for Hidden {
341 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(fmt, r#"<input type="hidden" name="{}""#, self.name)?;
343 if let Some(s) = &self.value {
344 write!(fmt, r#" value="{s}""#)?;
345 }
346 write!(fmt, ">")
347 }
348}
349
350pub struct Number {
351 pub name: Cow<'static, str>,
352 pub value: Option<Cow<'static, str>>,
353}
354
355impl fmt::Display for Number {
356 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
357 write!(fmt, r#"<input type="number" name="{}""#, self.name)?;
358 if let Some(s) = &self.value {
359 write!(fmt, r#" value="{s}""#)?;
360 }
361 write!(fmt, ">")
362 }
363}
364
365pub struct Password {
366 pub name: Cow<'static, str>,
367 pub value: Option<Cow<'static, str>>,
368}
369
370impl fmt::Display for Password {
371 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
372 write!(fmt, r#"<input type="password" name="{}""#, self.name)?;
373 if let Some(s) = &self.value {
374 write!(fmt, r#" value="{s}""#)?;
375 }
376 write!(fmt, ">")
377 }
378}
379
380pub struct Select {
381 pub name: Cow<'static, str>,
382 pub options: Vec<SelectOption>,
383}
384
385impl fmt::Display for Select {
386 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
387 write!(fmt, r#"<select name="{}">"#, &self.name)?;
388 for opt in &self.options {
389 write!(fmt, "{opt}")?;
390 }
391 write!(fmt, "</select>")
392 }
393}
394
395pub struct SelectOption {
396 pub label: Cow<'static, str>,
397 pub value: Cow<'static, str>,
398 pub disabled: bool,
399 pub selected: bool,
400}
401
402impl fmt::Display for SelectOption {
403 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
404 write!(fmt, r#"<option value="{}""#, self.value)?;
405 if self.disabled {
406 write!(fmt, " disabled")?;
407 }
408 if self.selected {
409 write!(fmt, " selected")?;
410 }
411 write!(fmt, ">{}</option>", self.label)
412 }
413}
414
415pub struct Submit {
416 pub value: Option<Cow<'static, str>>,
417}
418
419impl fmt::Display for Submit {
420 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
421 write!(fmt, r#"<input type="submit""#)?;
422 if let Some(s) = &self.value {
423 write!(fmt, r#" value="{s}""#)?;
424 }
425 write!(fmt, ">")
426 }
427}
428
429pub struct Text {
430 pub name: Cow<'static, str>,
431 pub value: Option<Cow<'static, str>>,
432}
433
434impl fmt::Display for Text {
435 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(fmt, r#"<input type="text" name="{}""#, self.name)?;
437 if let Some(s) = &self.value {
438 write!(fmt, r#" value="{s}""#)?;
439 }
440 write!(fmt, ">")
441 }
442}
443
444pub trait ToField {
445 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field;
446}
447
448impl ToField for bool {
449 fn to_field(name: Cow<'static, str>, _: &[(&str, &str)]) -> Field {
450 Field::Checkbox(Checkbox {
451 name,
452 checked: false,
453 })
454 }
455}
456
457impl ToField for String {
458 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
459 for (key, value) in params {
460 if *key == "type" {
461 if *value == "hidden" {
462 return Field::Hidden(Hidden::from_params(name, params));
463 } else if *value == "email" {
464 return Field::Email(Email { name, value: None });
465 } else if *value == "password" {
466 return Field::Password(Password { name, value: None });
467 }
468 }
469 }
470 Field::Text(Text { name, value: None })
471 }
472}
473
474impl ToField for Cow<'_, str> {
475 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
476 for (key, value) in params {
477 if *key == "type" {
478 if *value == "hidden" {
479 return Field::Hidden(Hidden::from_params(name, params));
480 } else if *value == "email" {
481 return Field::Email(Email { name, value: None });
482 } else if *value == "password" {
483 return Field::Password(Password { name, value: None });
484 }
485 }
486 }
487 Field::Text(Text { name, value: None })
488 }
489}
490
491impl ToField for u8 {
492 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
493 for (key, value) in params {
494 if *key == "type" && *value == "hidden" {
495 return Field::Hidden(Hidden::from_params(name, params));
496 }
497 }
498 Field::Number(Number { name, value: None })
499 }
500}
501
502impl ToField for u16 {
503 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
504 for (key, value) in params {
505 if *key == "type" && *value == "hidden" {
506 return Field::Hidden(Hidden::from_params(name, params));
507 }
508 }
509 Field::Number(Number { name, value: None })
510 }
511}
512
513impl ToField for u32 {
514 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
515 for (key, value) in params {
516 if *key == "type" && *value == "hidden" {
517 return Field::Hidden(Hidden::from_params(name, params));
518 }
519 }
520 Field::Number(Number { name, value: None })
521 }
522}
523
524impl ToField for u64 {
525 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
526 for (key, value) in params {
527 if *key == "type" && *value == "hidden" {
528 return Field::Hidden(Hidden::from_params(name, params));
529 }
530 }
531 Field::Number(Number { name, value: None })
532 }
533}
534
535impl ToField for i32 {
536 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
537 for (key, value) in params {
538 if *key == "type" && *value == "hidden" {
539 return Field::Hidden(Hidden::from_params(name, params));
540 }
541 }
542 Field::Number(Number { name, value: None })
543 }
544}
545
546impl ToField for i64 {
547 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
548 for (key, value) in params {
549 if *key == "type" && *value == "hidden" {
550 return Field::Hidden(Hidden::from_params(name, params));
551 }
552 }
553 Field::Number(Number { name, value: None })
554 }
555}
556
557impl ToField for f32 {
558 fn to_field(name: Cow<'static, str>, params: &[(&str, &str)]) -> Field {
559 for (key, value) in params {
560 if *key == "type" && *value == "hidden" {
561 return Field::Hidden(Hidden::from_params(name, params));
562 }
563 }
564 Field::Number(Number { name, value: None })
565 }
566}
567
568#[cfg(feature = "chrono")]
569#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
570impl ToField for chrono::NaiveDate {
571 fn to_field(name: Cow<'static, str>, _: &[(&str, &str)]) -> Field {
572 Field::Date(Date { name, value: None })
573 }
574}
575
576#[derive(Debug, Error)]
577pub enum Error {
578 #[error("invalid value for boolean field")]
579 SetInvalidBooleanValue,
580 #[error("no option with given value found in select")]
581 SetOptionNotFound,
582 #[error("unable to set value for unknown field")]
583 SetUnknownField,
584 #[error("setting value not supported for this field type")]
585 SetUnsupportedFieldType,
586}