Parameter Validation with _$()

OpenAF provides a powerful parameter validation system through the _$() function, enabling type checking, value constraints, transformations, and automatic error handling with fluent, chainable syntax.

Table of Contents

  1. Parameter Validation with _$()
    1. Overview
    2. Basic Usage
      1. Simple Type Validation
      2. Validation with Default
    3. Type Validators
      1. isString()
      2. isNumber()
      3. isArray()
      4. isMap() / isObject()
      5. isBoolean()
      6. isFunction()
      7. isDate()
      8. isDef() / isUnDef()
    4. Value Constraints
      1. Range Validation
      2. Equality Checks
      3. OneOf (Enum)
      4. Pattern Matching (Regex)
      5. anyOf / check
    5. Type Conversion
      1. toNumber()
      2. toString()
      3. toBoolean()
      4. toDate()
    6. Default Values
      1. default()
    7. Chaining Validations
      1. Multiple Constraints
      2. Complex Validation Chain
    8. Common Patterns
      1. Pattern 1: Function Parameter Validation
      2. Pattern 2: API Request Validation
      3. Pattern 3: Configuration Validation
      4. Pattern 4: Array Element Validation
    9. oJob Integration
      1. Job Input Validation
      2. Complex Job Validation
      3. Output Validation
    10. Error Handling
      1. Throw Errors with $_()
      2. Get Value Without Throwing
      3. Custom Error Messages
      4. Try-Catch Pattern
    11. Advanced Techniques
      1. Custom Validators
      2. Conditional Validation
      3. Validation Builder
    12. Best Practices
      1. 1. Validate Early
      2. 2. Use Meaningful Names
      3. 3. Combine with Defaults
      4. 4. Document Validation Rules
      5. 5. Use Type Conversion
    13. Complete Example
    14. See Also

Overview

The _$() function allows you to:

  • Validate parameter types (string, number, array, map, etc.)
  • Apply constraints (range, regex, equality checks)
  • Transform values (type conversion, defaults)
  • Chain validations for complex rules
  • Automatic error throwing with descriptive messages

Basic Usage

Simple Type Validation

function greet(name) {
  _$(name, "name").isString().$_();

  print("Hello, " + name + "!");
}

greet("Alice");  // Works
greet(123);      // Throws: name is not a string

Validation with Default

function greet(name) {
  name = _$(name, "name").isString().default("Guest");

  print("Hello, " + name + "!");
}

greet("Alice");     // Hello, Alice!
greet(undefined);   // Hello, Guest!

Type Validators

isString()

_$(username, "username").isString().$_();

isNumber()

_$(age, "age").isNumber().$_();

isArray()

_$(items, "items").isArray().$_();

isMap() / isObject()

_$(config, "config").isMap().$_();

isBoolean()

_$(enabled, "enabled").isBoolean().$_();

isFunction()

_$(callback, "callback").isFunction().$_();

isDate()

_$(timestamp, "timestamp").isDate().$_();

isDef() / isUnDef()

// Ensure value is defined
_$(value, "value").isDef().$_();

// Ensure value is undefined
_$(optional, "optional").isUnDef().$_();

Value Constraints

Range Validation

// Number in range
_$(port, "port").isNumber().inRange(1, 65535).$_();

// Age between 0 and 150
_$(age, "age").isNumber().inRange(0, 150).$_();

Equality Checks

// Equals specific value
_$(status, "status").isString().equals("active").$_();

// Does not equal
_$(username, "username").isString().notEquals("").$_();

OneOf (Enum)

// Must be one of these values
_$(level, "level")
  .isString()
  .oneOf(["debug", "info", "warn", "error"])
  .$_();

// With numbers
_$(priority, "priority")
  .isNumber()
  .oneOf([1, 2, 3, 4, 5])
  .$_();

Pattern Matching (Regex)

// Email validation
_$(email, "email")
  .isString()
  .match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
  .$_();

// Phone number
_$(phone, "phone")
  .isString()
  .match(/^\d{3}-\d{3}-\d{4}$/)
  .$_();

