Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/nsagora/peppermint
Declarative data validation framework, written in Swift
https://github.com/nsagora/peppermint
ios macos swift validation validation-engine validation-kit
Last synced: 15 days ago
JSON representation
Declarative data validation framework, written in Swift
- Host: GitHub
- URL: https://github.com/nsagora/peppermint
- Owner: nsagora
- License: mit
- Created: 2016-08-06T04:09:32.000Z (over 8 years ago)
- Default Branch: main
- Last Pushed: 2024-01-11T10:24:09.000Z (12 months ago)
- Last Synced: 2024-05-29T04:52:09.436Z (7 months ago)
- Topics: ios, macos, swift, validation, validation-engine, validation-kit
- Language: Swift
- Homepage:
- Size: 3.19 MB
- Stars: 46
- Watchers: 5
- Forks: 15
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-ios - ValidationToolkit - Lightweight framework for input validation written in Swift. (UI / Form & Settings)
README
# Peppermint [![badge-version]][url-peppermint]
[![badge-build-macos]][url-peppermint]
[![badge-build-linux]][url-peppermint]
[![badge-docs]][url-peppermint-docs]
[![badge-codecov]][url-codecov]
[![badge-license]][url-license]
[![badge-twitter]][url-twitter]1. [Introduction](#introduction)
2. [Requirements](#requirements)
3. [Installation](#installation)
- [Swift Package Manager](#swift-package-manager)
4. [Usage Examples](#usage-examples)
- [Predicates](#predicates)
- [Constraints](#constraints)
- [Predicate Constraint](#predicate-constraint)
- [Compound Constraint](#compound-constraint)
5. [Contribute](#contribute)
6. [Meta](#meta)## Introduction
```swift
let constraint = TypeConstraint {
KeyPathConstraint(\.username) {
BlockConstraint {
$0.count >= 5
} errorBuilder: {
.username
}
}
KeyPathConstraint(\.password) {
GroupConstraint(.all) {
PredicateConstraint {
.characterSet(.lowercaseLetters, mode: .inclusive)
} errorBuilder: {
.password(.missingLowercase)
}
PredicateConstraint{
.characterSet(.uppercaseLetters, mode: .inclusive)
} errorBuilder: {
.password(.missingUppercase)
}
PredicateConstraint {
.characterSet(.decimalDigits, mode: .inclusive)
} errorBuilder: {
.password(.missingDigits)
}
PredicateConstraint {
.characterSet(CharacterSet(charactersIn: "!?@#$%^&*()|\\/<>,.~`_+-="), mode: .inclusive)
} errorBuilder: {
.password(.missingSpecialChars)
}
PredicateConstraint {
.length(min: 8)
} errorBuilder: {
.password(.tooShort)
}
}
}
BlockConstraint {
$0.password == $0.passwordConfirmation
} errorBuilder: {
.password(.confirmationMismatch)
}
KeyPathConstraint(\.email) {
PredicateConstraint(.email, error: .email)
}
KeyPathConstraint(\.age) {
PredicateConstraint(.range(min: 14), error: .underAge)
}
KeyPathConstraint(\.website) {
PredicateConstraint(.url, error: .website)
.optional()
}
}let result = constraint.evaluate(with: account)
switch result {
case .success:
handleSuccess()
case .failure(let summary):
handleErrors(summary.errors)
}```
`Peppermint` is a declarative and lightweight data validation framework.
At the core of it, there are 2 principles:
- Empower composition.
- Embrace standard library.Every project is unique in it's own challenges and it's great when we can focus on solving them instead of spending our time on boilerplate tasks.
With this idea in mind, the framework follows the Protocol Oriented Programming paradigm and was designed from a small set of protocols and structures that can easily be composed to fit your project needs. Thus, you can think of `Peppermint` as an adjustable wrench more than a Swiss knife.
Since validation can take place at many levels, `Peppermint` is available on iOS, macOS, tvOS, watchOS and native Swift projects, such as server-side apps.
## Requirements
- Swift 4.2+
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 8.1+## Installation
`Peppermint` is available only through Swift Package Manager.
### Swift Package Manager
You can add `Peppermint` to your project [in Xcode][url-swift-package-manager] by going to `File > Swift Packages > Add Package Dependency`.
Or, if you want to use it as a dependency to your own package, you can add it to your `Package.swift` file:
```swift
import PackageDescriptionlet package = Package(
name: "YOUR_PROJECT_NAME",
targets: [],
dependencies: [
.Package(url: "https://github.com/nsagora/peppermint", majorVersion: 1),
]
)
```## Usage example
For a comprehensive list of examples try out the `Examples.playground`:
1. Download the repository locally on your machine
2. Open the project in Xcode
3. Select the `Examples` playground from the Project navigatorThe `Peppermint` framework is compact and offers you the foundation you need to build data validation around your project needs. In addition, it includes a set of common validation predicates and constraints that most projects can benefit off.
### Predicates
The `Predicate` represents the core `protocol` and has the role to `evaluate` if an input matches on a given validation condition.
At the core of `Peppermint` there are the following two predicates, which allows you to compose predicates specific to the project needs:
BlockPredicate
```swift
let predicate = BlockPredicate { $0.characters.count > 2 }
predicate.evaluate(with: "a") // returns false
predicate.evaluate(with: "abc") // returns true
```RegexPredicate
```swift
let predicate = RegexPredicate(expression: "^[a-z]$")
predicate.evaluate(with: "a") // returns true
predicate.evaluate(with: "5") // returns false
predicate.evaluate(with: "ab") // returns false
```In addition, the framework offers a set of common validation predicates that your project can benefit of:
EmailPredicate
```swift
let predicate = EmailPredicate()
predicate.evaluate(with: "hello@") // returns false
predicate.evaluate(with: "[email protected]") // returns true
predicate.evaluate(with: "hΓ©[email protected]") // returns true
```URLPredicate
```swift
let predicate = URLPredicate()
predicate.evaluate(with: "http://www.url.com") // returns true
predicate.evaluate(with: "http:\\www.url.com") // returns false
```RangePredicate
```swift
let predicate = let range = RangePredicate(10...20)
predicate.evaluate(with: 15) // returns true
predicate.evaluate(with: 21) // returns false
```LengthPredicate
```swift
let predicate = LengthPredicate(min: 5)
predicate.evaluate(with: "abcde") // returns true
predicate.evaluate(with: "abcd") // returns false
```On top of that, developers can build more advanced or complex predicates by extending the `Predicate` protocol, and/ or by composing or decorating the existing predicates:
Custom Predicate
```swift
public struct CustomPredicate: Predicate {public typealias InputType = String
private let custom: String
public init(custom: String) {
self.custom = custom
}public func evaluate(with input: String) -> Bool {
return input == custom
}
}let predicate = CustomPredicate(custom: "alphabet")
predicate.evaluate(with: "alp") // returns false
predicate.evaluate(with: "alpha") // returns false
predicate.evaluate(with: "alphabet") // returns true
```### Constraints
#### Predicate Constraint
A `PredicateConstraint` represents a data type that links a `Predicate` to an `Error`, in order to provide useful feedback for the end users.
PredicateConstraint
```swift
let constraint = PredicateConstraint(.email, error: .invalid)let result = constraint.evaluate(with: "[email protected]")
switch result {
case .valid:
print("Hi there π!")
case .invalid(let summary):
print("Oh, I was expecting a valid email address!")
} // prints "Hi there π!"
``````swift
enum MyError: Error {
case invalid
}
```#### Block Constraint
A `BlockConstraint` represents a data type that links a custom validation closure to an `Error` that describes why the evaluation has failed. It's a shortcut of a `PredicateConstraint` that is initialised with a `BlockPredicate`.
BlockConstraint
```swift
let constraint = BlockConstraint {
$0 % 2 == 0
} errorBuilder: {
.magicNumber
}constraint.evaluate(with: 3)
``````swift
enum Failure: MyError {
case magicNumber
}
```#### Group Constraint
A `GroupConstraint` represents a composition of constraints that allows the evaluation to be made on:
- all constraints
- or any of the constraintsTo provide context, a `GroupConstraint` allows us to constraint a piece of data as being required and also as being a valid email.
GroupConstraint(.all) {
PredicateConstraint {
.characterSet(.lowercaseLetters, mode: .loose)
} errorBuilder: {
.missingLowercase
}
PredicateConstraint{
.characterSet(.uppercaseLetters, mode: .loose)
} errorBuilder: {
.missingUppercase
}
PredicateConstraint {
.characterSet(.decimalDigits, mode: .loose)
} errorBuilder: {
.missingDigits
}
PredicateConstraint {
.characterSet(CharacterSet(charactersIn: "!?@#$%^&*()|\\/<>,.~`_+-="), mode: .loose)
} errorBuilder: {
.missingSpecialChars
}
PredicateConstraint {
.length(min: 8)
} errorBuilder: {
.minLength(8)
}
}let password = "3nGuard!"
let result = passwordConstraint.evaluate(with: password)switch result {
case .success:
print("Wow, that's a πͺ password!")
case .failure(let summary):
print(summary.errors.map({$0.localizedDescription}))
} // prints "Wow, that's a πͺ password!"
```From above, we see that once we've constructed the `passwordConstraint`, we're simply calling `evaluate(with:)` to get our evaluation `Result`. This contains a `Summary` that can be handled as we please.
## Contribute
We would love you for the contribution to **Peppermint**, check the [`LICENSE`][url-license-file] file for more info.
## Meta
This project is developed and maintained by the members of [iOS NSAgora][url-twitter], the community of iOS Developers of IaΘi, Romania.
Distributed under the [MIT][url-license] license. See [`LICENSE`][url-license-file] for more information.
[https://github.com/nsagora/peppermint]
[url-peppermint]: https://github.com/nsagora/peppermint
[url-peppermint-docs]: https://nsagora.github.io/peppermint/
[url-swift-package-manager]: https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app
[url-license]: http://choosealicense.com/licenses/mit/
[url-license-file]: https://github.com/nsagora/peppermint/blob/master/LICENSE
[url-twitter]: https://x.com/nsagora
[url-codecov]: https://codecov.io/gh/nsagora/peppermint
[badge-license]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat
[badge-twitter]: https://img.shields.io/badge/π-%40nsgaora-blue.svg?style=flat
[badge-build-macos]: https://github.com/nsagora/peppermint/actions/workflows/build-macos.yml/badge.svg
[badge-build-linux]: https://github.com/nsagora/peppermint/actions/workflows/build-linux.yml/badge.svg
[badge-codecov]: https://codecov.io/gh/nsagora/peppermint/branch/develop/graph/badge.svg
[badge-version]: https://img.shields.io/badge/version-1.2.0-blue.svg?style=flat
[badge-docs]: https://github.com/nsagora/peppermint/actions/workflows/docs.yml/badge.svg