ADDRESS Handler Utilities

Shared utilities for creating consistent and robust ADDRESS facility handlers in RexxJS.

Overview

The Address Handler Utilities provide a standardized toolkit for ADDRESS facility developers, offering consistent variable interpolation, response formatting, error handling, and logging across all address handlers.

Installation

// Node.js
const { interpolateMessage, createResponse, wrapHandler } = require('./src/address-handler-utils');

// Browser
// Utilities are automatically available as window.AddressHandlerUtils
const { interpolateMessage, createResponse } = window.AddressHandlerUtils;

Core Functions

String Interpolation

interpolateMessage(template, context, options)

Replaces variable patterns with values from the context object. The interpolation pattern can be configured globally using the interpolation configuration system.

const message = "User {name} scored {score} points";
const context = { name: "Alice", score: 95, age: 30 };

const interpolated = await interpolateMessage(message, context);
console.log(interpolated); // "User Alice scored 95 points"

Configurable Interpolation Patterns

RexxJS supports multiple interpolation patterns that can be switched globally using REXX statements:

-- Default RexxJS pattern: {variable}
LET name = "Alice"
SAY "Hello {name}"    -- Outputs: Hello Alice

-- Switch to Handlebars pattern: 
INTERPOLATION HANDLEBARS
LET name = "Bob"
SAY "Hello "  -- Outputs: Hello Bob

-- Switch to Shell pattern: ${variable}
INTERPOLATION SHELL
LET name = "Charlie"
SAY "Hello ${name}"   -- Outputs: Hello Charlie

-- Reset to default
INTERPOLATION DEFAULT

Available Predefined Patterns:

  • DEFAULT or REXX: {variable} (default)
  • HANDLEBARS: ``
  • SHELL: ${variable}
  • BATCH: %variable%
  • CUSTOM: $$variable$$
  • BRACKETS: [variable]

Creating Custom Patterns:

-- Define custom angle bracket pattern
INTERPOLATION PATTERN name=ANGLES start="<<" end=">>"

-- Switch to the custom pattern
INTERPOLATION ANGLES
LET name = "Dave"
SAY "Hello <<name>>"  -- Outputs: Hello Dave

-- Create and use multiple custom patterns
INTERPOLATION PATTERN name=RUBY start="#{" end="}"
INTERPOLATION RUBY
LET count = 42
SAY "Found #{count} results"  -- Outputs: Found 42 results

Pattern Switching in Scripts:

-- Switch pattern for specific operations
INTERPOLATION HANDLEBARS
LET user = "Alice"
LET status = "active"

ADDRESS myservice
"User  is "

-- Reset to default for the rest of the script
INTERPOLATION DEFAULT
SAY "User {user} processed"

Options:

const options = {
  throwOnMissing: false,           // Throw error if variable not found
  missingPlaceholder: '[MISSING]', // Placeholder for missing variables  
  transform: async (varName, value) => {
    // Transform values during interpolation
    if (varName === 'score' && value > 90) return `${value}⭐`;
    return value;
  }
};

const result = await interpolateMessage(
  "User {name} scored {score}", 
  { name: "Bob", score: 95 }, 
  options
);
// Result: "User Bob scored 95⭐"

extractVariables(template)

Extracts all variable names from a template string.

const variables = extractVariables("Hello {name}, you have {count} messages");
console.log(variables); // ["name", "count"]

validateContext(template, context, required)

Validates that required variables are present in context.

const validation = validateContext(
  "User {name} has {score} points",
  { name: "Alice", age: 30 }, // missing 'score'
);

console.log(validation);
// {
//   valid: false,
//   missing: ["score"],
//   found: ["name"]
// }

Response Formatting

createResponse(success, result, message, metadata)

Creates standardized response objects for ADDRESS handlers.

// Success response
const success = createResponse(true, { id: 123 }, "User created successfully");
console.log(success);
// {
//   success: true,
//   result: { id: 123 },
//   message: "User created successfully",
//   timestamp: "2025-01-15T10:30:00.000Z"
// }

// Simple success
const simple = createResponse(true);
// { success: true, timestamp: "2025-01-15T10:30:00.000Z" }

