From c8b632cd6418e8c23a0eaca9055a977860002e94 Mon Sep 17 00:00:00 2001 From: Mx Date: Sun, 22 Feb 2026 18:35:56 +0100 Subject: [PATCH 1/6] test: enforce expected throws in failure-path cases --- Tests/AllTests/AllTests.swift | 40 ++++++++++++----------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/Tests/AllTests/AllTests.swift b/Tests/AllTests/AllTests.swift index 2a72884..aa8296f 100644 --- a/Tests/AllTests/AllTests.swift +++ b/Tests/AllTests/AllTests.swift @@ -32,12 +32,10 @@ class AllTests: XCTestCase { let value = 0 - do - { + XCTAssertThrowsError( try Requirement("Non-zero value"){ $0 != value }.validate(value) - } - catch - { + ) + { error in guard let unsatisfiedRequirement = error as? UnsatisfiedRequirement else @@ -81,16 +79,13 @@ class AllTests: XCTestCase } } - do - { + XCTAssertThrowsError( try Check.that("Non-zero value") { try Container.failingProperty } - - } - catch - { + ) + { error in guard case let FailedCheck.errorDuringConditionCheck(desc, nestedError, context) = error else @@ -114,12 +109,10 @@ class AllTests: XCTestCase { let value = 0 - do - { + XCTAssertThrowsError( try Check.that("Non-zero value", value != 0 ) - } - catch - { + ) + { error in guard case let FailedCheck.unsatisfiedCondition(desc, context) = error else @@ -180,12 +173,7 @@ class AllTests: XCTestCase { let value: Int? = nil - do - { - try Check.nonEmpty { value } - } - catch - { + XCTAssertThrowsError(try Check.nonEmpty { value }) { error in switch error { case FailedCheck.unsatisfiedNonEmptyCondition(let desc, _): @@ -196,12 +184,10 @@ class AllTests: XCTestCase } } - do - { + XCTAssertThrowsError( try Check.nonEmpty("Custom check description") { value } - } - catch - { + ) + { error in switch error { case FailedCheck.unsatisfiedNonEmptyCondition(let desc, _): From 9380d92451524a6fd63586f2f0ca6d5ba1c2a68a Mon Sep 17 00:00:00 2001 From: Mx Date: Sun, 22 Feb 2026 18:38:03 +0100 Subject: [PATCH 2/6] api: make isValid rethrow evaluation errors --- README.md | 33 ++++++++++++++++++++------------- Sources/Core/Requirement.swift | 4 ++-- Tests/AllTests/AllTests.swift | 29 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b2ac155..ec71c8c 100644 --- a/README.md +++ b/README.md @@ -66,22 +66,29 @@ Note, that If a requirement contains phrases like **AND**, **OR** or any other l When requirement is created, here is an example of how it might be used for checking potentially suitable values. ```swift -if - r.isValid(14) // returns Bool +do { - // given value - 14 (Int) - fulfills the requirement - - // r.description - the description that has been provided - // during requirement initialization - - print("\(r.description) -> YES") + if + try r.isValid(14) // returns Bool, rethrows check-evaluation errors + { + // given value - 14 (Int) - fulfills the requirement + + // r.description - the description that has been provided + // during requirement initialization + + print("\(r.description) -> YES") + } + else + { + // this code block will be executed, + // if 0 will be passed into r.isValid(...) + + print("\(r.description) -> NO") + } } -else +catch { - // this code block will be executed, - // if 0 will be passed into r.isValid(...) - - print("\(r.description) -> NO") + print(error) } ``` diff --git a/Sources/Core/Requirement.swift b/Sources/Core/Requirement.swift index 7182e25..acfb87a 100644 --- a/Sources/Core/Requirement.swift +++ b/Sources/Core/Requirement.swift @@ -80,7 +80,7 @@ extension Requirement line: Int = #line, function: String = #function, _ value: Input - ) -> Bool + ) throws -> Bool { do { @@ -92,7 +92,7 @@ extension Requirement ) return true } - catch + catch is UnsatisfiedRequirement { return false } diff --git a/Tests/AllTests/AllTests.swift b/Tests/AllTests/AllTests.swift index aa8296f..b48205b 100644 --- a/Tests/AllTests/AllTests.swift +++ b/Tests/AllTests/AllTests.swift @@ -48,6 +48,35 @@ class AllTests: XCTestCase XCTAssertTrue(unsatisfiedRequirement.context.function.contains("test_requirement_unsatisfiedCondition")) } } + + func test_requirement_isValid_distinguishes_unsatisfiedCondition() + { + do + { + let actual = try Requirement("Non-zero value"){ $0 != 0 }.isValid(0) + XCTAssertFalse(actual) + } + catch + { + XCTFail("Unexpected failure") + } + } + + func test_requirement_isValid_rethrows_conditionEvaluationError() + { + enum TestError: Error { case brokenCondition } + + let requirement = Requirement("Non-zero value") { _ in + throw TestError.brokenCondition + } + + XCTAssertThrowsError(try requirement.isValid(14)) { error in + guard case TestError.brokenCondition = error else + { + return XCTFail("Unexpected error") + } + } + } func test_inlineCheck_success() { From fea6e5e95559a7309683e79537fb7e2c39eb25b4 Mon Sep 17 00:00:00 2001 From: Mx Date: Sun, 22 Feb 2026 18:38:13 +0100 Subject: [PATCH 3/6] test: replace force-cast with safe input assertion --- Tests/AllTests/AllTests.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/AllTests/AllTests.swift b/Tests/AllTests/AllTests.swift index b48205b..6a11849 100644 --- a/Tests/AllTests/AllTests.swift +++ b/Tests/AllTests/AllTests.swift @@ -44,7 +44,13 @@ class AllTests: XCTestCase } XCTAssertEqual(unsatisfiedRequirement.description, "Non-zero value") - XCTAssert(unsatisfiedRequirement.input as! Int == value) + guard + let actualInput = unsatisfiedRequirement.input as? Int + else + { + return XCTFail("Unexpected input type") + } + XCTAssertEqual(actualInput, value) XCTAssertTrue(unsatisfiedRequirement.context.function.contains("test_requirement_unsatisfiedCondition")) } } From bfbe83b13c1968b7a80b3116493aea098a74e41b Mon Sep 17 00:00:00 2001 From: Mx Date: Sun, 22 Feb 2026 18:39:03 +0100 Subject: [PATCH 4/6] docs: align Swift version metadata and install guidance --- Package.swift | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index 6d56232..58496cb 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.10 import PackageDescription @@ -25,4 +25,4 @@ let package = Package( path: "Tests/AllTests" ), ] -) \ No newline at end of file +) diff --git a/README.md b/README.md index ec71c8c..f5a784c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GitHub License](https://img.shields.io/github/license/XCEssentials/Requirement.svg?longCache=true)](LICENSE) [![GitHub Tag](https://img.shields.io/github/tag/XCEssentials/Requirement.svg?longCache=true)](https://github.com/XCEssentials/Requirement/tags) [![Swift Package Manager Compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg?longCache=true)](Package.swift) -[![Written in Swift](https://img.shields.io/badge/Swift-5.3-orange.svg?longCache=true)](https://swift.org) +[![Written in Swift](https://img.shields.io/badge/Swift-5.10%2B-orange.svg?longCache=true)](https://swift.org) [![Supported platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-blue.svg?longCache=true)](Package.swift) [![CI](https://github.com/XCEssentials/Requirement/actions/workflows/ci.yml/badge.svg)](https://github.com/XCEssentials/Requirement/actions/workflows/ci.yml) @@ -35,7 +35,7 @@ Each requirement can be evaluated against a given data value (which can be an at ## How to install -The recommended way is to install using [SwiftPM](https://swift.org/package-manager/), but [Carthage](https://github.com/Carthage/Carthage) is also supported out of the box. +Install using [SwiftPM](https://swift.org/package-manager/). The package currently targets Swift tools version `5.10` and newer. ## How it works From 117a1eefd0d12a6a0a574236e5f18648d1fd4dac Mon Sep 17 00:00:00 2001 From: Mx Date: Sun, 22 Feb 2026 18:42:05 +0100 Subject: [PATCH 5/6] Revert "api: make isValid rethrow evaluation errors" This reverts commit 9380d92451524a6fd63586f2f0ca6d5ba1c2a68a. --- README.md | 33 +++++++++++++-------------------- Sources/Core/Requirement.swift | 4 ++-- Tests/AllTests/AllTests.swift | 29 ----------------------------- 3 files changed, 15 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index f5a784c..b3f3ce5 100644 --- a/README.md +++ b/README.md @@ -66,29 +66,22 @@ Note, that If a requirement contains phrases like **AND**, **OR** or any other l When requirement is created, here is an example of how it might be used for checking potentially suitable values. ```swift -do +if + r.isValid(14) // returns Bool { - if - try r.isValid(14) // returns Bool, rethrows check-evaluation errors - { - // given value - 14 (Int) - fulfills the requirement - - // r.description - the description that has been provided - // during requirement initialization - - print("\(r.description) -> YES") - } - else - { - // this code block will be executed, - // if 0 will be passed into r.isValid(...) - - print("\(r.description) -> NO") - } + // given value - 14 (Int) - fulfills the requirement + + // r.description - the description that has been provided + // during requirement initialization + + print("\(r.description) -> YES") } -catch +else { - print(error) + // this code block will be executed, + // if 0 will be passed into r.isValid(...) + + print("\(r.description) -> NO") } ``` diff --git a/Sources/Core/Requirement.swift b/Sources/Core/Requirement.swift index acfb87a..7182e25 100644 --- a/Sources/Core/Requirement.swift +++ b/Sources/Core/Requirement.swift @@ -80,7 +80,7 @@ extension Requirement line: Int = #line, function: String = #function, _ value: Input - ) throws -> Bool + ) -> Bool { do { @@ -92,7 +92,7 @@ extension Requirement ) return true } - catch is UnsatisfiedRequirement + catch { return false } diff --git a/Tests/AllTests/AllTests.swift b/Tests/AllTests/AllTests.swift index 6a11849..47a8194 100644 --- a/Tests/AllTests/AllTests.swift +++ b/Tests/AllTests/AllTests.swift @@ -54,35 +54,6 @@ class AllTests: XCTestCase XCTAssertTrue(unsatisfiedRequirement.context.function.contains("test_requirement_unsatisfiedCondition")) } } - - func test_requirement_isValid_distinguishes_unsatisfiedCondition() - { - do - { - let actual = try Requirement("Non-zero value"){ $0 != 0 }.isValid(0) - XCTAssertFalse(actual) - } - catch - { - XCTFail("Unexpected failure") - } - } - - func test_requirement_isValid_rethrows_conditionEvaluationError() - { - enum TestError: Error { case brokenCondition } - - let requirement = Requirement("Non-zero value") { _ in - throw TestError.brokenCondition - } - - XCTAssertThrowsError(try requirement.isValid(14)) { error in - guard case TestError.brokenCondition = error else - { - return XCTFail("Unexpected error") - } - } - } func test_inlineCheck_success() { From e7617e4b96601d0d5a0a8239eb7e498dab910377 Mon Sep 17 00:00:00 2001 From: Mx Date: Sun, 22 Feb 2026 18:48:18 +0100 Subject: [PATCH 6/6] test: add coverage for requirement and nonEmpty edge paths --- Tests/AllTests/AllTests.swift | 127 ++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/Tests/AllTests/AllTests.swift b/Tests/AllTests/AllTests.swift index 47a8194..5622c68 100644 --- a/Tests/AllTests/AllTests.swift +++ b/Tests/AllTests/AllTests.swift @@ -26,6 +26,24 @@ class AllTests: XCTestCase XCTFail("Unexpected failure") } } + + func test_requirement_validate_rethrowsConditionEvaluationError() + { + enum TestError: Error { case brokenCondition } + + let requirement = Requirement("Any value") { _ in + throw TestError.brokenCondition + } + + XCTAssertThrowsError(try requirement.validate(14)) { error in + guard + case TestError.brokenCondition = error + else + { + return XCTFail("Unexpected error") + } + } + } func test_requirement_unsatisfiedCondition() @@ -54,6 +72,51 @@ class AllTests: XCTestCase XCTAssertTrue(unsatisfiedRequirement.context.function.contains("test_requirement_unsatisfiedCondition")) } } + + func test_requirement_unsatisfiedCondition_customContext() + { + let requirement = Requirement("Non-zero value") { $0 != 0 } + + XCTAssertThrowsError( + try requirement.validate( + file: "CustomFile.swift", + line: 42, + function: "customFunction()", + 0 + ) + ) + { error in + guard + let unsatisfiedRequirement = error as? UnsatisfiedRequirement + else + { + return XCTFail("Unexpected validation error") + } + + XCTAssertEqual(unsatisfiedRequirement.context.file, "CustomFile.swift") + XCTAssertEqual(unsatisfiedRequirement.context.line, 42) + XCTAssertEqual(unsatisfiedRequirement.context.function, "customFunction()") + } + } + + func test_requirement_isValid() + { + let requirement = Requirement("Non-zero value") { $0 != 0 } + + XCTAssertTrue(requirement.isValid(1)) + XCTAssertFalse(requirement.isValid(0)) + } + + func test_requirement_isValid_returnsFalseOnThrownConditionError() + { + enum TestError: Error { case brokenCondition } + + let requirement = Requirement("Any value") { _ in + throw TestError.brokenCondition + } + + XCTAssertFalse(requirement.isValid(1)) + } func test_inlineCheck_success() { @@ -204,4 +267,68 @@ class AllTests: XCTestCase } } } + + func test_nonEmpty_errorDuringConditionCheck_whenInputThrows() + { + enum TestError: Error { case brokenCondition } + + XCTAssertThrowsError( + try Check.nonEmpty("Value is set") { () -> Int? in + throw TestError.brokenCondition + } + ) + { error in + guard + case let FailedCheck.errorDuringConditionCheck(desc, nestedError, context) = error + else + { + return XCTFail("Unexpected validation error") + } + + XCTAssertEqual(desc, "Value is set") + XCTAssertTrue(context.function.contains("test_nonEmpty_errorDuringConditionCheck_whenInputThrows")) + guard + case TestError.brokenCondition = nestedError + else + { + return XCTFail("Unexpected nested error") + } + } + } + + func test_nonEmpty_unsatisfiedCondition_customContext() + { + let value: Int? = nil + + XCTAssertThrowsError( + try Check.nonEmpty( + file: "CustomFile.swift", + line: 77, + function: "customFunction()", + "Value is set" + ) { value } + ) + { error in + guard + case let FailedCheck.unsatisfiedNonEmptyCondition(desc, context) = error + else + { + return XCTFail("Unexpected validation error") + } + + XCTAssertEqual(desc, "Value is set") + XCTAssertEqual(context.file, "CustomFile.swift") + XCTAssertEqual(context.line, 77) + XCTAssertEqual(context.function, "customFunction()") + } + } + + func test_aliases_requireAndCondition() + { + let require: Require = Require("Non-zero value") { $0 != 0 } + let condition: Condition = Condition("Positive value") { $0 > 0 } + + XCTAssertTrue(require.isValid(1)) + XCTAssertFalse(condition.isValid(0)) + } }