feat:(first commit)created repository and complete 0.1.0
This commit is contained in:
270
src/utils/validators.rs
Normal file
270
src/utils/validators.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use anyhow::{bail, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
/// Conventional commit types
|
||||
pub const CONVENTIONAL_TYPES: &[&str] = &[
|
||||
"feat", // A new feature
|
||||
"fix", // A bug fix
|
||||
"docs", // Documentation only changes
|
||||
"style", // Changes that do not affect the meaning of the code
|
||||
"refactor", // A code change that neither fixes a bug nor adds a feature
|
||||
"perf", // A code change that improves performance
|
||||
"test", // Adding missing tests or correcting existing tests
|
||||
"build", // Changes that affect the build system or external dependencies
|
||||
"ci", // Changes to CI configuration files and scripts
|
||||
"chore", // Other changes that don't modify src or test files
|
||||
"revert", // Reverts a previous commit
|
||||
];
|
||||
|
||||
/// Commitlint configuration types (extends conventional)
|
||||
pub const COMMITLINT_TYPES: &[&str] = &[
|
||||
"feat", // A new feature
|
||||
"fix", // A bug fix
|
||||
"docs", // Documentation only changes
|
||||
"style", // Changes that do not affect the meaning of the code
|
||||
"refactor", // A code change that neither fixes a bug nor adds a feature
|
||||
"perf", // A code change that improves performance
|
||||
"test", // Adding missing tests or correcting existing tests
|
||||
"build", // Changes that affect the build system or external dependencies
|
||||
"ci", // Changes to CI configuration files and scripts
|
||||
"chore", // Other changes that don't modify src or test files
|
||||
"revert", // Reverts a previous commit
|
||||
"wip", // Work in progress
|
||||
"init", // Initial commit
|
||||
"update", // Update existing functionality
|
||||
"remove", // Remove functionality
|
||||
"security", // Security-related changes
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
/// Regex for conventional commit format
|
||||
static ref CONVENTIONAL_COMMIT_REGEX: Regex = Regex::new(
|
||||
r"^(?P<type>feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?: (?P<description>.+)$"
|
||||
).unwrap();
|
||||
|
||||
/// Regex for scope validation
|
||||
static ref SCOPE_REGEX: Regex = Regex::new(
|
||||
r"^[a-z0-9-]+$"
|
||||
).unwrap();
|
||||
|
||||
/// Regex for version tag validation (semver)
|
||||
static ref SEMVER_REGEX: Regex = Regex::new(
|
||||
r"^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
).unwrap();
|
||||
|
||||
/// Regex for email validation
|
||||
static ref EMAIL_REGEX: Regex = Regex::new(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||||
).unwrap();
|
||||
|
||||
/// Regex for SSH key validation (basic)
|
||||
static ref SSH_KEY_REGEX: Regex = Regex::new(
|
||||
r"^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521)\s+[A-Za-z0-9+/]+={0,2}\s+.*$"
|
||||
).unwrap();
|
||||
|
||||
/// Regex for GPG key ID validation
|
||||
static ref GPG_KEY_ID_REGEX: Regex = Regex::new(
|
||||
r"^[A-F0-9]{16,40}$"
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
/// Validate conventional commit message
|
||||
pub fn validate_conventional_commit(message: &str) -> Result<()> {
|
||||
let first_line = message.lines().next().unwrap_or("");
|
||||
|
||||
if !CONVENTIONAL_COMMIT_REGEX.is_match(first_line) {
|
||||
bail!(
|
||||
"Invalid conventional commit format. Expected: <type>[optional scope]: <description>\n\
|
||||
Valid types: {}",
|
||||
CONVENTIONAL_TYPES.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
// Check description length (max 100 chars for first line)
|
||||
if first_line.len() > 100 {
|
||||
bail!("Commit subject too long (max 100 characters)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate @commitlint commit message
|
||||
pub fn validate_commitlint_commit(message: &str) -> Result<()> {
|
||||
let first_line = message.lines().next().unwrap_or("");
|
||||
|
||||
// Commitlint is more lenient but still requires type prefix
|
||||
let parts: Vec<&str> = first_line.splitn(2, ':').collect();
|
||||
if parts.len() != 2 {
|
||||
bail!("Invalid commit format. Expected: <type>[optional scope]: <subject>");
|
||||
}
|
||||
|
||||
let type_part = parts[0];
|
||||
let subject = parts[1].trim();
|
||||
|
||||
// Extract type (handle scope and breaking indicator)
|
||||
let commit_type = type_part
|
||||
.split('(')
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.trim_end_matches('!');
|
||||
|
||||
if !COMMITLINT_TYPES.contains(&commit_type) {
|
||||
bail!(
|
||||
"Invalid commit type: '{}'. Valid types: {}",
|
||||
commit_type,
|
||||
COMMITLINT_TYPES.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
// Validate subject
|
||||
if subject.is_empty() {
|
||||
bail!("Commit subject cannot be empty");
|
||||
}
|
||||
|
||||
if subject.len() < 4 {
|
||||
bail!("Commit subject too short (min 4 characters)");
|
||||
}
|
||||
|
||||
if subject.len() > 100 {
|
||||
bail!("Commit subject too long (max 100 characters)");
|
||||
}
|
||||
|
||||
// Subject should not start with uppercase
|
||||
if subject.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
|
||||
bail!("Commit subject should not start with uppercase letter");
|
||||
}
|
||||
|
||||
// Subject should not end with period
|
||||
if subject.ends_with('.') {
|
||||
bail!("Commit subject should not end with a period");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate scope name
|
||||
pub fn validate_scope(scope: &str) -> Result<()> {
|
||||
if scope.is_empty() {
|
||||
bail!("Scope cannot be empty");
|
||||
}
|
||||
|
||||
if !SCOPE_REGEX.is_match(scope) {
|
||||
bail!("Invalid scope format. Use lowercase letters, numbers, and hyphens only");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate semantic version tag
|
||||
pub fn validate_semver(version: &str) -> Result<()> {
|
||||
let version = version.trim_start_matches('v');
|
||||
|
||||
if !SEMVER_REGEX.is_match(version) {
|
||||
bail!(
|
||||
"Invalid semantic version format. Expected: MAJOR.MINOR.PATCH[-prerelease][+build]\n\
|
||||
Examples: 1.0.0, 1.2.3-beta, v2.0.0+build123"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate email address
|
||||
pub fn validate_email(email: &str) -> Result<()> {
|
||||
if !EMAIL_REGEX.is_match(email) {
|
||||
bail!("Invalid email address format");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate SSH key format
|
||||
pub fn validate_ssh_key(key: &str) -> Result<()> {
|
||||
if !SSH_KEY_REGEX.is_match(key.trim()) {
|
||||
bail!("Invalid SSH public key format");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate GPG key ID
|
||||
pub fn validate_gpg_key_id(key_id: &str) -> Result<()> {
|
||||
if !GPG_KEY_ID_REGEX.is_match(key_id) {
|
||||
bail!("Invalid GPG key ID format. Expected 16-40 hexadecimal characters");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate profile name
|
||||
pub fn validate_profile_name(name: &str) -> Result<()> {
|
||||
if name.is_empty() {
|
||||
bail!("Profile name cannot be empty");
|
||||
}
|
||||
|
||||
if name.len() > 50 {
|
||||
bail!("Profile name too long (max 50 characters)");
|
||||
}
|
||||
|
||||
if !name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
|
||||
bail!("Profile name can only contain letters, numbers, hyphens, and underscores");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if commit type is valid
|
||||
pub fn is_valid_commit_type(commit_type: &str, use_commitlint: bool) -> bool {
|
||||
let types = if use_commitlint {
|
||||
COMMITLINT_TYPES
|
||||
} else {
|
||||
CONVENTIONAL_TYPES
|
||||
};
|
||||
|
||||
types.contains(&commit_type)
|
||||
}
|
||||
|
||||
/// Get available commit types
|
||||
pub fn get_commit_types(use_commitlint: bool) -> &'static [&'static str] {
|
||||
if use_commitlint {
|
||||
COMMITLINT_TYPES
|
||||
} else {
|
||||
CONVENTIONAL_TYPES
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_validate_conventional_commit() {
|
||||
assert!(validate_conventional_commit("feat: add new feature").is_ok());
|
||||
assert!(validate_conventional_commit("fix(auth): fix login bug").is_ok());
|
||||
assert!(validate_conventional_commit("feat!: breaking change").is_ok());
|
||||
assert!(validate_conventional_commit("invalid: message").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_semver() {
|
||||
assert!(validate_semver("1.0.0").is_ok());
|
||||
assert!(validate_semver("v1.2.3").is_ok());
|
||||
assert!(validate_semver("2.0.0-beta.1").is_ok());
|
||||
assert!(validate_semver("invalid").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_email() {
|
||||
assert!(validate_email("test@example.com").is_ok());
|
||||
assert!(validate_email("invalid-email").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_profile_name() {
|
||||
assert!(validate_profile_name("personal").is_ok());
|
||||
assert!(validate_profile_name("work-company").is_ok());
|
||||
assert!(validate_profile_name("").is_err());
|
||||
assert!(validate_profile_name("invalid name").is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user