diff --git a/Sources/DiscogsService/Internal/Extensions/String+Functions.swift b/Sources/DiscogsService/Internal/Extensions/String+Functions.swift new file mode 100644 index 000000000..abf2a7756 --- /dev/null +++ b/Sources/DiscogsService/Internal/Extensions/String+Functions.swift @@ -0,0 +1,45 @@ +// ===----------------------------------------------------------------------=== +// +// This source file is part of the DiscogsService open source project +// +// Copyright (c) 2025 Röck+Cöde VoF. and the DiscogsService project authors +// Licensed under Apache license v2.0 +// +// See LICENSE for license information +// See CONTRIBUTORS for the list of DiscogsService project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +// ===----------------------------------------------------------------------=== + +import Foundation + +extension String { + + // MARK: Functions + + /// Checks whether a regular expression pattern fully matches a string or not. + /// - Parameter pattern: A regular expression pattern to match a string against. + /// - Returns: A flag that indicates whether a given pattern fully matches a string or not. + func fullyMatch(pattern: String) -> Bool { + do { + if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 6.0, *) { + let securityInput = try Regex(pattern) + let matches = self.wholeMatch(of: securityInput) + + return matches != nil + } else { + let securityInput = try NSRegularExpression(pattern: pattern) + let matches = securityInput.matches( + in: self, + range: .init(location: 0, length: count) + ) + + return !matches.isEmpty + } + } catch { + return false + } + } + +} diff --git a/Sources/DiscogsService/Internal/Validation Rules/SecureValidationRule.swift b/Sources/DiscogsService/Internal/Validation Rules/SecureValidationRule.swift index 354b759a3..1873ed55a 100644 --- a/Sources/DiscogsService/Internal/Validation Rules/SecureValidationRule.swift +++ b/Sources/DiscogsService/Internal/Validation Rules/SecureValidationRule.swift @@ -77,32 +77,6 @@ private extension SecureValidationRule { // MARK: Functions - /// Checks if a given input is valid, - /// - Parameter input: An input to validate. - /// - Returns: A flag that indicates whether a given input is valid or not. - func isValid(_ input: String) -> Bool { - let regexPattern = String(format: .Pattern.securityInput, inputType.rawValue) - - do { - if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 6.0, *) { - let securityInput = try Regex(regexPattern) - let matches = input.matches(of: securityInput) - - return !matches.isEmpty - } else { - let securityInput = try NSRegularExpression(pattern: regexPattern) - let matches = securityInput.matches( - in: input, - range: .init(location: 0, length: input.count) - ) - - return !matches.isEmpty - } - } catch { - return false - } - } - /// Validates a given input. /// /// > note: This helper function would not be necessary when support for *Swift 5.10* is discontinued. @@ -114,7 +88,10 @@ private extension SecureValidationRule { guard let input else { return false } - guard isValid(input) else { + + guard input.fullyMatch( + pattern: .init(format: .Pattern.securityInput, inputType.rawValue) + ) else { switch inputType { case .consumerKey: throw InputValidationError.inputNotConsumerKey case .consumerSecret: throw InputValidationError.inputNotConsumerSecret diff --git a/Tests/DiscogsService/Cases/Internal/Extensions/String+FunctionsTests.swift b/Tests/DiscogsService/Cases/Internal/Extensions/String+FunctionsTests.swift new file mode 100644 index 000000000..78f756bc4 --- /dev/null +++ b/Tests/DiscogsService/Cases/Internal/Extensions/String+FunctionsTests.swift @@ -0,0 +1,99 @@ +// ===----------------------------------------------------------------------=== +// +// This source file is part of the DiscogsService open source project +// +// Copyright (c) 2025 Röck+Cöde VoF. and the DiscogsService project authors +// Licensed under Apache license v2.0 +// +// See LICENSE for license information +// See CONTRIBUTORS for the list of DiscogsService project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +// ===----------------------------------------------------------------------=== + +import Testing + +@testable import DiscogsService + +@Suite("String Functions", .tags(.extension)) +struct StringFunctionsTests { + + // MARK: Functions tests + +#if swift(>=6.2) + @Test(arguments: zip( + Input.stringsToMatch, + Output.stringsToMatch + )) + func `fully match`( + string: String, + expects isMatch: Bool + ) { + assertFullyMatch( + string: string, + pattern: .Pattern.sample, + expects: isMatch + ) + } +#else + @Test("fully match", arguments: zip( + Input.stringsToMatch, + Output.stringsToMatch + )) + func fullyMatch( + string: String, + expects isMatch: Bool + ) { + assertFullyMatch( + string: string, + pattern: .Pattern.sample, + expects: isMatch + ) + } +#endif + +} + +// MARK: - Assertions + +private extension StringFunctionsTests { + + // MARK: Functions + + /// Asserts the result of the `fullyMatch` function. + /// - Parameters: + /// - string: A string to match against a pattern. + /// - pattern: A regular expression pattern to match a string against. + /// - isMatch: An expected flag that indicates whether there is a match or not. + func assertFullyMatch( + string: String, + pattern: String, + expects isMatch: Bool + ) { + // GIVEN + // WHEN + let result = string.fullyMatch(pattern: pattern) + + // THEN + #expect(result == isMatch) + } + +} + +// MARK: - Constants + +private extension Input { + /// A list of strings to match against a regular expression pattern in test cases. + static let stringsToMatch: [String] = ["Some Pattern", "Some", "Some Other Pattern", "Pattern", .empty] +} + +private extension Output { + /// A list of expected results from matching a sample string against a sample regular expression pattern in test cases. + static let stringsToMatch: [Bool] = [true, false, false, false, false] +} + +private extension String.Pattern { + /// A sample regular expression pattern to match against. + static let sample = "Some Pattern" +} diff --git a/Tests/DiscogsService/Types/Extensions/Tag+Customs.swift b/Tests/DiscogsService/Types/Extensions/Tag+Customs.swift index 85184ee9c..6e41a6809 100644 --- a/Tests/DiscogsService/Types/Extensions/Tag+Customs.swift +++ b/Tests/DiscogsService/Types/Extensions/Tag+Customs.swift @@ -18,6 +18,9 @@ extension Tag { // MARK: Constants + /// A tag that indicates tests for a type extension. + @Tag static var `extension`: Self + /// A tag that indicates tests for a middleware type. @Tag static var middleware: Self