diff --git a/Sources/DiscogsService/Internal/Errors/InputValidationError.swift b/Sources/DiscogsService/Internal/Errors/InputValidationError.swift index 45901a584..60af7892d 100644 --- a/Sources/DiscogsService/Internal/Errors/InputValidationError.swift +++ b/Sources/DiscogsService/Internal/Errors/InputValidationError.swift @@ -18,6 +18,8 @@ enum InputValidationError: Error { case inputIsEmpty /// An input is nil. case inputIsNil + /// An input does not comply with the user agent name requirements. + case inputNotAgentName /// An input does not comply with the consumer key requirements. case inputNotConsumerKey /// An input does not comply with the consumer secret requirements. diff --git a/Sources/DiscogsService/Internal/Validation Rules/AgentNameValidationRule.swift b/Sources/DiscogsService/Internal/Validation Rules/AgentNameValidationRule.swift new file mode 100644 index 000000000..c64797d4c --- /dev/null +++ b/Sources/DiscogsService/Internal/Validation Rules/AgentNameValidationRule.swift @@ -0,0 +1,76 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +struct AgentNameValidationRule: InputValidationRule { + + // MARK: Functions + +#if swift(>=6.0) + func validate(_ input: String?) throws(InputValidationError) -> Bool { + try validate(input: input) + } +#else + func validate(_ input: String?) throws -> Bool { + try validate(input: input) + } +#endif + +} + +// MARK: - Definitions + +extension InputValidationRule where Self == AgentNameValidationRule { + + // MARK: Constants + + /// A validation rule that checks whether an input is camel-cased or not. + static var agentName: Self { .init() } + +} + +// MARK: - Helpers + +private extension AgentNameValidationRule { + + // MARK: Functions + + /// Validates a given input. + /// + /// > note: This helper function would not be necessary when support for *Swift 5.10* is discontinued. + /// + /// - Parameter input: An input to be validated. + /// - Returns: A flag that indicates whether a given input has been validated or not. + /// - Throws: An error of type ``InputValidatorError`` in case the validation failed. + func validate(input: String?) throws -> Bool { + guard let input else { + return false + } + + guard input.fullyMatch( + pattern: .init(format: .Pattern.agentName) + ) else { + throw InputValidationError.inputNotAgentName + } + + return true + } + +} + +// MARK: - Constants + +private extension String.Pattern { + /// A regular expression pattern to match the user agent name input against. + static let agentName = "^([A-Z]([a-z]|[0-9])+)+$" +} diff --git a/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift b/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUseCaseTests.swift similarity index 86% rename from Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift rename to Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUseCaseTests.swift index da9f63571..b93e37c42 100644 --- a/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift +++ b/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUseCaseTests.swift @@ -22,6 +22,20 @@ struct ValidateInputUseCaseTests { // MARK: Functions #if swift(>=6.2) + @Test(arguments: zip( + Input.inputsAgentName, + Output.inputsAgentName + )) func `validate agent name`( + input: String, + expects error: InputValidationError? + ) async throws { + try assertValidate( + rule: .agentName, + input: input, + expects: error + ) + } + @Test(arguments: zip( Input.inputsNotEmpty, Output.inputsNotEmpty @@ -92,6 +106,20 @@ struct ValidateInputUseCaseTests { ) } #else + @Test("validate agent name", arguments: zip( + Input.inputsAgentName, + Output.inputsAgentName + )) func validateAgentName( + input: String, + expects error: InputValidationError? + ) async throws { + try assertValidate( + rule: .agentName, + input: input, + expects: error + ) + } + @Test("validate not empty", arguments: zip( Input.inputsNotEmpty, Output.inputsNotEmpty @@ -203,6 +231,8 @@ private extension ValidateInputUseCaseTests { // MARK: - Constants private extension Input { + /// A list of inputs to validate against a user agent name validation rule. + static let inputsAgentName: [String] = ["SampleApp", "Sample4pp", "SampleApp1", "SampleApp🚀", "Sample App", "Sample-App", "Sample_App"] /// A list of inputs to validate against the not empty validation rule. static let inputsNotEmpty: [String] = ["Something", .empty] /// A list of inputs to validate against the not nil validation rule. @@ -216,6 +246,8 @@ private extension Input { } private extension Output { + /// A list of expected input validation errors to be thrown after validating inputs against the user agent name validation rule. + static let inputsAgentName: [InputValidationError?] = [nil, nil, nil, .inputNotAgentName, .inputNotAgentName, .inputNotAgentName, .inputNotAgentName] /// A list of expected input validation errors to be thrown after validating inputs against the not empty validation rule. static let inputsNotEmpty: [InputValidationError?] = [nil, .inputIsEmpty] /// A list of expected input validation errors to be thrown after validating inputs against the not nil validation rule.