diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift index 9f0717a1..8045ed0a 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Target.swift @@ -134,6 +134,10 @@ public extension TargetDependency.Core { } public extension TargetDependency.Shared { + static let Validator = TargetDependency.project( + target: ModulePaths.Shared.Validator.targetName(type: .sources), + path: .relativeToShared(ModulePaths.Shared.Validator.rawValue) + ) static let FoundationUtil = TargetDependency.project( target: ModulePaths.Shared.FoundationUtil.targetName(type: .sources), path: .relativeToShared(ModulePaths.Shared.FoundationUtil.rawValue) diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift index ab5ddfad..8e4781ef 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift @@ -52,6 +52,7 @@ public extension ModulePaths { public extension ModulePaths { enum Shared: String { + case Validator case FoundationUtil case ViewUtil case DateUtil diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index e618ff7c..63a039be 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -299,7 +299,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi #if !NEEDLE_DYNAMIC -private func register1() { +@inline(never) private func register1() { registerProviderFactory("^->AppComponent->JwtStoreComponent", factoryb27d5aae1eb7e73575a6f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->AppComponent->KeychainComponent", factoryEmptyDependencyProvider) diff --git a/Projects/Feature/BaseFeature/Project.swift b/Projects/Feature/BaseFeature/Project.swift index 46f6d607..f45058aa 100644 --- a/Projects/Feature/BaseFeature/Project.swift +++ b/Projects/Feature/BaseFeature/Project.swift @@ -14,6 +14,7 @@ let project = Project.makeModule( .Shared.DateUtil, .Shared.FoundationUtil, .Shared.GlobalThirdPartyLibrary, + .Shared.Validator, .Shared.UtilityModule ] ) diff --git a/Projects/Shared/Validator/Project.swift b/Projects/Shared/Validator/Project.swift new file mode 100644 index 00000000..d6bf8207 --- /dev/null +++ b/Projects/Shared/Validator/Project.swift @@ -0,0 +1,10 @@ +import ProjectDescription +import ProjectDescriptionHelpers +import DependencyPlugin + +let project = Project.makeModule( + name: ModulePaths.Shared.Validator.rawValue, + product: .staticLibrary, + targets: [.unitTest], + internalDependencies: [] +) diff --git a/Projects/Shared/Validator/Sources/EmailValidator.swift b/Projects/Shared/Validator/Sources/EmailValidator.swift new file mode 100644 index 00000000..626fa025 --- /dev/null +++ b/Projects/Shared/Validator/Sources/EmailValidator.swift @@ -0,0 +1,13 @@ +import Foundation + +public struct EmailValidator: Validator { + private let regexValidator = RegexValidator( + pattern: "^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$" + ) + + public init() {} + + public func validate(_ value: String) -> Bool { + regexValidator.validate(value) + } +} diff --git a/Projects/Shared/Validator/Sources/RegexValidator.swift b/Projects/Shared/Validator/Sources/RegexValidator.swift new file mode 100644 index 00000000..00d9053b --- /dev/null +++ b/Projects/Shared/Validator/Sources/RegexValidator.swift @@ -0,0 +1,26 @@ +import Foundation + +public struct RegexValidator: Validator { + private let regexPattern: String + private let regexOptions: NSRegularExpression.Options + private let matchingOptions: NSRegularExpression.MatchingOptions + + public init( + pattern: String, + regexOptions: NSRegularExpression.Options = [], + matchingOptions: NSRegularExpression.MatchingOptions = [] + ) { + self.regexPattern = pattern + self.regexOptions = regexOptions + self.matchingOptions = matchingOptions + } + + public func validate(_ value: String) -> Bool { + guard let regex = try? NSRegularExpression( + pattern: regexPattern, + options: regexOptions + ) else { return false } + let regexNSRange = (value as NSString).range(of: value) + return regex.firstMatch(in: value, options: matchingOptions, range: regexNSRange) != nil + } +} diff --git a/Projects/Shared/Validator/Sources/StringSizeValidator.swift b/Projects/Shared/Validator/Sources/StringSizeValidator.swift new file mode 100644 index 00000000..1a1c7a72 --- /dev/null +++ b/Projects/Shared/Validator/Sources/StringSizeValidator.swift @@ -0,0 +1,15 @@ +import Foundation + +public struct StringSizeValidator: Validator { + private let min: Int + private let max: Int + + public init(min: Int = .zero, max: Int = .max) { + self.min = min + self.max = max + } + + public func validate(_ value: String) -> Bool { + value.count >= min && value.count <= max + } +} diff --git a/Projects/Shared/Validator/Sources/URLValidator.swift b/Projects/Shared/Validator/Sources/URLValidator.swift new file mode 100644 index 00000000..01ab6ea9 --- /dev/null +++ b/Projects/Shared/Validator/Sources/URLValidator.swift @@ -0,0 +1,14 @@ +import Foundation + +public struct URLValidator: Validator { + private let regexValidator = RegexValidator( + pattern: "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#()?&//=]*)" + ) + + public init() {} + + public func validate(_ value: String) -> Bool { + regexValidator.validate(value) + } + +} diff --git a/Projects/Shared/Validator/Sources/Validator.swift b/Projects/Shared/Validator/Sources/Validator.swift new file mode 100644 index 00000000..07a91b2a --- /dev/null +++ b/Projects/Shared/Validator/Sources/Validator.swift @@ -0,0 +1,5 @@ +public protocol Validator { + associatedtype WrappedType + + func validate(_ value: WrappedType) -> Bool +} diff --git a/Projects/Shared/Validator/Tests/EmailValidatorTests.swift b/Projects/Shared/Validator/Tests/EmailValidatorTests.swift new file mode 100644 index 00000000..8c5c2736 --- /dev/null +++ b/Projects/Shared/Validator/Tests/EmailValidatorTests.swift @@ -0,0 +1,26 @@ +import XCTest +@testable import Validator + +final class EmailValidatorTests: XCTestCase { + var sut: EmailValidator! + + override func setUp() { + sut = EmailValidator() + } + + func test_validate_valid_email() { + let validResult1 = sut.validate("test@gmail.com") + XCTAssertTrue(validResult1) + + let validResult2 = sut.validate("test@gsm.hs.kr") + XCTAssertTrue(validResult2) + } + + func test_validate_invalid_email() { + let invalidResult1 = sut.validate("testgmailcoom") + XCTAssertFalse(invalidResult1) + + let invalidResult2 = sut.validate("test@gmailcoom") + XCTAssertFalse(invalidResult2) + } +} diff --git a/Projects/Shared/Validator/Tests/RegexValidatorTests.swift b/Projects/Shared/Validator/Tests/RegexValidatorTests.swift new file mode 100644 index 00000000..33330fbc --- /dev/null +++ b/Projects/Shared/Validator/Tests/RegexValidatorTests.swift @@ -0,0 +1,24 @@ +import Validator +import XCTest + +final class RegexValidatorTests: XCTestCase { + func test_only_string_or_number_validate() { + let validator = RegexValidator(pattern: "^[a-zA-Z0-9]+$") + XCTAssertTrue(validator.validate("abc")) + XCTAssertFalse(validator.validate("abc!")) + } + + func test_only_special_characters_validate() { + let validator = RegexValidator(pattern: "^[^a-zA-Z0-9]+$") + XCTAssertTrue(validator.validate("!@#$%^&*()")) + XCTAssertFalse(validator.validate("abc")) + } + + func test_email_validate() { + let validator = RegexValidator(pattern: "^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$") + XCTAssertTrue(validator.validate("test@gmail.com")) + XCTAssertTrue(validator.validate("test@gsm.hs.kr")) + XCTAssertFalse(validator.validate("testgmail.com")) + XCTAssertFalse(validator.validate("test@gmailcom")) + } +} diff --git a/Projects/Shared/Validator/Tests/StringSizeValidatorTests.swift b/Projects/Shared/Validator/Tests/StringSizeValidatorTests.swift new file mode 100644 index 00000000..ce7d66b7 --- /dev/null +++ b/Projects/Shared/Validator/Tests/StringSizeValidatorTests.swift @@ -0,0 +1,18 @@ +import Validator +import XCTest + +final class StringSizeValidatorTests: XCTestCase { + func test_validate_valid_string() { + let validator = StringSizeValidator(min: 1, max: 10) + XCTAssertTrue(validator.validate("a")) + XCTAssertTrue(validator.validate("abc")) + XCTAssertTrue(validator.validate("abcde")) + XCTAssertTrue(validator.validate("abcdefghij")) + } + + func test_validate_invalid_string() { + let validator = StringSizeValidator(min: 1, max: 10) + XCTAssertFalse(validator.validate("")) + XCTAssertFalse(validator.validate("abcdeABCDEa")) + } +} diff --git a/Projects/Shared/Validator/Tests/URLValidatorTests.swift b/Projects/Shared/Validator/Tests/URLValidatorTests.swift new file mode 100644 index 00000000..f3326dfe --- /dev/null +++ b/Projects/Shared/Validator/Tests/URLValidatorTests.swift @@ -0,0 +1,28 @@ +import Validator +import XCTest + +final class URLValidatorTests: XCTestCase { + var sut: URLValidator! + + override func setUp() { + super.setUp() + sut = URLValidator() + } + + func test_validate_valid_url() { + XCTAssertTrue(sut.validate("https://www.google.com")) + XCTAssertTrue(sut.validate("http://www.google.com")) + XCTAssertTrue(sut.validate("http://google.com")) + XCTAssertTrue(sut.validate("https://google.com")) + XCTAssertTrue(sut.validate("https://www.naver.com")) + XCTAssertTrue(sut.validate("https://github.com")) + XCTAssertTrue(sut.validate("http://github.com")) + XCTAssertTrue(sut.validate("https://www.apple.com")) + } + + func test_validate_invalid_url() { + XCTAssertFalse(sut.validate("www.google")) + XCTAssertFalse(sut.validate("www.naver.com")) + XCTAssertFalse(sut.validate("github.com")) + } +}