{"id":22112378,"url":"https://github.com/lukeredpath/swift-validations","last_synced_at":"2025-07-24T05:32:31.443Z","repository":{"id":42443296,"uuid":"276105752","full_name":"lukeredpath/swift-validations","owner":"lukeredpath","description":"A high-level functional validation library, written in Swift","archived":false,"fork":false,"pushed_at":"2023-01-19T19:29:15.000Z","size":1162,"stargazers_count":14,"open_issues_count":4,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-02T03:12:06.394Z","etag":null,"topics":["functional-swift","swift","validation","validation-library"],"latest_commit_sha":null,"homepage":"https://lukeredpath.github.io/swift-validations/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lukeredpath.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-30T13:24:52.000Z","updated_at":"2025-03-12T14:58:44.000Z","dependencies_parsed_at":"2023-02-11T18:30:56.635Z","dependency_job_id":null,"html_url":"https://github.com/lukeredpath/swift-validations","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lukeredpath/swift-validations","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2Fswift-validations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2Fswift-validations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2Fswift-validations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2Fswift-validations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukeredpath","download_url":"https://codeload.github.com/lukeredpath/swift-validations/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeredpath%2Fswift-validations/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266796832,"owners_count":23985483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-24T02:00:09.469Z","response_time":99,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-swift","swift","validation","validation-library"],"created_at":"2024-12-01T10:57:45.255Z","updated_at":"2025-07-24T05:32:31.419Z","avatar_url":"https://github.com/lukeredpath.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Validations\n\n![Swift](https://github.com/lukeredpath/swift-validations/workflows/Swift/badge.svg)\n\n## Overview\n\nValidations is a high-level validation library, written in a functional style. It was created to explore functional API design as outlined in the [Pointfree.co series on protocol witnesses](https://www.pointfree.co/collections/protocol-witnesses) as an alternative to the usual protocol-oriented approach. \n\nThe library builds on top of the `Validated` type provided by the [Validated](https://github.com/pointfreeco/swift-validated) library.\n\n[API Documentation](https://lukeredpath.github.io/swift-validations/)\n\n## Core API\n\nFundamentally, a validator can be expressed as a generic function of type:\n\n```swift\n(Value) -\u003e Validated\u003cValue, ErrorType\u003e\n```\n\nThe `Validated` type is an enum that represents either a valid value or an invalid value. An invalid value has a non-empty collection of errors of type `ErrorType`.\n\nValidations wrapps the above method up in a struct type called `ValidatorOf\u003cT, ErrorType\u003e` where `T` is the the of value being validated. Therefore, any custom validator can be expressed as such:\n\n```swift\nlet alwaysValid = ValidatorOf\u003cAny, Never\u003e { .valid($0) }\n\nXCTAssert(alwaysValid.validate(1).isValid)\nXCTAssert(alwaysValid.validate(\"foo\").isValid)\n\nlet alwaysFails = ValidatorOf\u003cAny, String\u003e { .error(\"failed\") }\n\nXCTAssertFalse(alwaysFails.validate(1).isValid)\nXCTAssertEquals(\"failed\", alwaysFails.validate(1).errors?.last)\n```\n\n### Re-using existing validators\n\nValidators be extended and re-used with other types by using the `pullback` function. For example, given we already have a `greaterThan` validator that works on `Int`:\n\n```swift\nfunc greaterThan(_ lowerBound: Int) -\u003e ValidatorOf\u003cInt, String\u003e {\n    return ValidatorOf\u003cInt, String\u003e { value in \n        if value \u003e lowerBound {\n            return .valid(value)\n        }\n        return .error(\"is not greater than \\(lowerBound)\")\n    }\n}\n```\n\nIf we wanted to write a similar validator for the length of a string, we could write one from scratch:\n\n```swift\nfunc lengthLongerThan(_ lowerBound: Int) -\u003e ValidatorOf\u003cString, String\u003e {\n    return ValidatorOf\u003cInt, String\u003e { value in \n        if value.count \u003e lowerBound {\n            return .valid(value)\n        }\n        return .error(\"length is not greater than \\(lowerBound)\")\n    }\n}\n```\n\nHowever, we are effectively duplicating the logic of the `greaterThan` validator - the only thing that changes is how we obtain the value to compare against (`value.count` instead of `value`) and the error message is prefixed with \"length \".\n\nWe can remove the logic duplication by pulling back the `greaterThan` validator to operate on the `value`'s `count`:\n\n```swift\nfunc lengthLongerThan(_ lowerBound: Int) -\u003e ValidatorOf\u003cString, String\u003e {\n    return greaterThan(lowerBound).pullback { $0.count }\n}\n```\n\nAs of Swift 5.2, we can shorten this further due to support for passing a keypath as a function parameter:\n\n```swift\nfunc lengthLongerThan(_ lowerBound: Int) -\u003e ValidatorOf\u003cString, String\u003e {\n    return greaterThan(lowerBound).pullback(\\.count)\n}\n```\n\nFinally, we can improve the error message to add back the \"length \" prefix by using `mapError`:\n\n```swift\nfunc lengthLongerThan(_ lowerBound: Int) -\u003e ValidatorOf\u003cString, String\u003e {\n    return greaterThan(lowerBound)\n      .pullback(\\.count)\n      .mapError { \"length \\($0)\" }\n}\n```\n\n### Combining validators\n\nHigher-level validators can be formed from existing ones using the `.combine` static method, so long as the operate on the same value type:\n\n```swift\nlet lowerAgeLimit = ValidatorOf\u003cInt, String\u003e.greaterThan(10)\nlet upperAgeLimit = ValidatorOf\u003cInt, String\u003e.lessThan(20)\nlet ageValidator = ValidatorOf\u003cInt, String\u003e.combine(lowerAgeLimit, upperAgeLimit)\n\nXCTAssert(ageValidator.validate(12).isValid)\nXCTAssertFalse(ageValidator.validate(9).isValid)\nXCTAssertFalse(ageValidator.validate(21).isValid)\n```\n\n### Negating validators\n\nA validator that operates as the logical inverse of an existing validator can be produced using the `negated()` method on `ValidatorOf`.\n\nFor example, given a validator that checks for odd numbers:\n\n```swift\nlet isOdd = ValidatorOf\u003cInt, String\u003e { \n    if $0 % 2 == 1 { \n        return .valid($0)\n    }\n    return .error(\"is not odd\"\")\n}\n```\n\nYou could create a validator that checks for even numbers by negating it. When negating a matcher, a new error message should be provided for the negated error case.\n\n```swift\nlet isEven = isOdd.negated(withError: \"is not even\")\n```\n\nA static function `.not` is provided as syntatic sugar. The above could be re-written as:\n\n```swift\nlet isEven: Validator\u003cInt, String\u003e = .not(isOdd)\n```\n\n### Handling optional values\n\nIt is possible to create a validator over an optional value, expressed as a type `ValidatorOf\u003cT?, Error\u003e` - when doing so, it is up to you to define how to handle nil values. If a value is optional, you can permit nil values by returning a `.valid` result - if a value is required then you should return an invalid result with an appropriate error message. \n\nFor example, a validator on an optional `Int` that allows nil values can be defined as:\n\n```swift\nlet optionalInt = ValidatorOf\u003cInt?, String\u003e { optionalValue in\n    if let value = optionalValue {\n        // your validation logic here\n    }\n    return .valid(optionalValue)\n}\n```\n\nAlternatively, if you always require a value in order for the validator to return a valid result, you could instead write the following:\n\n```swift\nlet optionalInt = ValidatorOf\u003cInt?, String\u003e { optionalValue in\n    if let value = optionalValue {\n        // your validation logic here\n    }\n    return .error(\"is required\")\n}\n```\n\nValidators that operate on optional types will return a `Validated\u003cT?, Error\u003e` result type.\n\nThe library provides an `optional()` operator in two different forms that can be used to convert an existing validator that operates on a non-optional type to one that operates on an optional - in both cases you are required to specify how missing values should be handled.\n\nThe generic overload requires that you pass in an optional error value of type `Error?` - if an error value is given then nil values will be treated as an error and will return an invalid result using the error value you give it. If no error value is given, then nil values will be treated as valid:\n\n```swift\nlet ageMustBeOverTen: ValidatorOf\u003cInt, String\u003e = ...\nlet optionalAgeValidator = ageMustBeOverTen.optional(errorOnNil: \"is required\")\n\noptionalAgeValidator.validate(11)   // returns Validated\u003cInt?, String\u003e.valid\noptionalAgeValidator.validate(10)   // returns Validated\u003cInt?, String\u003e.error(\"must be over 10\")\noptionalAgeValidator.validate(nil)  // returns Validated\u003cInt?, String\u003e.error(\"is required\")\n```\n\nAll of the built-in validators and most of the validators you write yourself will use a `String` error type - in this case, you can use the alternative form `optional(allowNil: Bool)`, simply specifying if nil values are allowed or not - if you pass `false` then a default value of \"is required\" will be used:\n\n```swift\nlet v1 = ValidatorOf\u003cString, String\u003e.hasPrefix(\"foo\").optional(allowNil: true)\nv1.validate(nil)    // returns Validated\u003cString?, String\u003e.valid\n\nlet v2 = ValidatorOf\u003cString, String\u003e.hasPrefix(\"foo\").optional(allowNil: false)\nv2.validate(nil)    // returns Validated\u003cString?, String\u003e.error(\"is required\")\n```\n\n### Built-in validators\n\nThe following validators are built-in and can be combined to form more domain-specific validations in your code:\n\n* Boolean\n    - `isTrue`\n    - `isFalse`\n* Collection\n    - `hasLengthOf`\n    - `contains` (where `Collection\u003cT: Equatable\u003e`)\n* Comparable\n    - `isAtLeast`\n    - `isAtMost`\n    - `isLessThan`\n    - `isGreaterThan`\n    - `isInRange(x...y)`\n    - `isInRange(x..\u003cy)`\n* Collection Membership\n    - `isIncluded(in: Array)`\n    - `isExcluded(from: Array)`\n    - `isIncluded(in: Set)`\n    - `isExcluded(from: Set)`\n* Equatable\n    - `isEqualTo`\n* Numeric\n    - `isExactly`\n    - `isOdd`\n    - `isEven`\n* String\n    - `itsLength(\u003cnumeric validator type\u003e)`\n    - `hasLengthOf`\n    - `beginsWith`\n    - `endsWith`\n    - `matchesPattern(_, as:)` (defaults to `.regularExpression`)\n\n## Validating and @Validating\n\nThe library ships with a `Validating` type which can be used either on it's own or as a property wrapper. The `Validating\u003cValue\u003e` wraps both a value of type `Value` and a `ValidatorOf\u003cValue, String\u003e` that re-validates every time `Value` is updated, producing a new `Validated\u003cValue\u003e` which is stored internally. \n\nThe `Validating` type provides dynamic property access to the underlying `Validated\u003cValue\u003e` so you can check if it is valid or access any errors.\n\n### Simple usage\n\n```swift\nlet validator: ValidatorOf\u003cString, String\u003e = .combine(\n    .hasPrefix(\"foo\"), \n    .hasLengthOf(.atLeast(4))\n)\n\nvar validatingString: Validating\u003cString\u003e = Validating(wrappedValue: \"\", validator: validator)\n\nXCTAssertEqual(\"\", validatingString.wrappedValue)\nXCTAssertFalse(validatingString.isValid)\n\nvalidatingString.wrappedValue = \"foobar\"\nXCTAssert(validatingString.isValid)\n```\n\n### Property wrapper usage\n\n```swift\nstruct FormViewModel {\n    @Validating(\n        .hasLengthOf(.atLeast(3))\n    )\n    var name: String\n    \n    @Validating(\n        .isInRange(13...80)\n    )\n    var age: Int\n}\n```\nWhen used as a property wrapper, you can use the `$var` syntax to access the `Validated\u003cValue\u003e` directly to check if they are valid. Using the `zip` function provided by the `Validated` library, you could implement an `isValid()` method for the entire view model:\n\n```swift\nextension FormViewModel {\n    var isValid: Bool {\n        zip($name, $age).isValid\n    }\n}\n```\n\n### Optional values\n\nWhilst the Swift type system allows you to express whether or not a value is required using optional or non-optional values, there are times when you may have a required value but not have a sensible default value that you can set to satisfy the compiler - this is often the case when you have some kind of value that represents user input. In this case, it is preferable to make the value optional and then use a validation to enforce that it is non-nil.\n\nIf you need to handle optional values, you can use the optional counterpart  `OptionalValidating`. Like `Validating` this can be used standalone or as a property-wrapper. You do not need to pass in validators on optional types as they are converted to optional forms automatically. \n\n`OptionalValidating` can be initialized with or without an initial value. When initialised with a default value, it will treat nil values as invalid by default - this can be changed by explicitly passing in the `required` parameter. When initialised without a default value, you must explicitly state whether the value is required or not.\n\nThe following example demonstrates various uses as a property wrapper:\n\n```swift\nstruct FormViewModel {\n    // no default value means it is implicitly required and will start as invalid\n    @OptionalValidating(.greaterThan(10))\n    var requiredAge: Int?\n    \n    // if you can specify a default value, but still require it be non-nil you can be explicit\n    @OptionalValidating(required: true, .myPostcodeValidator)\n    var postcode: String? = \"\"\n    \n    // you can make a field truly optional by explicitly stating that it is not required\n    @OptionalValidating(required: false, .myPhoneNumberValidator)\n    var phoneNumber: String\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukeredpath%2Fswift-validations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukeredpath%2Fswift-validations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukeredpath%2Fswift-validations/lists"}