From 25fdc1fabd9e06793488aa5679553d7115e5dca2 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Oct 2025 09:59:35 +0200 Subject: [PATCH 1/6] Implemented a Product model in the library target. --- .../Public/Models/Product.swift | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 Sources/DiscogsService/Public/Models/Product.swift diff --git a/Sources/DiscogsService/Public/Models/Product.swift b/Sources/DiscogsService/Public/Models/Product.swift new file mode 100644 index 000000000..b52d52b59 --- /dev/null +++ b/Sources/DiscogsService/Public/Models/Product.swift @@ -0,0 +1,48 @@ +// ===----------------------------------------------------------------------=== +// +// 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 + +/// A type that represents a product that uses the ``Client`` client. +public struct Product: Sendable { + + // MARK: Properties + + /// A camel-cased name of a product. + let name: String + + /// A URI link related to a product. + let url: String + + /// A semantic version of a product. + let version: String + + // MARK: Initializers + + /// Initializes this model. + /// - Parameters: + /// - name: A camel-cased name of a product. + /// - version: A URI link related to a product. + /// - url: A semantic version of a product. + public init( + name: String, + version: String, + url: String + ) { + self.name = name + self.url = url + self.version = version + } + +} -- 2.52.0 From db688c553d2c640cb9099c7e688b287517b53d4b Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Oct 2025 13:13:09 +0200 Subject: [PATCH 2/6] Added the "userAgent" format constant to the String+Constants extension in the library extension. --- .../DiscogsService/Internal/Extensions/String+Constants.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/DiscogsService/Internal/Extensions/String+Constants.swift b/Sources/DiscogsService/Internal/Extensions/String+Constants.swift index 3d2af2654..852fa6a4c 100644 --- a/Sources/DiscogsService/Internal/Extensions/String+Constants.swift +++ b/Sources/DiscogsService/Internal/Extensions/String+Constants.swift @@ -31,6 +31,8 @@ extension String { static let authConsumer = "Discogs \(String.Parameter.key)=%@, \(String.Parameter.secret)=%@" /// A format for the user authentication header. static let authUser = "Discogs \(String.Parameter.token)=%@" + /// A format for the user agent header. + static let userAgent = "%@/%@ +%@" } } -- 2.52.0 From 8c68ae9417bba8910548df3eead8d422a02e1ac9 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Oct 2025 13:24:42 +0200 Subject: [PATCH 3/6] Defined the InputValidationRule protocol in the library target. --- .../Errors/InputValidationError.swift | 21 ++++++++++++ .../Protocols/InputValidationRule.swift | 34 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 Sources/DiscogsService/Internal/Errors/InputValidationError.swift create mode 100644 Sources/DiscogsService/Internal/Protocols/InputValidationRule.swift diff --git a/Sources/DiscogsService/Internal/Errors/InputValidationError.swift b/Sources/DiscogsService/Internal/Errors/InputValidationError.swift new file mode 100644 index 000000000..e4299b690 --- /dev/null +++ b/Sources/DiscogsService/Internal/Errors/InputValidationError.swift @@ -0,0 +1,21 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +/// A representation of all the possible validation error that could be thrown while validating an input. +enum InputValidationError: Error { + /// An input is empty. + case inputIsEmpty + /// An input is nil. + case inputIsNil +} diff --git a/Sources/DiscogsService/Internal/Protocols/InputValidationRule.swift b/Sources/DiscogsService/Internal/Protocols/InputValidationRule.swift new file mode 100644 index 000000000..e82ad8953 --- /dev/null +++ b/Sources/DiscogsService/Internal/Protocols/InputValidationRule.swift @@ -0,0 +1,34 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +/// A protocol that defines an input validation rule to be applied to an input by the ``ValidateInputUseCase`` use case. +protocol InputValidationRule { + + // MARK: Functions + +#if swift(>=6.0) + /// Validates a given input against a validation rule. + /// - Parameter input: An input to be validated. + /// - Returns: A flag that indicates whether an input has been validated or not. + /// - Throws: An error of type ``InputValidationError`` in case a given input failed a validation. + @discardableResult func validate(_ input: String?) throws(InputValidationError) -> Bool +#else + /// Validates a given input against a validation rule. + /// - Parameter input: An input to be validated. + /// - Returns: A flag that indicates whether an input has been validated or not. + /// - Throws: An error of type ``InputValidationError`` in case a given input failed a validation. + @discardableResult func validate(_ input: String?) throws -> Bool +#endif + +} -- 2.52.0 From 630f8a03f715bd5b6f7c7e8c179900ae95af78b3 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Oct 2025 13:57:54 +0200 Subject: [PATCH 4/6] Implemented the NotEmptyValidationRule type in the library target. --- .../NotEmptyValidationRule.swift | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift diff --git a/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift b/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift new file mode 100644 index 000000000..dbcbfbfea --- /dev/null +++ b/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift @@ -0,0 +1,38 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +/// A validation rule type that checks whether an input is empty or not. +struct NotEmptyValidationRule: InputValidationRule { + + // MARK: Functions + + func validate(_ input: String?) throws -> Bool { + guard let input else { + return false + } + guard !input.isEmpty else { + throw InputValidationError.inputIsEmpty + } + + return true + } + +} + +// MARK: - Constants + +extension InputValidationRule where Self == NotEmptyValidationRule { + /// A validation rule that checks whether an input is empty or not. + static var notEmpty: Self { .init() } +} -- 2.52.0 From 2677bd8de55b1202121181315c05ac4707441baa Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Oct 2025 13:58:06 +0200 Subject: [PATCH 5/6] Implemented the NotNilValidationRule type in the library target. --- .../NotEmptyValidationRule.swift | 25 ++++++++ .../NotNilValidationRule.swift | 60 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 Sources/DiscogsService/Internal/Validation Rules/NotNilValidationRule.swift diff --git a/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift b/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift index dbcbfbfea..c27491872 100644 --- a/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift +++ b/Sources/DiscogsService/Internal/Validation Rules/NotEmptyValidationRule.swift @@ -17,7 +17,32 @@ struct NotEmptyValidationRule: 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: - Helpers + +private extension NotEmptyValidationRule { + + // 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 } diff --git a/Sources/DiscogsService/Internal/Validation Rules/NotNilValidationRule.swift b/Sources/DiscogsService/Internal/Validation Rules/NotNilValidationRule.swift new file mode 100644 index 000000000..1b9472e0d --- /dev/null +++ b/Sources/DiscogsService/Internal/Validation Rules/NotNilValidationRule.swift @@ -0,0 +1,60 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +/// A validation rule type that checks whether an input is nil or not. +struct NotNilValidationRule: 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: - Helpers + +private extension NotNilValidationRule { + + // 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 { + throw InputValidationError.inputIsNil + } + + return true + } + +} + +// MARK: - Constants + +extension InputValidationRule where Self == NotNilValidationRule { + /// A validation rule that checks whether an input is nil or not. + static var notNil: Self { .init() } +} -- 2.52.0 From c00c348f0289d5123856daba970fdf70c25adfde Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Oct 2025 14:34:28 +0200 Subject: [PATCH 6/6] Implemented the ValidateInputUseCase use case in the library target. --- .../Use Cases/ValidateInputUseCase.swift | 53 ++++++++++ .../Use Cases/ValidateInputUesCaseTests.swift | 97 +++++++++++++++++++ .../UserAgentMiddlewareTests.swift | 25 +++++ 3 files changed, 175 insertions(+) create mode 100644 Sources/DiscogsService/Internal/Use Cases/ValidateInputUseCase.swift create mode 100644 Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift create mode 100644 Tests/DiscogsService/Cases/Public/Middlewares/UserAgentMiddlewareTests.swift diff --git a/Sources/DiscogsService/Internal/Use Cases/ValidateInputUseCase.swift b/Sources/DiscogsService/Internal/Use Cases/ValidateInputUseCase.swift new file mode 100644 index 000000000..85186c92d --- /dev/null +++ b/Sources/DiscogsService/Internal/Use Cases/ValidateInputUseCase.swift @@ -0,0 +1,53 @@ +// ===----------------------------------------------------------------------=== +// +// 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 +// +// ===----------------------------------------------------------------------=== + +/// A use case that validates an input against a set of validation rules. +struct ValidateInputUseCase { + + // MARK: Properties + + /// A list of validation rules to match an input against. + private let rules: [any InputValidationRule] + + // MARK: Initializers + + /// Initializes this use case. + /// - Parameter rules: A list of validation rules to match an input against. + init(rules: any InputValidationRule...) { + self.rules = rules + } + + // MARK: Functions + +#if swift(>=6.0) + /// Validates an input against a set of validation rules. + /// - Parameter input: An input to be validated against a set of rules, if any. + /// - Throws: An error of type ``InputValidationError`` in case an input failed any validation. + func callAsFunction(_ input: String?) throws(InputValidationError) { + for rule in rules { + try rule.validate(input) + } + } +#else + /// Validates an input against a set of validation rules. + /// - Parameter input: An input to be validated against a set of rules, if any. + /// - Throws: An error of type ``InputValidationError`` in case an input failed any validation. + func callAsFunction(_ input: String?) throws { + for rule in rules { + try rule.validate(input) + } + } +#endif + +} diff --git a/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift b/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift new file mode 100644 index 000000000..631fd12ff --- /dev/null +++ b/Tests/DiscogsService/Cases/Internal/Use Cases/ValidateInputUesCaseTests.swift @@ -0,0 +1,97 @@ +// ===----------------------------------------------------------------------=== +// +// 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("Validate Input Use Cases") +struct ValidateInputUseCaseTests { + + // MARK: Functions + +#if swift(>=6.2) + @Test(arguments: zip( + Input.inputsToValidate, + Output.inputsToValidate + )) func `validate`( + input: String?, + expects error: InputValidationError? + ) async throws { + try assertValidate( + input: input, + expects: error + ) + } +#else + @Test("validate", arguments: zip( + Input.inputsToValidate, + Output.inputsToValidate + )) func validate( + input: String?, + expects error: InputValidationError? + ) async throws { + try assertValidate( + input: input, + expects: error + ) + } +#endif + +} + +// MARK: - Assertions + +private extension ValidateInputUseCaseTests { + + // MARK: Functions + + /// Asserts an input validation of a ``ValidateInputUseCase`` use case. + /// - Parameters: + /// - input: An input to validate, if any. + /// - error: An expected error, if any. + /// - Throws: An error of type ``InputValidationError`` in case of an unexpected test case scenario. + func assertValidate( + input: String?, + expects error: InputValidationError? + ) throws { + // GIVEN + let validate = ValidateInputUseCase(rules: .notNil, .notEmpty) + + // WHEN + // THEN + if let error { + #expect(throws: error) { + try validate(input) + } + } else { + #expect(throws: Never.self) { + try validate(input) + } + } + } + +} + +// MARK: - Constants + +private extension Input { + /// A list of inputs to validate against a set of validation rules. + static let inputsToValidate: [String?] = [nil, .empty, "SomeInput"] +} + +private extension Output { + /// A list of expected input validation errors to be thrown (if necessary). + static let inputsToValidate: [InputValidationError?] = [.inputIsNil, .inputIsEmpty, nil] +} diff --git a/Tests/DiscogsService/Cases/Public/Middlewares/UserAgentMiddlewareTests.swift b/Tests/DiscogsService/Cases/Public/Middlewares/UserAgentMiddlewareTests.swift new file mode 100644 index 000000000..a4c0b9a47 --- /dev/null +++ b/Tests/DiscogsService/Cases/Public/Middlewares/UserAgentMiddlewareTests.swift @@ -0,0 +1,25 @@ +// ===----------------------------------------------------------------------=== +// +// 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 DiscogsService +import Testing + +@Suite("User Agent Middleware", .tags(.middleware)) +struct UserAgentMiddlewareTests { + +// @Test func <#test function name#>() async throws { +// // Write your test here and use APIs like `#expect(...)` to check expected conditions. +// } + +} -- 2.52.0