createErrorResponse(error, operation, metadata)

Creates standardized error responses.

// Error from exception
const errorResp = createErrorResponse(
  new Error("Database connection failed"),
  "user_creation"
);

// Error from string
const stringError = createErrorResponse(
  "Invalid email format",
  "validation",
  { field: "email", value: "invalid-email" }
);

Command Parsing

parseCommand(message)

Parses command-style messages with parameters.

const parsed = parseCommand("create user name=John age=25 active=true");
console.log(parsed);
// {
//   command: "create",
//   subcommand: "user", 
//   params: { name: "John", age: "25", active: "true" }
// }

const simple = parseCommand("status");
// { command: "status", subcommand: "", params: {} }

Logging

logActivity(handlerName, operation, details, level)

Consistent logging for ADDRESS handlers.

logActivity("database", "query", { 
  table: "users", 
  rows: 5 
});
// Output: [ADDRESS:DATABASE] { timestamp: "...", handler: "database", operation: "query", table: "users", rows: 5 }

logActivity("api", "request_failed", { error: "timeout" }, "error");
// Error-level log with consistent format

Handler Wrapper

wrapHandler(handlerName, handlerFn, options)

Wraps ADDRESS handlers with common functionality.

const myHandler = async (message, context, sourceContext) => {
  // Your handler logic here
  return { processed: message.toUpperCase() };
};

const wrappedHandler = wrapHandler("myservice", myHandler, {
  autoInterpolate: true,     // Automatically interpolate {variables}
  logCalls: true,           // Log all calls
  validateContext: true,    // Validate required variables
  requiredVars: ["user_id"] // Required variables
});

// Register the wrapped handler
interpreter.addressTargets.set('myservice', {
  handler: wrappedHandler,
  methods: {},
  metadata: { name: 'My Service' }
});

Complete Examples

Basic Address Handler

const { interpolateMessage, createResponse, createErrorResponse } = require('./src/address-handler-utils');

async function simpleLogHandler(message, context, sourceContext) {
  try {
    // Interpolate variables in the log message
    const interpolated = await interpolateMessage(message, context);
    
    // Log with timestamp
    console.log(`[${new Date().toISOString()}] ${interpolated}`);
    
    return createResponse(true, null, "Message logged");
  } catch (error) {
    return createErrorResponse(error, "logging");
  }
}

// Register handler
interpreter.addressTargets.set('logger', {
  handler: simpleLogHandler,
  methods: {},
  metadata: { name: 'Simple Logger' }
});

// Usage in REXX
/*
LET user = "Alice"
LET action = "login"

ADDRESS logger <<LOG_ENTRIES
Starting application initialization
Loading configuration file
Database connection established
LOG_ENTRIES
LOG: User {user} performed {action} at {timestamp}
*/

Command-Style Handler

const { parseCommand, interpolateMessage, createResponse, wrapHandler } = require('./src/address-handler-utils');

async function databaseHandler(message, context, sourceContext) {
  // Parse command structure
  const { command, subcommand, params } = parseCommand(message);
  
  // Interpolate any variables in parameters
  for (const [key, value] of Object.entries(params)) {
    params[key] = await interpolateMessage(value, context);
  }
  
  switch (command) {
    case 'create':
      if (subcommand === 'user') {
        return createResponse(true, { id: 123 }, `User ${params.name} created`);
      }
      break;
      
    case 'query':
      return createResponse(true, 
        [{ name: params.name || 'John', age: 30 }], 
        "Query executed"
      );
      
    default:
      return createErrorResponse(`Unknown command: ${command}`, "command_parsing");
  }
}

// Wrap with automatic features
const wrappedDbHandler = wrapHandler("database", databaseHandler, {
  logCalls: true,
  validateContext: true
});

// Register handler  
interpreter.addressTargets.set('database', {
  handler: wrappedDbHandler,
  methods: {},
  metadata: { name: 'Database Handler' }
});

// Usage in REXX
/*
LET user_name = "Alice"
LET user_age = 25

ADDRESS database
create user name={user_name} age={user_age}
query users name={user_name}
*/

