薄荷 (Peppermint) badge-version

badge-build-macos badge-build-linux badge-docs badge-codecov badge-license badge-twitter

  1. 简介
  2. 要求
  3. 安装
  4. 使用示例
  5. 贡献
  6. 元信息

简介

let constraint = TypeConstraint<Account, Account.Error> {
    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 是一个声明式的、轻量级的数据验证框架。

其核心有两个原则:

每个项目在挑战上都是独一无二的,如果我们能专注于解决这些挑战,而不是把时间花在样板任务上,那就太好了。

考虑到这一点,该框架遵循面向协议编程的范式,并由一小组协议和结构设计而成,可以轻松地组合以满足您的项目需求。 因此,您可以将 Peppermint 视为一个可调节的扳手,而不是瑞士军刀。

由于验证可以在多个级别进行,因此 Peppermint 可用于 iOS、macOS、tvOS、watchOS 和原生 Swift 项目,例如服务器端应用程序。

要求

安装

Peppermint 仅通过 Swift Package Manager 提供。

Swift Package Manager

您可以通过转到 File > Swift Packages > Add Package Dependency,在 Xcode 中将 Peppermint 添加到您的项目

或者,如果您想将其用作您自己软件包的依赖项,您可以将其添加到您的 Package.swift 文件中

import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    targets: [],
    dependencies: [
        .Package(url: "https://github.com/nsagora/peppermint", majorVersion: 1),
    ]
)

使用示例

有关全面的示例列表,请尝试 Examples.playground

  1. 在您的机器上本地下载存储库
  2. 在 Xcode 中打开项目
  3. 从项目导航器中选择 Examples playground

Peppermint 框架紧凑,为您提供了围绕项目需求构建数据验证所需的基础。 此外,它还包括一组常见的验证谓词和约束,大多数项目都可以从中受益。

谓词 (Predicates)

Predicate 代表核心 protocol,其作用是 evaluate 输入是否匹配给定的验证条件。

Peppermint 的核心,以下有两个谓词,允许您组合特定于项目需求的谓词

BlockPredicate
let predicate = BlockPredicate<String> { $0.characters.count > 2 }
predicate.evaluate(with: "a") // returns false
predicate.evaluate(with: "abc") // returns true
RegexPredicate
let predicate = RegexPredicate(expression: "^[a-z]$")
predicate.evaluate(with: "a") // returns true
predicate.evaluate(with: "5") // returns false
predicate.evaluate(with: "ab") // returns false

此外,该框架提供了一组常见的验证谓词,您的项目可以从中受益

EmailPredicate
let predicate = EmailPredicate()
predicate.evaluate(with: "hello@") // returns false
predicate.evaluate(with: "hello@nsagora.com") // returns true
predicate.evaluate(with: "héllo@nsagora.com") // returns true
URLPredicate
let predicate = URLPredicate()
predicate.evaluate(with: "http://www.url.com") // returns true
predicate.evaluate(with: "http:\\www.url.com") // returns false
RangePredicate
let predicate = let range = RangePredicate(10...20)
predicate.evaluate(with: 15) // returns true
predicate.evaluate(with: 21) // returns false
LengthPredicate
let predicate = LengthPredicate<String>(min: 5)
predicate.evaluate(with: "abcde")   // returns true
predicate.evaluate(with: "abcd")    // returns false

最重要的是,开发人员可以通过扩展 Predicate 协议,和/或通过组合或装饰现有谓词来构建更高级或更复杂的谓词

自定义谓词
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)

PredicateConstraint 表示一种将 Predicate 链接到 Error 的数据类型,以便为最终用户提供有用的反馈。

PredicateConstraint
let constraint = PredicateConstraint<String, MyError>(.email, error: .invalid)

let result = constraint.evaluate(with: "hello@nsagora.com")
switch result {
case .valid:
    print("Hi there 👋!")
case .invalid(let summary):
    print("Oh, I was expecting a valid email address!")
}  // prints "Hi there 👋!"
enum MyError: Error {
    case invalid
}

Block Constraint

BlockConstraint 表示一种将自定义验证闭包链接到 Error 的数据类型,用于描述评估失败的原因。 它是使用 BlockPredicate 初始化的 PredicateConstraint 的快捷方式。

BlockConstraint
let constraint = BlockConstraint<Int, MyError> {
    $0 % 2 == 0
} errorBuilder: {
    .magicNumber
}

constraint.evaluate(with: 3)
enum Failure: MyError {
    case magicNumber
}

Group Constraint

GroupConstraint 表示约束的组合,允许在以下情况下进行评估

为了提供上下文,GroupConstraint 允许我们将一段数据约束为必需的,并且是有效的电子邮件。

GroupConstraint一个注册表的例子,用户需要输入一个强壮的 *密码*。这个过程通常需要一些形式的验证,但是逻辑本身通常是非结构化的,并且分散在一个视图控制器中。

Peppermint 试图整合、标准化并明确用于验证用户输入的逻辑。为此,下面的示例演示了如何构建一个完整的 GroupConstraint 对象,该对象可用于强制执行用户密码数据的要求

var passwordConstraint = GroupConstraint<String, Form.Password>(.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!"

从上面我们可以看到,一旦我们构建了 passwordConstraint,我们只是调用 evaluate(with:) 来获取我们的评估 Result。 这包含一个可以随意处理的 Summary

贡献

我们非常感谢您为 Peppermint 做出贡献,请查看 LICENSE 文件以获取更多信息。

元信息

该项目由罗马尼亚 Iași 的 iOS 开发者社区 iOS NSAgora 的成员开发和维护。

MIT 许可下分发。 有关更多信息,请参见 LICENSE

[https://github.com/nsagora/peppermint]