// Alphanumeric
_$(code, "code")
  .isString()
  .match(/^[a-zA-Z0-9]+$/)
  .$_();

anyOf / check

// Custom validation function
_$(value, "value")
  .check(v => v > 0 && v < 100, "Value must be between 0 and 100")
  .$_();

// Multiple conditions (any can be true)
_$(input, "input")
  .anyOf([
    v => isString(v),
    v => isNumber(v)
  ])
  .$_();

Type Conversion

toNumber()

var age = _$(args.age, "age").toNumber();
// "25" → 25
// 25 → 25

toString()

var id = _$(args.id, "id").toString();
// 123 → "123"
// "abc" → "abc"

toBoolean()

var enabled = _$(args.enabled, "enabled").toBoolean();
// "true" → true
// "false" → false
// 1 → true
// 0 → false

toDate()

var date = _$(args.date, "date").toDate();
// "2024-01-15" → Date object
// 1705276800000 → Date object

Default Values

default()

// Use default if undefined
var timeout = _$(args.timeout, "timeout")
  .isNumber()
  .default(30000);

// Default with type conversion
var port = _$(args.port, "port")
  .toNumber()
  .default(8080);

// Default for complex types
var config = _$(args.config, "config")
  .isMap()
  .default({
    host: "localhost",
    port: 3000
  });

Chaining Validations

Multiple Constraints

// Username: string, not empty, alphanumeric, 3-20 chars
_$(username, "username")
  .isString()
  .notEquals("")
  .match(/^[a-zA-Z0-9]+$/)
  .check(v => v.length >= 3 && v.length <= 20, "Length must be 3-20")
  .$_();

Complex Validation Chain

// Port number with all checks
var port = _$(args.port, "port")
  .toNumber()                    // Convert to number
  .isNumber()                    // Validate it's a number
  .inRange(1, 65535)            // Valid port range
  .default(8080);               // Default if undefined

// Email with transformation
var email = _$(args.email, "email")
  .isString()                   // Must be string
  .notEquals("")                // Not empty
  .check(v => v.toLowerCase())  // Convert to lowercase
  .match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)  // Valid format
  .$_();

Common Patterns

Pattern 1: Function Parameter Validation

function createUser(username, email, age, config) {
  // Validate all parameters
  _$(username, "username")
    .isString()
    .notEquals("")
    .match(/^[a-zA-Z0-9_]+$/)
    .$_();

  _$(email, "email")
    .isString()
    .match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
    .$_();

  age = _$(age, "age")
    .toNumber()
    .isNumber()
    .inRange(13, 120)
    .default(18);

  config = _$(config, "config")
    .isMap()
    .default({
      notifications: true,
      theme: "light"
    });

  // Now safe to use parameters
  var user = {
    username: username,
    email: email,
    age: age,
    config: config
  };

  return user;
}

Pattern 2: API Request Validation

function handleAPIRequest(request) {
  // Validate method
  _$(request.method, "request.method")
    .isString()
    .oneOf(["GET", "POST", "PUT", "DELETE"])
    .$_();

  // Validate headers
  _$(request.headers, "request.headers")
    .isMap()
    .$_();

  // Validate authorization
  _$(request.headers.authorization, "authorization")
    .isString()
    .notEquals("")
    .match(/^Bearer .+$/)
    .$_();

  // Validate body for POST/PUT
  if (request.method == "POST" || request.method == "PUT") {
    _$(request.body, "request.body")
      .isMap()
      .$_();
  }

  // Process request
  return processRequest(request);
}

Pattern 3: Configuration Validation

function loadConfig(configFile) {
  var config = io.readFileYAML(configFile);

  // Validate server config
  _$(config.server, "config.server").isMap().$_();

  config.server.host = _$(config.server.host, "server.host")
    .isString()
    .default("localhost");

  config.server.port = _$(config.server.port, "server.port")
    .toNumber()
    .isNumber()
    .inRange(1, 65535)
    .default(8080);

  // Validate database config
  _$(config.database, "config.database").isMap().$_();

  _$(config.database.url, "database.url")
    .isString()
    .match(/^jdbc:/)
    .$_();

  _$(config.database.user, "database.user")
    .isString()
    .notEquals("")
    .$_();

  // Validate optional features
  config.features = _$(config.features, "config.features")
    .isMap()
    .default({});

  config.features.logging = _$(config.features.logging, "features.logging")
    .toBoolean()
    .default(true);

  return config;
}

