Version 0 of String validation

Updated 2011-06-04 22:27:57 by Googie

Googie I playied a little with jquery and its string validation feature lately and I really liked it. I also neeeded server-side validation for values provided in a form, so I wrote something similar.

Few words about how to use it. First argument is a list with even number of elements. They are used as pairs: {fieldName ruleList}. The fieldName is actually variable name from current context. RuleList is also a list. Supported rules are described in sourcecode header. Some rules require parameters - in these cases rule should be specified as a list itself, where first argument is the rule and rest elements are parameters to the rule.

This might seem complicated, but there's an example below the sourcecode, so I think you will find it very simple and cool to use ;) I do so.

Second argument to the procedure is similar mapping for messages in case some rule is not satisfied. If message(s) is not specified, then default one is used. Not much to explain here - see example below.

Here's code:

#
# Rules: integer, boolean, double, email, url, glob, regexp, required, minlength, maxlength, equalTo, function
#
proc validate {varAndRules {messages ""}} {
        set details [dict create]
        set result [dict create valid true numberOfErrors 0]
        foreach {varName rules} $varAndRules {
                upvar $varName local_$varName
                if {![info exists local_$varName]} {
                        set value ""
                } else {
                        set value [set local_$varName]
                }

                set fieldDetails [dict create valid true errors [list]]
                
                if {[dict exists $messages $varName]} {
                        set fieldMessages [dict get $messages $varName]
                } else {
                        set fieldMessages [dict create]
                }

                foreach rule $rules {
                        set ruleType [lindex $rule 0]
                        if {[dict exists $fieldMessages $ruleType]} {
                                set fieldMessage [dict get $fieldMessages $ruleType]
                        } else {
                                set fieldMessage ""
                        }

                        set valid true
                        set msg ""
                        switch -- $ruleType {
                                "required" {
                                        if {$value == ""} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' is required."}]
                                        }
                                }
                                "notEmpty" {
                                        if {[string trim $value] == ""} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' cannot be empty."}]
                                        }
                                }
                                "integer" {
                                        if {$value == "" || ![string is integer $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' has to be an integer."}]
                                        }
                                }
                                "boolean" {
                                        if {$value == "" || ![string is boolean $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' has to be a boolean."}]
                                        }
                                }
                                "double" {
                                        if {$value == "" || ![string is double $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' has to be an a double."}]
                                        }
                                }
                                "email" {
                                        if {![regexp -- {^[A-Z0-9._%+-]+\@[A-Z0-9.-]+\.[A-Z]{2,4}$} $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' has to be valid e-mail address."}]
                                        }
                                }
                                "glob" {
                                        set ruleArg [lindex $rule 1]
                                        if {![string match $ruleArg $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName $ruleArg] :
                                                        "'$varName' has to match '$ruleArg' mask."}]
                                        }
                                }
                                "regexp" {
                                        set ruleArg [lindex $rule 1]
                                        if {![regexp -- $ruleArg $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName $ruleArg] :
                                                        "'$varName' has to match '$ruleArg' regular expression."}]
                                        }
                                }
                                "url" {
                                        if {![regexp -- {^((http|https|ftp):\/\/)?(www\.)?([a-z0-9\-\.]{4,})(\/)?.*$} $value]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName] :
                                                        "'$varName' has to be valid URL."}]
                                        }
                                }
                                "minlength" {
                                        set ruleArg [lindex $rule 1]
                                        if {[string length $value] < $ruleArg} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName $ruleArg] :
                                                        "'$varName' has to be at least $ruleArg characters length."}]
                                        }
                                }
                                "maxlength" {
                                        set ruleArg [lindex $rule 1]
                                        if {[string length $value] > $ruleArg} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName $ruleArg] :
                                                        "'$varName' has to be $ruleArg characters length at most."}]
                                        }
                                }
                                "equalTo" {
                                        set ruleArg [lindex $rule 1]
                                        set secondValue [uplevel [list set $ruleArg]]
                                        if {![string equal $value $secondValue]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName $ruleArg] :
                                                        "'$varName' has to be equal to '$ruleArg'."}]
                                        }
                                }
                                "function" {
                                        set ruleArg [lindex $rule 1]
                                        set fnName [lindex $ruleArg 0]
                                        set call $fnName
                                        foreach fnArg [lrange $ruleArg 1 end] {
                                                if {[string match "\$*" $fnArg]} {
                                                        lappend call [uplevel [list set [string range $fnArg 1 end]]]
                                                } else {
                                                        lappend call $fnArg
                                                }
                                        }
                                        if {![uplevel #0 $call]} {
                                                set valid false
                                                set msg [expr {$fieldMessage != "" ?
                                                        [format $fieldMessage $varName $fnName] :
                                                        "'$varName' doesn't satisfy requirements of '$fnName' function."}]
                                        }
                                }
                        }
                        if {!$valid} {
                                dict set fieldDetails valid false
                                dict lappend fieldDetails errors $msg
                                dict set result valid false
                                dict incr result numberOfErrors
                        }
                }
                dict set details $varName $fieldDetails
        }
        dict set result details $details
        return $result
}

Example of usage:

proc checkIfLoginAvailable {u} {
        expr {$u == "taken_name"}
}

set name "aa"
set email "bb"
set pass1 "123"
set pass2 "545"
set username "test123"

set res [validate {
        name {
                required
        }
        email {
                required
                email
        }
        pass1 {
                required
        }
        pass2 {
                required
                {equalTo pass1}
        }
        username {
                notEmpty
                {function {checkIfLoginAvailable $username}}
        }
} {
        name {
                required {My custom message about field %s.}
        }
        pass2 {
                equalTo {Password mismatch.}
        }
}]

puts "errors: [dict get $res numberOfErrors]"
dict for {key val} [dict get $res details] {
        puts "$key: $val"
}

Result would be:

errors: 3
name: valid true errors {}
email: valid false errors {{'email' has to be valid e-mail address.}}
pass1: valid true errors {}
pass2: valid false errors {{Password mismatch.}}
username: valid false errors {{'username' doesn't satisfy requirements of 'checkIfLoginAvailable' function.}}