RxValidator

Easy to Use, Read, Extensible, Flexible Validation Checker.

It can use without Rx.

Requirements

RxValidator is written in Swift 4. Compatible with iOS 8.0+

Installation

CocoaPods

RxValidator is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod 'RxValidator'

At a Glance

You just use like this:


//without Rx
Validate.to(TargetValue)
    .validate(condition)
        ...
    .validate(condition)
    .asObservable() or .check()
    
//with Rx
observable
    .validate(condition)
        ...
    .validate(condition)
    .subscribe(...)
    

Usage

String

	
Validate.to("word is not empty")
    .validate(StringShouldNotBeEmpty())
    .check()
// result -> RxValidatorResult.valid

//multiple condition
Validate.to("[email protected]")
    .validate(StringShouldNotBeEmpty())
    .validate(StringIsNotOverflowThen(maxLength: 50))
    .validate(StringShouldBeMatch("[a-z]+@[a-z]+\\.[a-z]+"))
    .check()
// result -> RxValidatorResult.valid

Date


let targetDate: Date //2018-05-05
let sameTargetDate: Date
let afterTargetDate: Date
let beforeTargetDate: Date

Validate.to(Date())
	.validate(.shouldEqualTo(date: sameTargetDate))             //(1)
	.validate(.shouldAfterOrSameThen(date: sameTargetDate))     //(2)
	.validate(.shouldBeforeOrSameThen(date: sameTargetDate))    //(3)
	.validate(.shouldBeforeOrSameThen(date: afterTargetDate))   //(4)
	.validate(.shouldBeforeThen(date: afterTargetDate))         //(5)
	.validate(.shouldAfterOrSameThen(date: beforeTargetDate))   //(6)
	.validate(.shouldAfterThen(date: beforeTargetDate))         //(7)
	.check()
	
	// check() result
	
	// valid result  -> RxValidatorResult.valid
	
	// (1) not valid -> RxValidatorResult.notEqualDate
	// (2) not valid -> RxValidatorResult.notAfterDate
	// (3) not valid -> RxValidatorResult.notBeforeDate
	// (4) not valid -> RxValidatorResult.notBeforeDate
	// (5) not valid -> RxValidatorResult.notBeforeDate
	// (6) not valid -> RxValidatorResult.notAfterDate
	// (7) not valid -> RxValidatorResult.notAfterDate

Int

Validate.to(2)
    .validate(NumberShouldBeEven())
    .check()
    //.valid
    
Validate.to(1)
    .validate(NumberShouldBeEven())
    .check()
    //.notEvenNumber

Working with RxSwift

String

	
Validate.to("word is not empty")
    .validate(StringShouldNotBeEmpty())
    .asObservable()
    .subscribe(onNext: { value in
        print(value)
	//print("word is not empty")
    })
    .disposed(by: disposeBag)

Validate.to("word is not empty")
    .validate(StringShouldNotBeEmpty())
    .asObservable()
    .map { $0 + "!!" }
    .bind(to: anotherObservableBinder)
    .disposed(by: disposeBag)
	

//Multiple condition
Validate.to("[email protected]")
    .validate(StringShouldNotBeEmpty())                         //(1)
    .validate(StringIsNotOverflowThen(maxLength: 50))           //(2)
    .validate(StringShouldBeMatch("[a-z]+@[a-z]+\\.[a-z]+"))    //(3)
    .asObservable()
    .subscribe(onNext: { value in
        print(value)
        //print("[email protected]")
    },
    onError: { error in
        let validError = RxValidatorResult.determine(error: error)
        // (1) validError -> RxValidatorResult.stringIsEmpty
        // (2) validError -> RxValidatorResult.stringIsOverflow
        // (3) validError -> RxValidatorResult.stringIsNotMatch
    })
    .disposed(by: disposeBag)
		

Int

Validate.to(2)
    .validate(NumberShouldBeEven())
    .asObservable()
    .subscribe(onNext: { value in
        print(value)
        //print(2)
    })
    .disposed(by: disposeBag)
    
Validate.to(1)
    .validate(NumberShouldBeEven())
    .asObservable()
    .subscribe(onNext: { value in
        print(value)
        //print(1)
    },
    onError: { error in
        let validError = RxValidatorResult.determine(error: error)
        //validError -> RxValidatorResult.notEvenNumber
    })
    .disposed(by: disposeBag)

Date


let targetDate: Date //2018-05-05
let sameTargetDate: Date
let afterTargetDate: Date
let beforeTargetDate: Date