Validation-Heavy Handler

const { 
  validateContext, 
  interpolateMessage, 
  createResponse, 
  createErrorResponse 
} = require('./src/address-handler-utils');

async function apiHandler(message, context, sourceContext) {
  // Validate required variables are present
  const validation = validateContext(message, context, ['api_key', 'endpoint']);
  if (!validation.valid) {
    return createErrorResponse(
      `Missing required variables: ${validation.missing.join(', ')}`,
      "validation"
    );
  }
  
  // Interpolate the full message
  const interpolated = await interpolateMessage(message, context);
  
  // Simulate API call
  const apiResult = {
    status: 200,
    data: { message: interpolated },
    timestamp: new Date().toISOString()
  };
  
  return createResponse(true, apiResult, "API call successful");
}

// Usage with validation
/*
LET api_key = "secret123"
LET endpoint = "/users"
LET user_id = 456

ADDRESS api <<JSON_REQUEST
{
  "method": "GET",
  "endpoint": "/users"
}
JSON_REQUEST  
API: GET {endpoint}/{user_id} with key {api_key}
*/

Multi-Format Response Handler

const { 
  interpolateMessage, 
  createResponse, 
  logActivity 
} = require('./src/address-handler-utils');

async function reportHandler(message, context, sourceContext) {
  const interpolated = await interpolateMessage(message, context);
  
  logActivity("report", "generate", { 
    template: message,
    variables: Object.keys(context).length 
  });
  
  // Generate different formats
  const formats = {
    text: interpolated,
    json: JSON.stringify({ message: interpolated, context }),
    html: `<p>${interpolated}</p>`,
    timestamp: new Date().toISOString()
  };
  
  return createResponse(true, formats, "Report generated in multiple formats");
}

Best Practices

1. Always Use Standard Response Format

// GOOD - Standard format
return createResponse(true, result, "Success message");

// AVOID - Inconsistent format  
return { ok: true, data: result };

2. Validate Input When Possible

// Validate variables are present
const validation = validateContext(message, context);
if (!validation.valid) {
  return createErrorResponse(`Missing: ${validation.missing.join(', ')}`);
}

3. Use Handler Wrapper for Common Features

// GOOD - Wrapped with common functionality
const wrappedHandler = wrapHandler("myservice", myHandler, {
  autoInterpolate: true,
  logCalls: true
});

// MANUAL - Implementing logging and interpolation manually
const manualHandler = async (message, context, sourceContext) => {
  console.log("Handler called"); // Manual logging
  const interpolated = await interpolateMessage(message, context); // Manual interpolation
  return await myHandler(interpolated, context, sourceContext);
};

4. Handle Errors Gracefully

try {
  const result = await someAsyncOperation();
  return createResponse(true, result);
} catch (error) {
  return createErrorResponse(error, "operation_name");
}

5. Log Important Operations

logActivity("myhandler", "important_operation", {
  input: message,
  contextSize: Object.keys(context).length
});

Integration with ADDRESS HEREDOC

The utilities work seamlessly with ADDRESS HEREDOC patterns:

// Handler using utilities
const handler = wrapHandler("test", async (message, context) => {
  // Message comes from HEREDOC content block
  // Context contains all variables
  const processed = await interpolateMessage(message, context);
  return createResponse(true, { processed });
}, { 
  autoInterpolate: false, // Handle interpolation manually for more control
  logCalls: true 
});

// REXX usage
/*
LET name = "Alice"
LET score = 95

ADDRESS test <<TEST_CASES
user authentication should succeed
password validation should enforce rules
input sanitization should prevent XSS
TEST_CASES
TEST: {name} achieved {score}% on the exam
*/

Error Handling

All utilities include comprehensive error handling:

// Interpolation with missing variables
const result = await interpolateMessage("Hello {missing_var}", {});
console.log(result); // "Hello {missing_var}" (preserves original)

// With error throwing enabled
try {
  await interpolateMessage("Hello {missing_var}", {}, { throwOnMissing: true });
} catch (error) {
  console.log(error.message); // "Variable 'missing_var' not found in context"
}

See Also


Complete Example Library: