Git Product home page Git Product logo

validatedpropertykit's Introduction


ValidatedPropertyKit Logo

ValidatedPropertyKit

A Swift Package to easily validate your properties using Property Wrappers ๐Ÿ‘ฎ

Swift Version Platforms
Build and Test Status Documentation Twitter

import SwiftUI
import ValidatedPropertyKit

struct LoginView: View {

    @Validated(!.isEmpty && .isEmail)
    var mailAddress = String()

    @Validated(.range(8...))
    var password = String()

    var body: some View {
        List {
            TextField(
               "E-Mail",
               text: self.$mailAddress
            )
            if self._mailAddress.isInvalidAfterChanges {
                Text(verbatim: "Please enter a valid E-Mail address.")
            }
            TextField(
               "Password",
               text: self.$password
            )
            if self._password.isInvalidAfterChanges {
                Text(verbatim: "Please enter a valid password.")
            }
            Button {
               print("Login", self.mailAddress, self.password)
            } label: {
               Text(verbatim: "Submit")
            }
            .validated(
                self._mailAddress,
                self._password
            )
        }
    }

}

Features

  • Easily validate your properties ๐Ÿ‘ฎ
  • Predefined validations ๐Ÿšฆ
  • Logical Operators to combine validations ๐Ÿ”—
  • Customization and configuration to your needs ๐Ÿ’ช

Installation

Swift Package Manager

To integrate using Apple's Swift Package Manager, add the following as a dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/SvenTiigi/ValidatedPropertyKit.git", from: "0.0.6")
]

Or navigate to your Xcode project then select Swift Packages, click the โ€œ+โ€ icon and search for ValidatedPropertyKit.

Manually

If you prefer not to use any of the aforementioned dependency managers, you can integrate ValidatedPropertyKit into your project manually. Simply drag the Sources Folder into your Xcode project.

Validated ๐Ÿ‘ฎโ€โ™‚๏ธ

The @Validated attribute allows you to specify a validation alongside to the declaration of your property.

Note: @Validated supports SwiftUI View updates and will basically work the same way as @State does.

@Validated(!.isEmpty)
var username = String()

@Validated(.hasPrefix("https"))
var avatarURL: String?

If @Validated is applied on an optional type e.g. String? you can specify whether the validation should fail or succeed when the value is nil.

@Validated(
   .isURL && .hasPrefix("https"),
   isNilValid: true
)
var avatarURL: String?

By default the argument nilValidation is set to .constant(false)

In addition the SwiftUI.View extension validated() allows you to disable or enable a certain SwiftUI.View based on your @Validated properties. The validated() function will disable the SwiftUI.View if at least one of the passed in @Validated properties evaluates to false.

@Validated(!.isEmpty && .contains("@"))
var mailAddress = String()

@Validated(.range(8...))
var password = String()

Button(
   action: {},
   label: { Text("Submit") }
)
.validated(self._mailAddress && self._password)

By using the underscore notation you are passing the @Validated property wrapper to the validated() function

Validation ๐Ÿšฆ

Each @Validated attribute will be initialized with a Validation which can be initialized with a simple closure that must return a Bool value.

@Validated(.init { value in
   value.isEmpty
})
var username = String()

Therefore, ValidatedPropertyKit comes along with many built-in convenience functions for various types and protocols.

@Validated(!.contains("Android", options: .caseInsensitive))
var favoriteOperatingSystem = String()

@Validated(.equals(42))
var magicNumber = Int()

@Validated(.keyPath(\.isEnabled, .equals(true)))
var object = MyCustomObject()

Head over the Predefined Validations section to learn more

Additionally, you can extend the Validation via conditional conformance to easily declare your own Validations.

extension Validation where Value == Int {

    /// Will validate if the Integer is the meaning of life
    static var isMeaningOfLife: Self {
        .init { value in
            value == 42
        }
    }

}

And apply them to your validated property.

