1use std::fmt;
16use std::ops::Deref;
17
18use regex::Regex;
19
20use crate::error::ValidationError;
21use crate::value::Value;
22
23fn validate_date(date: &str) -> bool {
25 let reg_date = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
26 reg_date.is_match(date)
27}
28
29fn validate_datetime(datetime: &str) -> bool {
30 let reg_datetime = Regex::new(
31 r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$").unwrap();
32 reg_datetime.is_match(datetime)
33}
34
35fn validate_time(time: &str) -> bool {
36 let reg_time = Regex::new(r"^\d{2}:\d{2}$").unwrap();
37 reg_time.is_match(time)
38}
39
40fn validate_email(email: &str) -> bool {
41 let reg_email = Regex::new(r"^\S+@\w[-\.\w]+\.\w{2,}$").unwrap();
44 reg_email.is_match(email)
45}
46
47fn validate_url(url: &str) -> bool {
48 let reg_url = Regex::new(r"^\w+\:\/\/\w[-\.\w]+(\/\S*)?$").unwrap();
52 reg_url.is_match(url)
53}
54
55#[derive(Debug)]
58pub enum Method {
59 Get,
60 Post,
61 Dialog,
64}
65
66impl Method {
67 pub fn attrvalue(&self) -> String {
68 match &self {
69 Method::Get => "get",
70 Method::Post => "post",
71 Method::Dialog => "dialog",
72 }.to_string()
73 }
74}
75
76#[derive(Debug)]
82pub enum Element {
83 Input(InputType),
84 Textarea,
85 Select(SelectType),
86 Button(ButtonType),
87}
88
89impl Element {
90 pub fn validate(&self, formvalue: &Value)
92 -> Result<(), ValidationError> {
93 match self {
94 Element::Input(InputType::Date) => {
95 if !validate_date(&formvalue.to_string()) {
96 Err(ValidationError::new(
97 &format!("Invalid date {}.", formvalue.as_string())))
98 } else {
99 Ok(())
100 }
101 },
102 Element::Input(InputType::Time) => {
103 if !validate_time(&formvalue.to_string()) {
104 Err(ValidationError::new(
105 &format!("Invalid time {}.", formvalue.as_string())))
106 } else {
107 Ok(())
108 }
109 },
110 Element::Input(InputType::DateTime) => {
111 if !validate_datetime(&formvalue.to_string()) {
112 Err(ValidationError::new(
113 &format!(
114 "Invalid datetime {}.", formvalue.as_string())))
115 } else {
116 Ok(())
117 }
118 },
119 Element::Input(InputType::Email) => {
120 if !validate_email(&formvalue.to_string()) {
121 Err(ValidationError::new(
122 &format!(
123 "Invalid email address {}.",
124 formvalue.as_string())))
125 } else {
126 Ok(())
127 }
128 },
129 Element::Input(InputType::Url) => {
130 if !validate_url(&formvalue.to_string()) {
131 Err(ValidationError::new(
132 &format!("Invalid url {}", formvalue.as_string())))
133 } else {
134 Ok(())
135 }
136 },
137 _ => Ok(())
138 }
139 }
140
141 pub fn element_name(&self) -> &'static str {
144 match &self {
145 Element::Input(_) => "input",
146 Element::Textarea => "textarea",
147 Element::Select(_) => "select",
148 Element::Button(_) => "button",
149 }
150 }
151
152 pub fn element_type(&self) -> &'static str {
155 match &self {
156 Element::Input(input_type) => match input_type {
157 InputType::Text => "text",
158 InputType::Password => "password",
159 InputType::Radio => "radio",
160 InputType::Checkbox => "checkbox",
161 InputType::Number => "number",
162 InputType::Range => "range",
163 InputType::Date => "date",
164 InputType::DateTime => "datetime-local",
165 InputType::Month => "month",
166 InputType::Week => "week",
167 InputType::Time => "time",
168 InputType::Url => "url",
169 InputType::Email => "email",
170 InputType::Tel => "tel",
171 InputType::Color => "color",
172 InputType::File => "file",
173 InputType::Search => "search",
174 InputType::Button => "button",
175 InputType::Reset => "reset",
176 InputType::Submit => "submit",
177 InputType::Image => "image",
178 InputType::Hidden => "hidden",
179 },
180 Element::Button(button_type) => match button_type {
181 ButtonType::Submit => "submit",
182 ButtonType::Reset => "reset",
183 ButtonType::Button => "button",
184 },
185 _ => "",
186 }
187 }
188
189 pub fn multi(&self) -> bool {
192 match self {
193 Element::Input(InputType::Checkbox) |
194 Element::Select(SelectType::Multi) => true,
195 _ => false,
196 }
197 }
198}
199
200#[derive(Debug)]
203pub enum InputType {
204 Text,
205 Password,
206 Radio,
207 Checkbox,
208 Number,
209 Range,
210 Date,
211 DateTime,
212 Month,
213 Week,
214 Time,
215 Url,
216 Email,
217 Tel,
218 Color,
219 File,
220 Search,
221 Button,
222 Submit,
223 Reset,
224 Image,
225 Hidden,
226}
227
228#[derive(Debug)]
230pub enum SelectType {
231 Single,
232 Multi,
233}
234
235#[derive(Debug)]
237pub enum ButtonType {
238 Submit,
239 Reset,
240 Button,
241}
242
243#[derive(Debug)]
246pub enum Spellcheck {
247 True,
248 Default,
249 False,
250}
251
252#[derive(Debug)]
254pub enum Wrap {
255 Hard,
256 Soft,
257 Off,
258}
259
260#[derive(Debug)]
262pub enum Autocomplete {
263 On,
264 Off,
265}
266
267pub enum Constraint<'a> {
275 MinLength(usize),
277 MaxLength(usize),
279 MinNumber(f64),
281 MaxNumber(f64),
283 MinDate(&'a str),
285 MaxDate(&'a str),
287 MinDateTime(&'a str),
289 MaxDateTime(&'a str),
291 MinTime(&'a str),
293 MaxTime(&'a str),
295 Pattern(&'a str),
299 Func(Box<Fn(&Value) -> Result<(), ValidationError>>),
302}
303
304impl <'a> Constraint<'a> {
305 pub fn validate(&self, formvalue: &Value)
307 -> Result<(), ValidationError> {
308 match self {
309 Constraint::MinLength(min) => {
310 let value = formvalue.as_string();
311 if value.len() < *min {
312 return Err(ValidationError::new(
313 &format!(
314 "Must be at least {} characters long.",
315 min)));
316 }
317 },
318 Constraint::MaxLength(max) => {
319 let value = formvalue.as_string();
320 if value.len() > *max {
321 return Err(ValidationError::new(
322 &format!(
323 "Can not be more than {} characters long.",
324 max)));
325 }
326 },
327 Constraint::MinNumber(min) => {
328 let value: f64 = formvalue.parse()?;
329 if value < *min {
330 return Err(ValidationError::new(
331 &format!("Must be at least {}.", min)));
332 }
333 },
334 Constraint::MaxNumber(max) => {
335 let value: f64 = formvalue.parse()?;
336 if value > *max {
337 return Err(ValidationError::new(
338 &format!("Can not be more than {}.", max)));
339 }
340 },
341 Constraint::MinDate(min) => {
342 let value = formvalue.to_string();
343 if !validate_date(&value) {
344 return Err(ValidationError::new(
345 &format!("Invalid date {}.", value)));
346 }
347 for (i, chr) in value.as_bytes().iter().enumerate() {
351 if *chr < min.as_bytes()[i] {
352 return Err(ValidationError::new(
353 &format!(
354 "Date should be after {}.", min)));
355 }
356 }
357 },
358 Constraint::MaxDate(max) => {
359 let value = formvalue.to_string();
360 if !validate_date(&value) {
361 return Err(ValidationError::new(
362 &format!("Invalid date {}.", value)));
363 }
364 for (i, chr) in value.as_bytes().iter().enumerate() {
365 if *chr > max.as_bytes()[i] {
366 return Err(ValidationError::new(
367 &format!(
368 "Date can not be after {}.", max)));
369 }
370 }
371 },
372 Constraint::MinDateTime(min) => {
373 let value = formvalue.to_string();
374 if !validate_datetime(&value) {
375 return Err(ValidationError::new(
376 &format!("Invalid date and time {}.", value)));
377 }
378 for (i, chr) in value.as_bytes().iter().enumerate() {
379 if *chr < min.as_bytes()[i] {
380 return Err(ValidationError::new(
381 &format!(
382 "Date and time must be after {}",
383 min)));
384 }
385 }
386 },
387 Constraint::MaxDateTime(max) => {
388 let value = formvalue.to_string();
389 if !validate_datetime(&value) {
390 return Err(ValidationError::new(
391 &format!("Invalid date and time {}.", value)));
392 }
393 for (i, chr) in value.as_bytes().iter().enumerate() {
394 if *chr > max.as_bytes()[i] {
395 return Err(ValidationError::new(
396 &format!(
397 "Date and time can not be after {}.",
398 max)));
399 }
400 }
401 },
402 Constraint::MinTime(min) => {
403 let value = formvalue.to_string();
404 if !validate_time(&value) {
405 return Err(ValidationError::new(
406 &format!("Invalid time {}.", value)));
407 }
408 for (i, chr) in value.as_bytes().iter().enumerate() {
409 if *chr < min.as_bytes()[i] {
410 return Err(ValidationError::new(
411 &format!(
412 "Time must be after {}.", min)));
413 }
414 }
415 },
416 Constraint::MaxTime(max) => {
417 let value = formvalue.to_string();
418 if !validate_time(&value) {
419 return Err(ValidationError::new(
420 &format!("Invalid time {}.", value)));
421 }
422 for (i, chr) in value.as_bytes().iter().enumerate() {
423 if *chr > max.as_bytes()[i] {
424 return Err(ValidationError::new(
425 &format!(
426 "Time can not be after {}.", max)));
427 }
428 }
429 },
430 Constraint::Pattern(pattern) => {
431 let value = formvalue.as_string();
432 let reg = match Regex::new(pattern) {
433 Ok(reg) => reg,
434 Err(_) => return Err(
435 ValidationError::new(
436 &format!("Invalid pattern {:?}.", value))),
437 };
438 if !reg.is_match(&value) {
439 return Err(
440 ValidationError::new(
441 &format!("Please match the format requested.")));
442 }
443 },
444 Constraint::Func(validator) => {
445 validator(&formvalue)?;
446 },
447 }
448 Ok(())
449 }
450
451 pub fn attrpair(&self) -> Option<(String, String)> {
456 match self {
457 Constraint::MinLength(min) =>
458 Some((String::from("minlength"), min.to_string())),
459 Constraint::MaxLength(max) =>
460 Some((String::from("maxlength"), max.to_string())),
461 Constraint::MinNumber(min) =>
462 Some((String::from("min"), min.to_string())),
463 Constraint::MaxNumber(max) =>
464 Some((String::from("max"), max.to_string())),
465 Constraint::MinDate(min) |
466 Constraint::MinTime(min) |
467 Constraint::MinDateTime(min) =>
468 Some((String::from("min"), min.to_string())),
469 Constraint::MaxDate(max) |
470 Constraint::MaxTime(max) |
471 Constraint::MaxDateTime(max) =>
472 Some((String::from("max"), max.to_string())),
473 Constraint::Pattern(pattern) =>
474 Some((String::from("pattern"), pattern.to_string())),
475 Constraint::Func(_) => None,
476 }
477 }
478
479 pub fn allowed_on(&self, element: &Element) -> bool {
482 match self {
483 Constraint::MinLength(_) |
484 Constraint::MaxLength(_) => match element {
485 Element::Textarea => true,
486 Element::Input(input_type) => match input_type {
487 InputType::Radio |
488 InputType::Checkbox |
489 InputType::File |
490 InputType::Button |
491 InputType::Submit |
492 InputType::Reset |
493 InputType::Image |
494 InputType::Hidden => false,
495 _ => true,
496 },
497 _ => false,
498 },
499 Constraint::MinNumber(_) => match element {
500 Element::Input(InputType::Number) => true,
501 _ => false,
502 },
503 Constraint::MaxNumber(_) => match element {
504 Element::Input(InputType::Number) => true,
505 _ => false,
506 },
507 Constraint::MinDate(_) => match element {
508 Element::Input(InputType::Date) => true,
509 _ => false,
510 },
511 Constraint::MaxDate(_) => match element {
512 Element::Input(InputType::Date) => true,
513 _ => false,
514 },
515 Constraint::MinTime(_) => match element {
516 Element::Input(InputType::Time) => true,
517 _ => false,
518 },
519 Constraint::MaxTime(_) => match element {
520 Element::Input(InputType::Time) => true,
521 _ => false,
522 },
523 Constraint::MinDateTime(_) => match element {
524 Element::Input(InputType::DateTime) => true,
525 _ => false,
526 },
527 Constraint::MaxDateTime(_) => match element {
528 Element::Input(InputType::DateTime) => true,
529 _ => false,
530 },
531 Constraint::Pattern(_) => match element {
532 Element::Textarea => true,
533 Element::Input(input_type) => match input_type {
534 InputType::Text |
535 InputType::Password |
536 InputType::Date |
537 InputType::Url |
538 InputType::Email |
539 InputType::Tel |
540 InputType::Search => true,
541 _ => false,
542 },
543 _ => false,
544 },
545 Constraint::Func(_) => true,
546 }
547 }
548}
549
550impl <'a> fmt::Debug for Constraint<'a> {
551 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
552 match self {
553 Constraint::MinLength(len) => {
554 write!(f, "Constraint::MinLength({})", len)
555 },
556 Constraint::MaxLength(len) => {
557 write!(f, "Constraint::MaxLength({})", len)
558 },
559 Constraint::MinNumber(number) => {
560 write!(f, "Constraint::MinNumber({})", number)
561 },
562 Constraint::MaxNumber(number) => {
563 write!(f, "Constraint::MaxNumber({})", number)
564 },
565 Constraint::MinDate(number) => {
566 write!(f, "Constraint::MinDate({})", number)
567 },
568 Constraint::MaxDate(number) => {
569 write!(f, "Constraint::MaxDate({})", number)
570 },
571 Constraint::MinTime(number) => {
572 write!(f, "Constraint::MinTime({})", number)
573 },
574 Constraint::MaxTime(number) => {
575 write!(f, "Constraint::MaxTime({})", number)
576 },
577 Constraint::MinDateTime(number) => {
578 write!(f, "Constraint::MinDateTime({})", number)
579 },
580 Constraint::MaxDateTime(number) => {
581 write!(f, "Constraint::MaxDateTime({})", number)
582 },
583 Constraint::Pattern(pattern) => {
584 write!(f, "Constraint::Pattern({})", pattern)
585 },
586 Constraint::Func(_) => {
587 write!(f, "Constraint::Func(Fn)")
588 },
589 }
590 }
591}
592
593#[derive(Debug)]
599pub enum Attr<'a> {
600 Any(&'a str, &'a str),
602
603 Id(&'a str),
605 Title(&'a str),
606 Placeholder(&'a str),
607 Autocomplete(Autocomplete),
608 Autofocus,
609 Disabled,
610 Readonly,
611 Tabindex(i64),
612
613 StepFloat(f64),
615 StepInt(u64),
616
617 Size(u64),
619 Width(u64),
620 Height(u64),
621
622 Rows(u64),
624 Cols(u64),
625 Spellcheck(Spellcheck),
626 Wrap(Wrap),
627
628 FormAction(&'a str),
630 FormEnctype(&'a str),
631 FormNoValidate,
632 FormTarget(&'a str),
633}
634
635impl <'a> Attr<'a> {
636 pub fn attrpair(&self) -> (String, String) {
637 let (name, value) = match self {
638 Attr::Any(name, value) => (name.deref(), value.to_string()),
639 Attr::StepFloat(step) => ("step", step.to_string()),
640 Attr::StepInt(step) => ("step", step.to_string()),
641 Attr::Size(size) => ("size", size.to_string()),
642 Attr::Width(width) => ("width", width.to_string()),
643 Attr::Height(height) => ("height", height.to_string()),
644 Attr::Rows(rows) => ("rows", rows.to_string()),
645 Attr::Cols(cols) => ("cols", cols.to_string()),
646 Attr::Spellcheck(wrap) =>
647 ("wrap", match wrap {
648 Spellcheck::True => String::from("true"),
649 Spellcheck::Default => String::from("default"),
650 Spellcheck::False => String::from("false"),
651 }),
652 Attr::Wrap(wrap) =>
653 ("wrap", match wrap {
654 Wrap::Hard => String::from("hard"),
655 Wrap::Soft => String::from("soft"),
656 Wrap::Off => String::from("off"),
657 }),
658 Attr::FormAction(formaction) =>
659 ("formaction", formaction.to_string()),
660 Attr::FormEnctype(formenctype) =>
661 ("formenctype", formenctype.to_string()),
662 Attr::FormNoValidate => (
663 "formnovalidate", String::from("formnovalidate")),
664 Attr::FormTarget(formtarget) =>
665 ("formtarget", formtarget.to_string()),
666 Attr::Id(id) => ("id", id.to_string()),
667 Attr::Title(label) => ("title", label.to_string()),
668 Attr::Placeholder(label) =>
669 ("placeholder", label.to_string()),
670 Attr::Autocomplete(autocomplete) =>
671 ("autocomplete", match autocomplete {
672 Autocomplete::On => String::from("on"),
673 Autocomplete::Off => String::from("off"),
674 }),
675 Attr::Autofocus => ("autofocus", String::from("autofocus")),
676 Attr::Disabled => ("disabled", String::from("disabled")),
677 Attr::Readonly => ("readonly", String::from("readonly")),
678 Attr::Tabindex(tabindex) => ("tabindex", tabindex.to_string()),
679 };
680 (String::from(name), value)
681 }
682
683 pub fn allowed_on(&self, element: &Element) -> bool {
684 match element {
685 Element::Input(input_type) => match self {
686 Attr::Any(_, _) |
687 Attr::Id(_) |
688 Attr::Title(_) => true,
691 Attr::StepFloat(_) => match input_type {
692 InputType::Number => true,
693 _ => false,
694 },
695 Attr::StepInt(_) => match input_type {
696 InputType::Number |
697 InputType::Date |
698 InputType::Time |
699 InputType::DateTime |
700 InputType::Month |
701 InputType::Week |
702 InputType::Range => true,
703 _ => false,
704 },
705 Attr::Width(_) |
706 Attr::Height(_) => match input_type {
707 InputType::Image => true,
708 _ => false,
709 },
710 Attr::Rows(_) |
711 Attr::Cols(_) |
712 Attr::Spellcheck(_) |
713 Attr::Wrap(_) => false,
714 Attr::FormAction(_) |
715 Attr::FormEnctype(_) |
716 Attr::FormTarget(_) => match input_type {
717 InputType::Submit |
718 InputType::Image => true,
719 _ => false,
720 },
721 Attr::FormNoValidate => match input_type {
722 InputType::Submit => true,
723 _ => false,
724 },
725 Attr::Placeholder(_) => match input_type {
726 InputType::Text |
727 InputType::Password |
728 InputType::Email |
729 InputType::Url |
730 InputType::Tel |
731 InputType::Search => true,
732 _ => false,
733 },
734 Attr::Autocomplete(_) => match input_type {
735 InputType::Text |
736 InputType::Email |
737 InputType::Url |
738 InputType::Tel |
739 InputType::Search |
740 InputType::Date |
741 InputType::DateTime |
742 InputType::Month | InputType::Week | InputType::Time | InputType::Range |
746 InputType::Color => true,
747 _ => false,
748 },
749 Attr::Autofocus => match input_type {
750 InputType::Button |
751 InputType::Submit |
752 InputType::Reset |
753 InputType::Image |
754 InputType::Hidden => false,
755 _ => true,
756 },
757 Attr::Size(_) |
758 Attr::Disabled |
759 Attr::Readonly |
760 Attr::Tabindex(_) => match input_type {
761 InputType::Hidden => false,
762 _ => true,
763 },
764 },
765 Element::Textarea => match self {
766 Attr::Any(_, _) |
767 Attr::Id(_) |
768 Attr::Title(_) |
769 Attr::Placeholder(_) |
770 Attr::Autocomplete(_) |
771 Attr::Autofocus |
772 Attr::Disabled |
773 Attr::Readonly |
774 Attr::Rows(_) |
775 Attr::Cols(_) |
776 Attr::Spellcheck(_) |
777 Attr::Wrap(_) => true,
778 _ => false,
779 },
780 Element::Select(_) => match self {
781 Attr::Any(_, _) |
782 Attr::Id(_) |
783 Attr::Title(_) |
784 Attr::Autocomplete(_) |
785 Attr::Autofocus |
786 Attr::Disabled |
787 Attr::Readonly |
788 Attr::Size(_) |
789 _ => false,
790 },
791 Element::Button(_) => match self {
792 Attr::Any(_, _) |
793 Attr::Id(_) |
794 Attr::Title(_) |
795 Attr::Autocomplete(_) |
796 Attr::Autofocus |
797 Attr::Disabled |
798 Attr::Readonly |
799 Attr::FormAction(_) |
800 Attr::FormEnctype(_) |
801 Attr::FormTarget(_) |
802 Attr::FormNoValidate |
803 Attr::Size(_) |
804 _ => false,
805 },
806 }
807 }
808}