Creating New Rules
Creating HTTP validation rules in Thymian is straightforward. You can generate a rule scaffold using the CLI or write one from scratch. This guide walks you through both approaches.
Quick Start with CLI
Section titled “Quick Start with CLI”The fastest way to create a new rule is using the interactive CLI generator:
thymian generate ruleThis command guides you through the rule creation process:
- Rule name — Unique identifier for your rule
- Severity —
error,warn, orhint - URL — Link to documentation (optional)
- Description — What the rule validates
- Rule types — One or more of:
static,analytics,test,informational - Applies to — Target participants:
client,server,proxy, etc.
The CLI generates a rule template that you can copy into your project:
import { httpRule } from '@thymian/core';
export default httpRule('your-rule-name').severity('error').type('static', 'analytics').description('Your rule description').appliesTo('server').done();Writing a Rule from Scratch
Section titled “Writing a Rule from Scratch”Step 1: Set Up Imports
Section titled “Step 1: Set Up Imports”Start by importing the necessary components:
import { httpRule, statusCode, not, responseHeader } from '@thymian/core';The @thymian/core package provides both the rule builder and filter expressions for matching HTTP transactions.
Step 2: Define Rule Metadata
Section titled “Step 2: Define Rule Metadata”Create the rule with its basic metadata:
export default httpRule('ensure-location-on-201').severity('error').type('static', 'analytics', 'test').description('201 Created responses must include a Location header').appliesTo('server');Best practices:
- Use descriptive names in kebab-case
- Choose appropriate severity based on requirement criticality
- Include clear descriptions that explain what is validated
Step 3: Add Validation Logic
Section titled “Step 3: Add Validation Logic”Add the validation logic using the common interface:
.rule((ctx) => ctx.validateCommonHttpTransactions( statusCode(201), not(responseHeader('location')) ) )This rule:
- Finds all transactions with status 201
- Reports violations when the
Locationheader is missing
Step 4: Complete the Rule
Section titled “Step 4: Complete the Rule”Always end with .done():
.done();Complete Example
Section titled “Complete Example”Here’s a complete rule that enforces API versioning through custom headers:
import { httpRule } from '@thymian/core';import { not, requestHeader } from '@thymian/core';
export default httpRule('require-api-version-header') .severity('error') .type('static', 'analytics', 'test') .url('https://api-guidelines.mycompany.com/versioning') .description('All API requests must include X-API-Version header') .appliesTo('client') .rule((ctx) => ctx.validateCommonHttpTransactions(not(requestHeader('x-api-version')))) .done();Validation Patterns
Section titled “Validation Patterns”There are three main patterns for writing validation logic:
Pattern 1: Transaction + Violation Filters
Section titled “Pattern 1: Transaction + Violation Filters”Use two filters—one to select transactions, another to find violations:
.rule((ctx) => ctx.validateCommonHttpTransactions( method('DELETE'), // Select DELETE requests not(statusCodeRange(200, 204)) // Flag if status not 200-204 ))Pattern 2: Single Violation Filter
Section titled “Pattern 2: Single Violation Filter”Use one filter when matching it is itself the violation:
.rule((ctx) => ctx.validateCommonHttpTransactions( and(method('GET'), statusCode(200), hasRequestBody()), // GET should not have request body ))Pattern 3: Custom Validation Function
Section titled “Pattern 3: Custom Validation Function”Use a function for complex logic that requires examining the transaction details:
import { getHeader } from '@thymian/core';
.rule((ctx) => ctx.validateHttpTransactions( responseHeader('www-authenticate'), (request, response) => { const authHeader = getHeader(response.headers, 'www-authenticate');
// Custom validation logic return !isValidAuthHeader(authHeader); } ))Common Filter Expressions
Section titled “Common Filter Expressions”Filter expressions from @thymian/core let you declaratively match HTTP transactions:
Request Filters
Section titled “Request Filters”method('GET'); // Match HTTP methodrequestHeader('content-type'); // Match header presencehasRequestBody(); // Has request bodyrequestMediaType('application/json'); // Match content typeauthorization(); // Has authorizationResponse Filters
Section titled “Response Filters”statusCode(404); // Match status codestatusCodeRange(400, 499); // Match status rangeresponseHeader('location'); // Match header presencehasResponseBody(); // Has response bodyresponseMediaType('application/json'); // Match content typeLogical Operators
Section titled “Logical Operators”and(method('POST'), statusCode(201)); // Both must matchor(statusCode(301), statusCode(302)); // Either can matchnot(responseHeader('location')); // Must NOT matchxor(...);Real-World Examples
Section titled “Real-World Examples”Example 1: Enforce Consistent Error Format
Section titled “Example 1: Enforce Consistent Error Format”Ensure all error responses use Problem Details format:
import { httpRule } from '@thymian/core';import { statusCodeRange, not, responseMediaType } from '@thymian/core';
export default httpRule('errors-use-problem-details') .severity('warn') .type('static', 'analytics') .description('Error responses should use application/problem+json format') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions(statusCodeRange(400, 599), not(responseMediaType('application/problem+json')))) .done();Example 2: Enforce Correlation ID Tracking
Section titled “Example 2: Enforce Correlation ID Tracking”Ensure distributed tracing by requiring correlation IDs:
import { httpRule } from '@thymian/core';import { not, requestHeader } from '@thymian/core';
export default httpRule('require-correlation-id') .severity('warn') .type('static', 'analytics') .description('Requests should include X-Correlation-ID for distributed tracing') .appliesTo('client') .rule((ctx) => ctx.validateCommonHttpTransactions(not(requestHeader('x-correlation-id')))) .done();Example 3: Require Deprecation Headers
Section titled “Example 3: Require Deprecation Headers”Ensure deprecated endpoints include proper sunset notices:
import { httpRule } from '@thymian/core';import { and, path, not, responseHeader } from '@thymian/core';
export default httpRule('deprecated-endpoints-require-sunset') .severity('error') .type('static', 'analytics') .description('Deprecated API endpoints must include Sunset header') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions(and(path('/api/v1/*')), not(responseHeader('sunset')))) .done();Advanced: Custom Validation Functions
Section titled “Advanced: Custom Validation Functions”When filters aren’t sufficient, use custom validation functions:
import { httpRule } from '@thymian/core';import { responseHeader, getHeader } from '@thymian/core';
export default httpRule('validate-cache-control-directives') .severity('warn') .type('test', 'analytics') .description('Cache-Control header must include valid directives') .appliesTo('server') .rule((ctx) => ctx.validateHttpTransactions(responseHeader('cache-control'), (request, response) => { const cacheControl = getHeader(response.headers, 'cache-control');
// Custom parsing and validation const directives = parseCacheControl(cacheControl);
// Return true if violation detected return !hasValidDirectives(directives); }), ) .done();
function parseCacheControl(header: string) { // Your parsing logic}
function hasValidDirectives(directives: any) { // Your validation logic}Common Pitfalls
Section titled “Common Pitfalls”1. Forgetting .done()
Section titled “1. Forgetting .done()”Always end your rule definition with .done():
// ❌ Missing .done()export default httpRule('my-rule') .severity('error') .type('static') .rule((ctx) => { ... });
// ✅ Correctexport default httpRule('my-rule') .severity('error') .type('static') .rule((ctx) => { ... }) .done(); // Don't forget!2. Using Wrong Filter Combination
Section titled “2. Using Wrong Filter Combination”Ensure your filter logic matches your intent:
// ❌ This will never match (GET is not POST)and(method('GET'), method('POST'));
// ✅ Use OR for alternativesor(method('GET'), method('POST'));Next Steps
Section titled “Next Steps”Now that you know how to create rules:
- Explore rule types in depth to understand context-specific features
- Learn about combining rule types for hybrid rules
- See how to use rules in your projects