@Validated(.isMeaningOfLife)
var number = Int()

isValid โœ…

You can access the isValid state at anytime by using the underscore notation to directly access the @Validated property wrapper.

@Validated(!.isEmpty)
var username = String()

username = "Mr.Robot"
print(_username.isValid) // true

username = ""
print(_username.isValid) // false

Validation Operators ๐Ÿ”—

Validation Operators allowing you to combine multiple Validations like you would do with Bool values.

// Logical AND
@Validated(.hasPrefix("https") && .hasSuffix("png"))
var avatarURL = String()

// Logical OR
@Validated(.hasPrefix("Mr.") || .hasPrefix("Mrs."))
var name = String()

// Logical NOT
@Validated(!.contains("Android", options: .caseInsensitive))
var favoriteOperatingSystem = String()

Predefined Validations

The ValidatedPropertyKit comes with many predefined common validations which you can make use of in order to specify a Validation for your validated property.

KeyPath

The keyPath validation will allow you to specify a validation for a given KeyPath of the attributed property.

@Validated(.keyPath(\.isEnabled, .equals(true)))
var object = MyCustomObject()

Strings

A String property can be validated in many ways like contains, hasPrefix and even RegularExpressions.

@Validated(.isEmail)
var string = String()

@Validated(.contains("Mr.Robot"))
var string = String()

@Validated(.hasPrefix("Mr."))
var string = String()

@Validated(.hasSuffix("OS"))
var string = String()

@Validated(.regularExpression("[0-9]+$"))
var string = String()

Equatable

A Equatable type can be validated against a specified value.

@Validated(.equals(42))
var number = Int()

Sequence

A property of type Sequence can be validated via the contains or startsWith validation.

@Validated(.contains("Mr.Robot", "Elliot"))
var sequence = [String]()

@Validated(.startsWith("First Entry"))
var sequence = [String]()

Collection

Every Collection type offers the isEmpty validation and the range validation where you can easily declare the valid capacity.

@Validated(!.isEmpty)
var collection = [String]()

@Validated(.range(1...10))
var collection = [String]()

Comparable

A Comparable type can be validated with all common comparable operators.

@Validated(.less(50))
var comparable = Int()

@Validated(.lessOrEqual(50))
var comparable = Int()

@Validated(.greater(50))
var comparable = Int()

@Validated(.greaterOrEqual(50))
var comparable = Int()

License

