How does EK9 handle input validation?
← Security and Sanitization · Ref: Q269
EK9 provides four validation mechanisms that work together: constrained types for structural validation, guard expressions for conditional execution, require statements for preconditions, and sanitized parameters for taint tracking.
CONSTRAINED TYPES
Constrained types validate at construction time. A bare constructor ASSERTS the value is valid: a set value that violates the constraint panics at runtime (and a literal constant that provably violates is the compile error E08260), so use a bare constructor only for values you already trust:
EmailAddress as String constrain as matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ port <- Port(8080)
For untrusted or boundary input use the fallible .of(value) factory instead. It takes the base-type value and returns an UNSET object if the value violates the constraint (or is itself unset), and never panics.
GUARD EXPRESSIONS
Guards combine assignment with set-checking in one line:
if email <- EmailAddress().of(userInput) processEmail(email)
The block only executes if construction succeeds. This prevents processing of invalid data.
REQUIRE STATEMENTS
Require validates preconditions at the start of a function:
require input? require length input > 0
A failed require throws an exception, enforcing the contract.
SANITIZED PARAMETERS
The sanitized modifier tracks external input through the system:
-> userInput as sanitized String
The compiler enforces the copy constructor pattern for safe handling.
FOUR LAYERS TOGETHER
The complete validation flow uses all four:
1. sanitized marks the input as external 2. Constrained type validates the format 3. Guard expression checks construction succeeded 4. require enforces additional business rules
Each layer catches a different class of invalid input.
See Q215 for sanitized parameters. See Q257 for constrained types. See Q29 for unset variables and guards. See Q74 for guard expressions. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.
Example
defines module qa.security.inputvalidation defines type // Constrained type: validates format at construction EmailAddress as String constrain as matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ // Constrained type: validates range Port as Integer constrain >= 1 and <= 65535 defines function // All four validation layers validateAndProcess() as pure -> input as sanitized String <- result as String: "Invalid" // Layer 1: sanitized marks input as external (parameter) // Layer 2: copy constructor + constrained type validates safeCopy <- String(input) // Layer 3: guard checks fallible construction succeeded if email <- EmailAddress().of(safeCopy) // Layer 4: require enforces business rules require length $email > 5 result: "Valid email: " + $email defines program InputValidationDemo() stdout <- Stdout() // === CONSTRAINED TYPE VALIDATION === if email <- EmailAddress().of("user@example.com") stdout.println("Valid: " + $email) if badEmail <- EmailAddress().of("not-an-email") stdout.println("Should not reach here (email)") else stdout.println("Invalid email caught by constraint") // === PORT RANGE VALIDATION === if port <- Port().of(8080) stdout.println("Valid port: " + $port) if badPort <- Port().of(99999) stdout.println("Should not reach here (port)") else stdout.println("Invalid port caught by constraint") // === COMBINED VALIDATION === validResult <- validateAndProcess("user@example.com") if validResult? stdout.println(validResult) invalidResult <- validateAndProcess("bad") if invalidResult? stdout.println("Should not reach here (combined)") else stdout.println("Combined validation rejected invalid input")
Common mistakes
E50001 — Renaming the variable means later references become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.
Incorrect:
validResultXYZ <- validateAndProcess("user@example.com")
Correct:
validResult <- validateAndProcess("user@example.com")
E50010 — Only built-in types like String, Integer, and Float can be constrained. Custom records and classes cannot be constrained because they do not have the comparison semantics needed for constraint evaluation. See ek9 -h E50010 for details.
Incorrect:
ValidatedUser as UserRecord constrain as matches /^[a-zA-Z]+$/
Correct:
EmailAddress as String constrain as matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
Other ways to ask this
- What input validation mechanisms does EK9 provide?
- How do I validate user input in EK9?
- How do constrained types and guards work together for validation?
Coming from another language?
Java: Bean Validation annotations, manual null checks, OWASP ESAPI. Python: no compile-time validation, runtime libraries. Rust: newtype pattern with manual validation. Go: manual if-err checks. Kotlin: nullable types and require() function. EK9: four integrated mechanisms (constrained types, guards, require, sanitized) that compose into a validation pipeline.
Keywords: isset, security, sanitize, null-safe, check, validate, protect, safe, validation, constrain, require, guard, input, form, verify