using JobTrackerApi.Models; namespace JobTrackerApi.Services; // Phase 3 (validation): guards against abuse/DoS on the career-profile write path, NOT content // completeness (that is CareerCompleteness's job — a WIP profile must always be savable). Enforces // item counts and string lengths so a single PUT cannot store an unbounded blob. Returns an error // string to reject, or null to accept. public static class CareerProfileValidator { private const int MaxItemsPerSection = 200; // generous; a real CV has < 50 private const int MaxListEntries = 200; // bullets/skills/details per item private const int MaxShortField = 500; // titles, names, single-line fields private const int MaxLongField = 5000; // a single bullet/summary line public static string? Validate(StructuredCvProfile p) { if (p.Jobs.Count > MaxItemsPerSection) return $"Too many experience entries (max {MaxItemsPerSection})."; if (p.Education.Count > MaxItemsPerSection) return $"Too many education entries (max {MaxItemsPerSection})."; if (p.Skills.Count > MaxItemsPerSection) return $"Too many skills (max {MaxItemsPerSection})."; if (p.Projects.Count > MaxItemsPerSection) return $"Too many projects (max {MaxItemsPerSection})."; if (p.Certifications.Count > MaxItemsPerSection) return $"Too many certifications (max {MaxItemsPerSection})."; if (p.Languages.Count > MaxItemsPerSection) return $"Too many languages (max {MaxItemsPerSection})."; if (p.Summary.Count > MaxListEntries) return $"Summary is too long (max {MaxListEntries} lines)."; foreach (var j in p.Jobs) { if (Over(j.Title, MaxShortField) || Over(j.Company, MaxShortField) || Over(j.Location, MaxShortField)) return "An experience field exceeds the allowed length."; if (j.Bullets.Count > MaxListEntries || j.Skills.Count > MaxListEntries) return "An experience has too many bullets/skills."; if (j.Bullets.Any(b => Over(b, MaxLongField))) return "An experience bullet is too long."; } foreach (var e in p.Education) { if (Over(e.Qualification, MaxShortField) || Over(e.Institution, MaxShortField)) return "An education field exceeds the allowed length."; if (e.Details.Count > MaxListEntries) return "An education entry has too many details."; } foreach (var pr in p.Projects) { if (Over(pr.Name, MaxShortField) || Over(pr.Role, MaxShortField)) return "A project field exceeds the allowed length."; if (pr.Bullets.Count > MaxListEntries || pr.Skills.Count > MaxListEntries) return "A project has too many bullets/skills."; } foreach (var s in p.Skills) if (Over(s, MaxShortField)) return "A skill entry is too long."; if (Over(p.Contact.FullName, MaxShortField) || Over(p.Contact.Email, MaxShortField) || Over(p.Contact.Headline, MaxShortField) || Over(p.Contact.Location, MaxShortField)) return "A contact field exceeds the allowed length."; return null; } private static bool Over(string? value, int max) => value is not null && value.Length > max; }