CommentDraftValidator.swift 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import Foundation
  2. enum CommentDraftValidationError: LocalizedError {
  3. case emptySubreddit
  4. case invalidSubreddit
  5. case emptyPostTitle
  6. case emptyParentComment
  7. case emptyComment
  8. case commentTooLong
  9. case invalidPostURL
  10. var errorDescription: String? {
  11. switch self {
  12. case .emptySubreddit:
  13. "Enter a subreddit to tailor the comment."
  14. case .invalidSubreddit:
  15. "Subreddit names must be 3–21 characters and use only letters, numbers, or underscores."
  16. case .emptyPostTitle:
  17. "Enter the post title so the AI understands the thread context."
  18. case .emptyParentComment:
  19. "Paste the comment you're replying to."
  20. case .emptyComment:
  21. "Write or generate a comment before copying."
  22. case .commentTooLong:
  23. "Comments must be 10,000 characters or fewer."
  24. case .invalidPostURL:
  25. "Enter a valid Reddit post URL (https://reddit.com/…)."
  26. }
  27. }
  28. }
  29. enum CommentDraftValidator {
  30. static let maxCommentLength = 10_000
  31. static func validateForGeneration(from draft: CommentDraft) throws {
  32. guard !draft.subreddit.trimmingCharacters(in: .whitespaces).isEmpty else {
  33. throw CommentDraftValidationError.emptySubreddit
  34. }
  35. guard PostDraftValidator.isValidSubreddit(draft.subreddit) else {
  36. throw CommentDraftValidationError.invalidSubreddit
  37. }
  38. guard !draft.postTitle.trimmingCharacters(in: .whitespaces).isEmpty else {
  39. throw CommentDraftValidationError.emptyPostTitle
  40. }
  41. if draft.commentType == .reply {
  42. guard !draft.parentComment.trimmingCharacters(in: .whitespaces).isEmpty else {
  43. throw CommentDraftValidationError.emptyParentComment
  44. }
  45. }
  46. }
  47. static func validateForExport(_ draft: CommentDraft) throws {
  48. try validateForGeneration(from: draft)
  49. let body = draft.body.trimmingCharacters(in: .whitespaces)
  50. guard !body.isEmpty else { throw CommentDraftValidationError.emptyComment }
  51. guard body.count <= maxCommentLength else { throw CommentDraftValidationError.commentTooLong }
  52. let url = draft.postURL.trimmingCharacters(in: .whitespaces)
  53. if !url.isEmpty, !isValidRedditURL(url) {
  54. throw CommentDraftValidationError.invalidPostURL
  55. }
  56. }
  57. static func canGenerate(from draft: CommentDraft, isGenerating: Bool) -> Bool {
  58. guard !isGenerating else { return false }
  59. return (try? validateForGeneration(from: draft)) != nil
  60. }
  61. static func canExport(_ draft: CommentDraft) -> Bool {
  62. (try? validateForExport(draft)) != nil
  63. }
  64. static func isValidRedditURL(_ string: String) -> Bool {
  65. let trimmed = string.trimmingCharacters(in: .whitespaces)
  66. guard let url = URL(string: trimmed),
  67. let scheme = url.scheme?.lowercased(),
  68. scheme == "http" || scheme == "https",
  69. let host = url.host?.lowercased(),
  70. host.contains("reddit.com")
  71. else { return false }
  72. return true
  73. }
  74. }