abstract_core/objects/validation/
verifiers.rs

1use core::result::Result::{Err, Ok};
2
3use super::ValidationError;
4
5pub(crate) const MIN_DESC_LENGTH: usize = 1;
6pub(crate) const MAX_DESC_LENGTH: usize = 1024;
7/// Minimum link length is 11, because the shortest url could be http://a.be
8pub(crate) const MIN_LINK_LENGTH: usize = 11;
9pub(crate) const MAX_LINK_LENGTH: usize = 128;
10pub(crate) const MIN_TITLE_LENGTH: usize = 1;
11pub(crate) const MAX_TITLE_LENGTH: usize = 64;
12
13pub(crate) const DANGEROUS_CHARS: &[char] = &['"', '\'', '=', '>', '<'];
14
15fn contains_dangerous_characters(input: &str) -> bool {
16    input.chars().any(|c| DANGEROUS_CHARS.contains(&c))
17}
18
19fn is_valid_url(link: &str) -> bool {
20    link.starts_with("http://") || link.starts_with("https://") || link.starts_with("ipfs://")
21}
22
23pub fn validate_link(link: Option<&str>) -> Result<(), ValidationError> {
24    if let Some(link) = link {
25        if link.len() < MIN_LINK_LENGTH {
26            Err(ValidationError::LinkInvalidShort(MIN_LINK_LENGTH))
27        } else if link.len() > MAX_LINK_LENGTH {
28            Err(ValidationError::LinkInvalidLong(MAX_LINK_LENGTH))
29        } else if !is_valid_url(link) {
30            Err(ValidationError::LinkInvalidFormat {})
31        } else if contains_dangerous_characters(link) {
32            Err(ValidationError::LinkContainsDangerousCharacters {})
33        } else {
34            Ok(())
35        }
36    } else {
37        Ok(())
38    }
39}
40
41pub fn validate_name(title: &str) -> Result<(), ValidationError> {
42    if title.len() < MIN_TITLE_LENGTH {
43        Err(ValidationError::TitleInvalidShort(MIN_TITLE_LENGTH))
44    } else if title.len() > MAX_TITLE_LENGTH {
45        Err(ValidationError::TitleInvalidLong(MAX_TITLE_LENGTH))
46    } else if contains_dangerous_characters(title) {
47        Err(ValidationError::TitleContainsDangerousCharacters {})
48    } else {
49        Ok(())
50    }
51}
52
53pub fn validate_description(maybe_description: Option<&str>) -> Result<(), ValidationError> {
54    if let Some(description) = maybe_description {
55        if description.len() < MIN_DESC_LENGTH {
56            return Err(ValidationError::DescriptionInvalidShort(MIN_DESC_LENGTH));
57        } else if description.len() > MAX_DESC_LENGTH {
58            return Err(ValidationError::DescriptionInvalidLong(MAX_DESC_LENGTH));
59        } else if contains_dangerous_characters(description) {
60            return Err(ValidationError::DescriptionContainsDangerousCharacters {});
61        }
62    }
63    Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68    use rstest::rstest;
69    use speculoos::prelude::*;
70
71    use super::*;
72
73    mod link {
74        use super::*;
75
76        #[rstest(
77            input,
78            case("https://www.google.com"),
79            case("http://example.com"),
80            case("https://example.net:8080")
81        )]
82        fn valid(input: &str) {
83            assert_that!(validate_link(Some(input))).is_ok();
84        }
85
86        #[rstest(
87            input,
88            case("http://a.b"),
89            case("://example.com"),
90            case("example.com"),
91            case("https://example.org/path?query=value"),
92            case("https:/example.com")
93        )]
94        fn invalid(input: &str) {
95            assert_that!(validate_link(Some(input))).is_err();
96        }
97    }
98
99    mod name {
100        use super::*;
101
102        #[rstest(input,
103        case("name"),
104        case("name123"),
105        case("name 123"),
106        case("a"),
107        case(& "a".repeat(MAX_TITLE_LENGTH)),
108        case("name!$%&*+,-.;@^_`|~"),
109        case("名前"),
110        )]
111        fn valid_names(input: &str) {
112            assert_that!(validate_name(input)).is_ok();
113        }
114
115        #[rstest(input,
116        case(""),
117        case(& "a".repeat(MAX_TITLE_LENGTH + 1)),
118        case("name<>'\""),
119        )]
120        fn invalid_names(input: &str) {
121            assert_that!(validate_name(input)).is_err();
122        }
123    }
124
125    mod description {
126        use super::*;
127
128        #[rstest(input,
129        case("d"),
130        case("description123"),
131        case("description 123"),
132        case(& "a".repeat(MAX_DESC_LENGTH)),
133        case("description!$%&*+,-.;@^_`|~"),
134        case("説明"),
135        )]
136        fn valid_descriptions(input: &str) {
137            assert_that!(validate_description(Some(input))).is_ok();
138        }
139
140        #[rstest(input,
141        case(""),
142        case(& "a".repeat(MAX_DESC_LENGTH + 1)),
143        case("description<>'\""),
144        )]
145        fn invalid_descriptions(input: &str) {
146            assert_that!(validate_description(Some(input))).is_err();
147        }
148    }
149}