From ac769e656b122ff569c3f1534701b71e00fed586 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 27 Oct 2020 19:21:35 +0800 Subject: Split front and back end. --- .../Validation/GeneralTimelineNameValidator.cs | 33 ++++++ .../Timeline/Models/Validation/NameValidator.cs | 42 +++++++ .../Models/Validation/NicknameValidator.cs | 25 ++++ .../Models/Validation/TimelineNameValidator.cs | 19 +++ .../Models/Validation/UsernameValidator.cs | 19 +++ BackEnd/Timeline/Models/Validation/Validator.cs | 127 +++++++++++++++++++++ 6 files changed, 265 insertions(+) create mode 100644 BackEnd/Timeline/Models/Validation/GeneralTimelineNameValidator.cs create mode 100644 BackEnd/Timeline/Models/Validation/NameValidator.cs create mode 100644 BackEnd/Timeline/Models/Validation/NicknameValidator.cs create mode 100644 BackEnd/Timeline/Models/Validation/TimelineNameValidator.cs create mode 100644 BackEnd/Timeline/Models/Validation/UsernameValidator.cs create mode 100644 BackEnd/Timeline/Models/Validation/Validator.cs (limited to 'BackEnd/Timeline/Models/Validation') diff --git a/BackEnd/Timeline/Models/Validation/GeneralTimelineNameValidator.cs b/BackEnd/Timeline/Models/Validation/GeneralTimelineNameValidator.cs new file mode 100644 index 00000000..e1c96fbd --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/GeneralTimelineNameValidator.cs @@ -0,0 +1,33 @@ +using System; + +namespace Timeline.Models.Validation +{ + public class GeneralTimelineNameValidator : Validator + { + private readonly UsernameValidator _usernameValidator = new UsernameValidator(); + private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); + + protected override (bool, string) DoValidate(string value) + { + if (value.StartsWith('@')) + { + return _usernameValidator.Validate(value.Substring(1)); + } + else + { + return _timelineNameValidator.Validate(value); + } + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] + public class GeneralTimelineNameAttribute : ValidateWithAttribute + { + public GeneralTimelineNameAttribute() + : base(typeof(GeneralTimelineNameValidator)) + { + + } + } +} diff --git a/BackEnd/Timeline/Models/Validation/NameValidator.cs b/BackEnd/Timeline/Models/Validation/NameValidator.cs new file mode 100644 index 00000000..b74c40b7 --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/NameValidator.cs @@ -0,0 +1,42 @@ +using System.Linq; +using System.Text.RegularExpressions; +using static Timeline.Resources.Models.Validation.NameValidator; + +namespace Timeline.Models.Validation +{ + public class NameValidator : Validator + { + private static Regex UniqueIdRegex { get; } = new Regex(@"^[a-zA-Z0-9]{32}$"); + + public const int MaxLength = 26; + + protected override (bool, string) DoValidate(string value) + { + if (value.Length == 0) + { + return (false, MessageEmptyString); + } + + if (value.Length > MaxLength) + { + return (false, MessageTooLong); + } + + foreach ((char c, int i) in value.Select((c, i) => (c, i))) + { + if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_')) + { + return (false, MessageInvalidChar); + } + } + + // Currently name can't be longer than 26. So this is not needed. But reserve it for future use. + if (UniqueIdRegex.IsMatch(value)) + { + return (false, MessageUnqiueId); + } + + return (true, GetSuccessMessage()); + } + } +} diff --git a/BackEnd/Timeline/Models/Validation/NicknameValidator.cs b/BackEnd/Timeline/Models/Validation/NicknameValidator.cs new file mode 100644 index 00000000..1d6ab163 --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/NicknameValidator.cs @@ -0,0 +1,25 @@ +using System; +using static Timeline.Resources.Models.Validation.NicknameValidator; + +namespace Timeline.Models.Validation +{ + public class NicknameValidator : Validator + { + protected override (bool, string) DoValidate(string value) + { + if (value.Length > 25) + return (false, MessageTooLong); + + return (true, GetSuccessMessage()); + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public class NicknameAttribute : ValidateWithAttribute + { + public NicknameAttribute() : base(typeof(NicknameValidator)) + { + + } + } +} diff --git a/BackEnd/Timeline/Models/Validation/TimelineNameValidator.cs b/BackEnd/Timeline/Models/Validation/TimelineNameValidator.cs new file mode 100644 index 00000000..f1ab54e8 --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/TimelineNameValidator.cs @@ -0,0 +1,19 @@ +using System; + +namespace Timeline.Models.Validation +{ + public class TimelineNameValidator : NameValidator + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] + public class TimelineNameAttribute : ValidateWithAttribute + { + public TimelineNameAttribute() + : base(typeof(TimelineNameValidator)) + { + + } + } +} diff --git a/BackEnd/Timeline/Models/Validation/UsernameValidator.cs b/BackEnd/Timeline/Models/Validation/UsernameValidator.cs new file mode 100644 index 00000000..87bbf85f --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/UsernameValidator.cs @@ -0,0 +1,19 @@ +using System; + +namespace Timeline.Models.Validation +{ + public class UsernameValidator : NameValidator + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] + public class UsernameAttribute : ValidateWithAttribute + { + public UsernameAttribute() + : base(typeof(UsernameValidator)) + { + + } + } +} diff --git a/BackEnd/Timeline/Models/Validation/Validator.cs b/BackEnd/Timeline/Models/Validation/Validator.cs new file mode 100644 index 00000000..aef7891c --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/Validator.cs @@ -0,0 +1,127 @@ +using System; +using System.ComponentModel.DataAnnotations; +using static Timeline.Resources.Models.Validation.Validator; + +namespace Timeline.Models.Validation +{ + /// + /// A validator to validate value. + /// + public interface IValidator + { + /// + /// Validate given value. + /// + /// The value to validate. + /// Validation success or not and message. + (bool, string) Validate(object? value); + } + + public static class ValidatorExtensions + { + public static bool Validate(this IValidator validator, object? value, out string message) + { + if (validator == null) + throw new ArgumentNullException(nameof(validator)); + + var (r, m) = validator.Validate(value); + message = m; + return r; + } + } + + /// + /// Convenient base class for validator. + /// + /// The type of accepted value. + /// + /// Subclass should override to do the real validation. + /// This class will check the nullity and type of value. + /// If value is null, it will pass or fail depending on . + /// If value is not null and not of type + /// it will fail and not call . + /// + /// is true by default. + /// + /// If you want some other behaviours, write the validator from scratch. + /// + public abstract class Validator : IValidator + { + protected bool PermitNull { get; set; } = true; + + public (bool, string) Validate(object? value) + { + if (value == null) + { + if (PermitNull) + return (true, GetSuccessMessage()); + else + return (false, ValidatorMessageNull); + } + + if (value is T v) + { + return DoValidate(v); + } + else + { + return (false, ValidatorMessageBadType); + } + } + + protected static string GetSuccessMessage() => ValidatorMessageSuccess; + + protected abstract (bool, string) DoValidate(T value); + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] + public class ValidateWithAttribute : ValidationAttribute + { + private readonly IValidator _validator; + + /// + /// Create with a given validator. + /// + /// The validator used to validate. + public ValidateWithAttribute(IValidator validator) + { + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); + } + + /// + /// Create the validator with default constructor. + /// + /// The type of the validator. + public ValidateWithAttribute(Type validatorType) + { + if (validatorType == null) + throw new ArgumentNullException(nameof(validatorType)); + + if (!typeof(IValidator).IsAssignableFrom(validatorType)) + throw new ArgumentException(ValidateWithAttributeExceptionNotValidator, nameof(validatorType)); + + try + { + _validator = (Activator.CreateInstance(validatorType) as IValidator)!; + } + catch (Exception e) + { + throw new ArgumentException(ValidateWithAttributeExceptionCreateFail, e); + } + } + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var (result, message) = _validator.Validate(value); + if (result) + { + return ValidationResult.Success; + } + else + { + return new ValidationResult(message); + } + } + } +} -- cgit v1.2.3