Validate.to(Date())
	.validate(.shouldEqualTo(date: sameTargetDate))             //(1)
	.validate(.shouldAfterOrSameThen(date: sameTargetDate))     //(2)
	.validate(.shouldBeforeOrSameThen(date: sameTargetDate))    //(3)
	.validate(.shouldBeforeOrSameThen(date: afterTargetDate))   //(4)
	.validate(.shouldBeforeThen(date: afterTargetDate))         //(5)
	.validate(.shouldAfterOrSameThen(date: beforeTargetDate))   //(6)
	.validate(.shouldAfterThen(date: beforeTargetDate))         //(7)
	.asObservable()
	.subscribe(onNext: { value in
        print(value) //print("2018-05-05")
	}, onError: { error in
		let validError = RxValidatorResult.determine(error: error)
		
        // (1) validError -> RxValidatorResult.notEqualDate
        // (2) validError -> RxValidatorResult.notAfterDate
        // (3) validError -> RxValidatorResult.notBeforeDate
        // (4) validError -> RxValidatorResult.notBeforeDate
        // (5) validError -> RxValidatorResult.notBeforeDate
        // (6) validError -> RxValidatorResult.notAfterDate
        // (7) validError -> RxValidatorResult.notAfterDate
	})
	.disposed(by: disposeBag)

Chaining from Observable


textField.rx.text
    .filterNil()
    .validate(StringIsAlwaysPass())
    .subscribe(onNext: { (text) in
        print(text)
    })
    .disposed(by: disposeBag)
        
let text = PublishSubject<String>()
text
    .validate(StringIsAlwaysPass())
    .subscribe(onNext: { (text) in
        print(text)
    })
    .disposed(by: disposeBag)

Instant Condition Validation



Validate.to("swift")
    .validate({ $0 == "objc" })
    .check()

Validate.to(7)
    .validate({ $0 > 10 })
    .check()

Validate.to(Date())
    .validate({ !$0.isToday })
    .check()
    

Validate.to("swift")
    .validate({ $0 == "objc" }, message: "This is not swift")
    .check()

Validate.to(7)
    .validate({ $0 > 10 }, message: "Number is too small.")
    .check()

Validate.to(Date())
    .validate({ !$0.isToday }, message: "It is today!!")
    .check()


ResultType

enum RxValidatorResult

    case notValid
    case notValidWithMessage(message: String)
    case notValidWithCode(code: Int)
    
    case undefinedError
    
    case stringIsOverflow
    case stringIsEmpty
    case stringIsNotMatch
    
    case notEvenNumber
    
    case invalidateDateTerm
    case notBeforeDate
    case notAfterDate
    case notEqualDate

Working with ReactorKit (http://reactorkit.io)

func mutate(action: Action) -> Observable<Mutation> {
....

case let .changeTitle(title):
  return Validate.to(title)
    .validate(StringIsNotOverflowThen(maxLength: TITLE_MAX_LENGTH))
    .asObservable()
    .flatMap { Observable<Mutation>.just(.updateTitle(title: $0)) }
    .catchError({ (error) -> Observable<Mutation> in
        let validError = ValidationTargetErrorType.determine(error: error)
        return Observable<Mutation>.just(.setTitleValidateError(validError, title))
    })

....

Supported Validation Rules

//String
StringShouldNotBeEmpty()
StringIsNotOverflowThen(maxLength: Int)
StringShouldBeMatch("regex string")

//Int
NumberShouldBeEven()

//Date
DateValidatorType.shouldEqualTo(Date)
DateValidatorType.shouldBeforeThen(Date)
DateValidatorType.shouldBeforeOrSameThen(Date)
DateValidatorType.shouldAfterThen(Date)
DateValidatorType.shouldAfterOrSameThen(Date)
DateValidatorType.shouldBeCloseDates(date: Date, termOfDays: Int)

Make custom ValidationRule like this:

//String Type
class MyCustomStringValidationRule: StringValidatorType {
    func validate(_ value: String) throws {
        if {notValidCondition} {
            throw RxValidatorResult.notValidate(code: 999) //'code' must be defined your self.  
        }
    }
}


//Int Type
class MyCustomIntValidationRule: IntValidatorType {
    func validate(_ value: Int) throws {
        if {notValidCondition} {
            throw RxValidatorResult.notValidate(code: 999) //'code' must be defined your self.  
        }
    }
}


I want to be...

  • More Built-in Validation Rules. (Help me. Welcome to PR.)
  • Support More Type (Array, Float, Double, etc)
  • More Flexible Code via Generics.

GitHub

https://github.com/vbmania/RxValidator