Pattern 4: Array Element Validation

function processItems(items) {
  // Validate array
  _$(items, "items")
    .isArray()
    .check(arr => arr.length > 0, "Must have at least one item")
    .$_();

  // Validate each item
  items.forEach((item, idx) => {
    _$(item.id, `items[${idx}].id`)
      .isString()
      .notEquals("")
      .$_();

    _$(item.quantity, `items[${idx}].quantity`)
      .toNumber()
      .isNumber()
      .check(v => v > 0, "Quantity must be positive")
      .$_();

    item.price = _$(item.price, `items[${idx}].price`)
      .toNumber()
      .isNumber()
      .check(v => v >= 0, "Price cannot be negative")
      .default(0);
  });

  return items;
}

oJob Integration

Job Input Validation

jobs:
  - name: Process User
    check:
      in:
        username: isString
        email: isString
        age: isNumber.inRange(13,120).default(18)
        active: isBoolean.default(true)
    exec: |
      // Parameters are already validated
      log("Processing user: " + args.username);

Complex Job Validation

jobs:
  - name: Create Order
    check:
      in:
        customerId: isString.notEquals("")
        items: isArray.check("items.length > 0")
        paymentMethod: isString.oneOf(["card","paypal","bank"])
        billingAddress: isMap
        shippingAddress: isMap.default(__).check("billingAddress")
        notes: isString.default("")
    exec: |
      log("Creating order for customer: " + args.customerId);
      log("Items: " + args.items.length);

Output Validation

jobs:
  - name: Calculate Total
    check:
      in:
        items: isArray
      out:
        total: isNumber.check(">= 0")
        tax: isNumber.check(">= 0")
        currency: isString.default("USD")
    exec: |
      var total = 0;
      args.items.forEach(item => {
        total += item.price * item.quantity;
      });

      var tax = total * 0.1;

      args.total = total;
      args.tax = tax;
      args.currency = "USD";

Error Handling

Throw Errors with $_()

// Throws error if validation fails
_$(value, "value").isString().$_();

Get Value Without Throwing

// Returns validated value, doesn't throw
var validated = _$(value, "value").isString().default("default");

Custom Error Messages

_$(age, "age")
  .isNumber()
  .check(v => v >= 18, "Must be 18 or older to register")
  .$_();

Try-Catch Pattern

try {
  _$(username, "username")
    .isString()
    .match(/^[a-zA-Z0-9]+$/)
    .$_();

  processUser(username);

} catch(e) {
  if (e.message.indexOf("username") >= 0) {
    log("Invalid username format");
  } else {
    throw e;
  }
}

Advanced Techniques

Custom Validators

// Create reusable validator
function validateEmail(value, name) {
  return _$(value, name)
    .isString()
    .notEquals("")
    .match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
    .$_();
}

// Use it
validateEmail(args.email, "email");

Conditional Validation

function validate(data) {
  // Always validate type
  _$(data.type, "type")
    .isString()
    .oneOf(["personal", "business"])
    .$_();

  // Conditional validation based on type
  if (data.type == "business") {
    _$(data.companyName, "companyName")
      .isString()
      .notEquals("")
      .$_();

    _$(data.taxId, "taxId")
      .isString()
      .match(/^\d{9}$/)
      .$_();
  }

  if (data.type == "personal") {
    _$(data.firstName, "firstName")
      .isString()
      .notEquals("")
      .$_();

    _$(data.lastName, "lastName")
      .isString()
      .notEquals("")
      .$_();
  }
}

Validation Builder

