using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; namespace ChaosBot.WebServer.Services { public class ValidationService { public bool Validate(JsonElement requestBody, Dictionary> getValidationRules, out string error) { StringBuilder errorBuilder = new StringBuilder(); foreach (KeyValuePair> validationRule in getValidationRules) { string key = validationRule.Key; dynamic value = JsonElementHelper.GetValueFromRequest(requestBody, key); List rules = validationRule.Value; foreach (string rule in rules) { string[] ruleParts = rule.Split(':'); string ruleType = ruleParts.First(); if (ruleType == null) continue; ValidationResult result = ruleType switch { "required" => CheckRequired(key, value), "type" => CheckType(key, value, ruleParts), "min" => CheckMin(key, value, ruleParts), "max" => CheckMax(key, value, ruleParts), _ => new ValidationResult.Unknown() }; if (result.GetError() != null) { errorBuilder.AppendLine($"[{result.GetKey()}] {result.GetError()}"); break; } } } error = errorBuilder.ToString(); return error.Length == 0; } private ValidationResult CheckRequired(string key, dynamic value) { if (value != null) return new ValidationResult.Success(); return new ValidationResult.Failure(key, $"{key} is required"); } private ValidationResult CheckType(string key, dynamic value, string[] ruleParts) { if (ruleParts.Length != 2) return new ValidationResult.Unknown(); Type type = ruleParts[1] switch { "string" => typeof(string), "boolean" => typeof(bool), "short" => typeof(short), "ushort" => typeof(ushort), "int" => typeof(int), "uint" => typeof(uint), "long" => typeof(long), "ulong" => typeof(ulong), _ => null }; if (type == null) return new ValidationResult.Unknown(); dynamic val = Convert.ChangeType(value, type); if (val != null) return new ValidationResult.Success(); return new ValidationResult.Failure(key, $"{key} could not be interpreted as {ruleParts[1]}"); } private ValidationResult CheckMin(string key, dynamic value, string[] ruleParts) { if (ruleParts.Length != 2) return new ValidationResult.Unknown(); if (value is string stringValue) { int minLength = Convert.ToInt32(ruleParts[1]); if (stringValue.Length < minLength) return new ValidationResult.Failure(key, $"{key} cannot be shorter than {minLength} characters"); } else if (value is ulong intValue) { ulong minSize = Convert.ToUInt64(ruleParts[1]); if (intValue < minSize) return new ValidationResult.Failure(key, $"{key} must be greater than or equal to {minSize}"); } else if (value is double floatValue) { double minSize = Convert.ToDouble(ruleParts[1]); if (floatValue < minSize) return new ValidationResult.Failure(key, $"{key} must be greater than or equal to {minSize}"); } else { return new ValidationResult.Unknown(); } return new ValidationResult.Success(); } private ValidationResult CheckMax(string key, dynamic value, string[] ruleParts) { if (ruleParts.Length != 2) return new ValidationResult.Unknown(); if (value is string stringValue) { int maxLength = Convert.ToInt32(ruleParts[1]); if (stringValue.Length > maxLength) return new ValidationResult.Failure(key, $"{key} cannot be longer than {maxLength} characters"); } else if (value is ulong intValue) { ulong maxSize = Convert.ToUInt64(ruleParts[1]); if (intValue > maxSize) return new ValidationResult.Failure(key, $"{key} must be less than or equal to {maxSize}"); } else if (value is double floatValue) { double maxSize = Convert.ToDouble(ruleParts[1]); if (floatValue > maxSize) return new ValidationResult.Failure(key, $"{key} must be less than or equal to {maxSize}"); } else { return new ValidationResult.Unknown(); } return new ValidationResult.Success(); } } internal class ValidationResult { private readonly string _errorMessage; private readonly string _key; private ValidationResult() { _errorMessage = null; _key = null; } private ValidationResult(string key, string errorMessage) { _errorMessage = errorMessage; _key = key; } internal class Success : ValidationResult {} internal class Failure : ValidationResult { public Failure(string key, string message) : base(key, message) {} } internal class Unknown : ValidationResult {} public string GetError() { return this._errorMessage; } public string GetKey() { return this._key; } } }