ValidatedPropertyKit
Copyright (c) 2022 Sven Tiigi [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

validatedpropertykit's People

Contributors

omaralbeik avatar sventiigi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

validatedpropertykit's Issues

Validation Error support

Hello Sven.
Wonderful project, well done!

Motivation

I believe it would be a nice addition to implement support for reporting validation errors. It can be helpful, for example, when you want to display a related error message to the user.

Solution

I played around with the project in a fork. I would love to implement the changes myself and submit a PR, but wanted to first do a proposal and open a discussion.

There are a couple of ways to do it. What first comes to my mind would be this:

  • Let's say that we have a special struct to report our errors:
public struct ValidationError: Error {
    let localizedMessage: String
    ...
}
  • The validation closure could return a Result<Value, ValidationError>, instead of a Bool.

  • The property wrapper could include a validationError optional property that receives values upon validation and gets set to nil when restore() is called.

  • When using, we would be able to check for a validationError and if there isn't any, we can go ahead and use the value.

Here's a rough example:

public extension Validation where Value == String {

    static var isValidPassword: Validation {
        return .init { (value) -> Result<String, ValidationError> in
            if value.count < 6 {
                return .failure(ValidationError(localizedMessage: "Password is too short. Minimum character count is 6."))
            }

            /* Additional checks

                ...

            */

            return .success(value)
        }
    }

}
if let errorMessage = $passwordInput.validationError?.localizedMessage {
    UserNotification.displayError(message: errorMessage)
    return 
}

// Safe to continue and use the passwordInput string

Additional Remarks

This feature would require quite some changes, but all of them would be internal to the API, meaning that the users of it won't have to change anything when using the default provided validations. However, their custom ones will have to be rewritten to return Results, instead of Bools.

All of this is a result of a small pre-lunch brainstorm. I'm sure there is a way to improve it ๐Ÿ˜„ Let me know what you think :)

Doesn't seems to work on latest iOS version ( 14.2 )

ValidatedPropertyKit Environment

  • ValidatedPropertyKit version: 0.0.4
  • macOS version: 11.0.1
  • Xcode version: 12.2
  • Dependency manager (Carthage, CocoaPods, SPM, Manually): SPM

What did you do?

I have
@Validated(.isEmail) var email: String? { didSet { emailError = _email.isValid } }
in an ObservableObject
In a view I have a TextField linked to email

What did you expect to happen?

I expected the emailError to contains a bool & email to contains the text that was typed.

What happened instead?

emailError do contains a bool but it's always false because when I type, the letter disappears.
The value is present when I call willSet but isn't anymore in the didSet.
If I remove the call to isValid it works.

It works as expected on devices running 14.1 or lower but not on devices running 14.2

SwiftUI Integration

Do you have any example of using Validated package with SwiftUI, specially in the case of TextField.

Validation on UITextField and UITextView

Motivation

It could be really cool if we could use @validated on UITextField and UITextView to simplify form validation.

Solution

I don't have any solution yet but the purpose is to have :
@Validated(.nonEmpty) var myTextField: UITextField
@Validated(.hasPrefix("prefix") var myTextView: UITextView

And to have a new class Validator which can validate a list of specific @validated.
let validator = Validator()
validator.add(myTextField)
validator.add(myTextView)
let isFormValid = validator.isValid()

Additional context

I use UITextField as example but it could be applied to any UIKit components, example:
@Validated(.isOn or .ifOff) var mySwitch: UISwitch
@Validated(.range(13...)) var myStepper: UIStepper

I'm not sur if it's possible to easily adapt your code to UIKit components.

Sugestivnรญ

Motivation

โ„น Please replace this with your motivation. For example if your feature request is related to a problem.

Solution

โ„น Please replace this with your proposed solution.

Additional context

โ„น Please replace this with any other context or screenshots about your feature request (optional).

Support for iOS 13.x?

Motivation

โ„น My app needs to support iOS 13 and this package requires 14

Solution

โ„น There only seems to be one incompatible thing, which is the use of @StateObject in Validated

Additional context

โ„น This doesn't look like it's too difficult of a change. Would it be a case of changing the @StateObject to maybe an @ObservedObject and calling it a day?

Referencing initializer 'init(wrappedValue:_:nilValidation:)' on 'Validated' requires that 'Published<String>' conform to 'Optionalable'

ValidatedPropertyKit Environment

  • ValidatedPropertyKit version:
    0.0.5

  • macOS version:
    11.6.2

  • Xcode version:
    13.2.1

  • Dependency manager (Carthage, CocoaPods, SPM, Manually):
    SPM

What did you do?

I'm using a view model.

import CoreData
import Foundation
import ValidatedPropertyKit

extension EditView {
    class ViewModel: NSObject, ObservableObject {
        let item: Item
        let add: Bool
        let hasRelationship: Bool

        private let dataController: DataController

        @Validated(!.isEmpty)
        @Published var title = "" <= error
        // @Published var title: String <= error 

What did you expect to happen?

To access the variable as shown in the readme examples.

What happened instead?

Error....

Referencing initializer 'init(wrappedValue:_:nilValidation:)' on 'Validated' requires 
that 'Published<String>' conform to 'Optionalable'

I'm not sure why this is occurring?

Thanks in advance.

Support validation for non NIL fields

Motivation

There are many occasions where you have non nil Published variables in SwiftUI's ViewModel, validation of this kind of fields would be a nice addition.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.