function buildValidator(config) {
  return function(value, name) {
    var validator = _$(value, name);

    if (config.type == "string") {
      validator = validator.isString();
    } else if (config.type == "number") {
      validator = validator.toNumber().isNumber();
    }

    if (isDef(config.default)) {
      validator = validator.default(config.default);
    }

    if (isDef(config.pattern)) {
      validator = validator.match(new RegExp(config.pattern));
    }

    if (isDef(config.oneOf)) {
      validator = validator.oneOf(config.oneOf);
    }

    return validator.$_();
  };
}

// Usage
var validateStatus = buildValidator({
  type: "string",
  oneOf: ["active", "inactive", "pending"],
  default: "active"
});

validateStatus(args.status, "status");

Best Practices

1. Validate Early

function processOrder(order) {
  // Validate at the beginning
  _$(order, "order").isMap().$_();
  _$(order.id, "order.id").isString().$_();
  _$(order.items, "order.items").isArray().$_();

  // Now safe to process
  var total = calculateTotal(order.items);
  // ...
}

2. Use Meaningful Names

// Good - clear parameter name
_$(userEmail, "userEmail").isString().$_();

// Bad - generic name
_$(value, "value").isString().$_();

3. Combine with Defaults

// Provide sensible defaults
var timeout = _$(args.timeout, "timeout")
  .toNumber()
  .isNumber()
  .inRange(1, 300000)
  .default(30000);

var retries = _$(args.retries, "retries")
  .toNumber()
  .isNumber()
  .inRange(0, 10)
  .default(3);

4. Document Validation Rules

/**
 * Creates a new user account
 * @param {string} username - Alphanumeric, 3-20 characters
 * @param {string} email - Valid email format
 * @param {number} age - Between 13 and 120, defaults to 18
 */
function createUser(username, email, age) {
  _$(username, "username")
    .isString()
    .match(/^[a-zA-Z0-9]+$/)
    .check(v => v.length >= 3 && v.length <= 20)
    .$_();

  _$(email, "email")
    .isString()
    .match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
    .$_();

  age = _$(age, "age")
    .toNumber()
    .isNumber()
    .inRange(13, 120)
    .default(18);

  // Implementation...
}

5. Use Type Conversion

// Convert and validate in one chain
var port = _$(args.port, "port")
  .toNumber()          // Convert first
  .isNumber()          // Then validate
  .inRange(1, 65535)
  .default(8080);

Complete Example

/**
 * REST API handler with full validation
 */
function handleCreateUser(request) {
  // Validate request structure
  _$(request, "request").isMap().$_();
  _$(request.body, "request.body").isMap().$_();

  var body = request.body;

  // Validate username
  _$(body.username, "username")
    .isString()
    .notEquals("")
    .match(/^[a-zA-Z0-9_]{3,20}$/)
    .check(v => !userExists(v), "Username already exists")
    .$_();

  // Validate email
  _$(body.email, "email")
    .isString()
    .notEquals("")
    .match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
    .check(v => !emailExists(v), "Email already registered")
    .$_();

  // Validate password
  _$(body.password, "password")
    .isString()
    .check(v => v.length >= 8, "Password must be at least 8 characters")
    .check(v => /[A-Z]/.test(v), "Password must contain uppercase letter")
    .check(v => /[a-z]/.test(v), "Password must contain lowercase letter")
    .check(v => /[0-9]/.test(v), "Password must contain number")
    .$_();

  // Validate optional fields with defaults
  var age = _$(body.age, "age")
    .toNumber()
    .isNumber()
    .inRange(13, 120)
    .default(18);

  var role = _$(body.role, "role")
    .isString()
    .oneOf(["user", "admin", "moderator"])
    .default("user");

  var preferences = _$(body.preferences, "preferences")
    .isMap()
    .default({
      notifications: true,
      theme: "light",
      language: "en"
    });

  // Create user
  var user = {
    id: genUUID(),
    username: body.username,
    email: body.email.toLowerCase(),
    passwordHash: sha256(body.password),
    age: age,
    role: role,
    preferences: preferences,
    createdAt: nowUTC(),
    active: true
  };

  // Save user
  saveUser(user);

  // Return sanitized response (no password)
  delete user.passwordHash;
  return user;
}

See Also