| 24 |
24 |
|
pub const BLOG_POST_TITLE_MAX: usize = 200;
|
| 25 |
25 |
|
pub const BLOG_POST_SLUG_MAX: usize = 100;
|
| 26 |
26 |
|
pub const BLOG_POST_BODY_MAX: usize = 100_000;
|
|
27 |
+ |
pub const CHAPTER_TITLE_MAX: usize = 200;
|
|
28 |
+ |
pub const ITEM_TEXT_BODY_MAX: usize = 500_000;
|
| 27 |
29 |
|
pub const KEY_CODE_MAX: usize = 50;
|
| 28 |
30 |
|
pub const MACHINE_ID_MAX: usize = 255;
|
| 29 |
31 |
|
pub const ACTIVATION_LABEL_MAX: usize = 100;
|
| 130 |
132 |
|
Ok(())
|
| 131 |
133 |
|
}
|
| 132 |
134 |
|
|
|
135 |
+ |
/// Validate a chapter title
|
|
136 |
+ |
pub fn validate_chapter_title(title: &str) -> Result<(), AppError> {
|
|
137 |
+ |
if title.is_empty() {
|
|
138 |
+ |
return Err(AppError::Validation("Chapter title is required".to_string()));
|
|
139 |
+ |
}
|
|
140 |
+ |
if title.chars().count() > limits::CHAPTER_TITLE_MAX {
|
|
141 |
+ |
return Err(AppError::Validation(format!(
|
|
142 |
+ |
"Chapter title must be {} characters or less",
|
|
143 |
+ |
limits::CHAPTER_TITLE_MAX
|
|
144 |
+ |
)));
|
|
145 |
+ |
}
|
|
146 |
+ |
Ok(())
|
|
147 |
+ |
}
|
|
148 |
+ |
|
|
149 |
+ |
/// Validate an item text body
|
|
150 |
+ |
pub fn validate_item_text_body(body: &str) -> Result<(), AppError> {
|
|
151 |
+ |
if body.chars().count() > limits::ITEM_TEXT_BODY_MAX {
|
|
152 |
+ |
return Err(AppError::Validation(format!(
|
|
153 |
+ |
"Text body must be {} characters or less",
|
|
154 |
+ |
limits::ITEM_TEXT_BODY_MAX
|
|
155 |
+ |
)));
|
|
156 |
+ |
}
|
|
157 |
+ |
Ok(())
|
|
158 |
+ |
}
|
|
159 |
+ |
|
| 133 |
160 |
|
/// Validate a tag name (for admin tag creation).
|
| 134 |
161 |
|
///
|
| 135 |
162 |
|
/// Regular users select tags from the taxonomy via typeahead search,
|
| 471 |
498 |
|
Ok(())
|
| 472 |
499 |
|
}
|
| 473 |
500 |
|
|
|
501 |
+ |
/// Validate a git repository name: 1-64 chars, ASCII alphanumeric + hyphens/underscores/dots, no leading dot.
|
|
502 |
+ |
pub fn validate_git_repo_name(name: &str) -> Result<(), AppError> {
|
|
503 |
+ |
if name.is_empty() || name.len() > 64 {
|
|
504 |
+ |
return Err(AppError::Validation(
|
|
505 |
+ |
"Git repo name must be 1-64 characters".to_string(),
|
|
506 |
+ |
));
|
|
507 |
+ |
}
|
|
508 |
+ |
if name.starts_with('.') {
|
|
509 |
+ |
return Err(AppError::Validation(
|
|
510 |
+ |
"Git repo name cannot start with a dot".to_string(),
|
|
511 |
+ |
));
|
|
512 |
+ |
}
|
|
513 |
+ |
if !name
|
|
514 |
+ |
.chars()
|
|
515 |
+ |
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
|
|
516 |
+ |
{
|
|
517 |
+ |
return Err(AppError::Validation(
|
|
518 |
+ |
"Git repo name can only contain letters, numbers, hyphens, underscores, and dots"
|
|
519 |
+ |
.to_string(),
|
|
520 |
+ |
));
|
|
521 |
+ |
}
|
|
522 |
+ |
Ok(())
|
|
523 |
+ |
}
|
|
524 |
+ |
|
| 474 |
525 |
|
/// Validate price in cents (must be non-negative)
|
| 475 |
526 |
|
pub fn validate_price_cents(price: i32) -> Result<(), AppError> {
|
| 476 |
527 |
|
if price < 0 {
|
| 760 |
811 |
|
assert!(validate_tier_price(-1).is_err()); // negative
|
| 761 |
812 |
|
assert!(validate_tier_price(1_000_001).is_err()); // over cap
|
| 762 |
813 |
|
}
|
|
814 |
+ |
|
|
815 |
+ |
#[test]
|
|
816 |
+ |
fn test_validate_chapter_title() {
|
|
817 |
+ |
assert!(validate_chapter_title("Introduction").is_ok());
|
|
818 |
+ |
assert!(validate_chapter_title("X").is_ok()); // single char
|
|
819 |
+ |
assert!(validate_chapter_title("").is_err()); // empty
|
|
820 |
+ |
assert!(validate_chapter_title(&"a".repeat(200)).is_ok()); // at limit
|
|
821 |
+ |
assert!(validate_chapter_title(&"a".repeat(201)).is_err()); // over limit
|
|
822 |
+ |
}
|
|
823 |
+ |
|
|
824 |
+ |
#[test]
|
|
825 |
+ |
fn test_validate_item_text_body() {
|
|
826 |
+ |
assert!(validate_item_text_body("Some content").is_ok());
|
|
827 |
+ |
assert!(validate_item_text_body("").is_ok()); // empty is valid
|
|
828 |
+ |
assert!(validate_item_text_body(&"a".repeat(500_000)).is_ok()); // at limit
|
|
829 |
+ |
assert!(validate_item_text_body(&"a".repeat(500_001)).is_err()); // over limit
|
|
830 |
+ |
}
|
|
831 |
+ |
|
|
832 |
+ |
#[test]
|
|
833 |
+ |
fn test_validate_git_repo_name() {
|
|
834 |
+ |
// Valid names
|
|
835 |
+ |
assert!(validate_git_repo_name("my-repo").is_ok());
|
|
836 |
+ |
assert!(validate_git_repo_name("my_repo").is_ok());
|
|
837 |
+ |
assert!(validate_git_repo_name("MyRepo123").is_ok());
|
|
838 |
+ |
assert!(validate_git_repo_name("repo.name").is_ok());
|
|
839 |
+ |
assert!(validate_git_repo_name("a").is_ok()); // single char
|
|
840 |
+ |
assert!(validate_git_repo_name(&"a".repeat(64)).is_ok()); // at limit
|
|
841 |
+ |
|
|
842 |
+ |
// Invalid: empty
|
|
843 |
+ |
assert!(validate_git_repo_name("").is_err());
|
|
844 |
+ |
// Invalid: too long
|
|
845 |
+ |
assert!(validate_git_repo_name(&"a".repeat(65)).is_err());
|
|
846 |
+ |
// Invalid: leading dot
|
|
847 |
+ |
assert!(validate_git_repo_name(".hidden").is_err());
|
|
848 |
+ |
// Invalid: spaces
|
|
849 |
+ |
assert!(validate_git_repo_name("my repo").is_err());
|
|
850 |
+ |
// Invalid: slashes
|
|
851 |
+ |
assert!(validate_git_repo_name("foo/bar").is_err());
|
|
852 |
+ |
// Invalid: special chars
|
|
853 |
+ |
assert!(validate_git_repo_name("repo@name").is_err());
|
|
854 |
+ |
assert!(validate_git_repo_name("repo!").is_err());
|
|
855 |
+ |
}
|
| 763 |
856 |
|
}
|