Skip to main content

Overview

String operators enable powerful text matching and validation capabilities. Rule Engine JS provides four string operators for different text comparison needs:

contains

Check if string contains substring

startsWith

Check if string starts with prefix

endsWith

Check if string ends with suffix

regex

Match against regular expression patterns

Architecture

String operators are implemented across two classes extending BaseOperator: Source Files:
  • String operators: src/operators/string.js
  • Regex operator: src/operators/regex.js
  • Base operator: src/operators/base/BaseOperator.js
  • Type utilities: src/utils/TypeUtils.js
  • Unit tests: tests/unit/operators/string.test.js

Key Features

  • Dynamic field comparison - Compare two fields or field to literal value
  • Case sensitive by default - Exact matching for security
  • Automatic type coercion - Non-string values converted to strings
  • Pattern caching - Regex patterns cached for performance
  • Comprehensive validation - Validates argument count and types

contains - Substring Match

Check if a string contains a substring.

Syntax

{ contains: [haystack, needle] }
{ contains: [haystack, needle, options] }

Parameters

haystack
string
required
The string to search in - field path or literal value
needle
string
required
The substring to search for - field path or literal value
options
object
Configuration options

Returns

boolean - true if haystack contains needle, false otherwise

Examples

  • Basic Contains
  • Dynamic Field Comparison
  • Email Domain Validation
  • With Rule Helpers
import { createRuleEngine } from 'rule-engine-js';

const engine = createRuleEngine();
const data = {
  user: {
    email: 'john@company.com',
    bio: 'Software Engineer at TechCorp',
  }
};

// Check if email contains domain
engine.evaluateExpr({ contains: ['user.email', '@company.com'] }, data);
// Result: { success: true }

// Check bio for keyword
engine.evaluateExpr({ contains: ['user.bio', 'Software'] }, data);
// Result: { success: true }

// Case sensitive - will fail
engine.evaluateExpr({ contains: ['user.bio', 'software'] }, data);
// Result: { success: false }

Common Use Cases

const article = {
  title: 'Introduction to React Hooks',
  content: 'React Hooks provide a way to use state...',
  tags: ['react', 'hooks', 'javascript']
};

// Search in title or content
const searchRule = {
  or: [
    { contains: ['title', 'React'] },
    { contains: ['content', 'React'] }
  ]
};

engine.evaluateExpr(searchRule, article);
// Result: { success: true }
const post = {
  content: 'Check out this amazing product!',
  author: 'spammer@example.com'
};

// Filter spam content
const spamRule = {
  or: [
    { contains: ['content', 'amazing product'] },
    { contains: ['content', 'click here'] },
    { contains: ['author', 'spammer'] }
  ]
};

engine.evaluateExpr(spamRule, post);
// Result: { success: true } - Flagged as spam
const product = {
  name: 'MacBook Pro 16-inch',
  category: 'Electronics > Computers > Laptops'
};

// Check product category
const isLaptop = { contains: ['category', 'Laptops'] };
const isElectronics = { contains: ['category', 'Electronics'] };

engine.evaluateExpr(isLaptop, product);
// Result: { success: true }
Case Sensitivity: contains is case-sensitive by default. “Software” !== “software”

startsWith - Prefix Match

Check if a string starts with a specific prefix.

Syntax

{ startsWith: [string, prefix] }
{ startsWith: [string, prefix, options] }

Parameters

string
string
required
The string to check - field path or literal value
prefix
string
required
The prefix to match - field path or literal value
options
object
Configuration options

Returns

boolean - true if string starts with prefix, false otherwise

Examples

  • Basic StartsWith
  • Dynamic Prefix
  • ID Prefix Validation
const data = {
  user: {
    name: 'John Doe',
    username: 'john_doe_123'
  },
  url: 'https://example.com/page'
};

// Check name prefix
engine.evaluateExpr({ startsWith: ['user.name', 'John'] }, data);
// Result: { success: true }

// Check URL protocol
engine.evaluateExpr({ startsWith: ['url', 'https'] }, data);
// Result: { success: true }

// Check username pattern
engine.evaluateExpr({ startsWith: ['user.username', 'john_'] }, data);
// Result: { success: true }

Common Use Cases

const links = [
  { url: 'https://secure.example.com' },
  { url: 'http://example.com' },
  { url: 'ftp://files.example.com' }
];

// Only allow HTTPS
const httpsOnly = { startsWith: ['url', 'https://'] };

links.forEach(link => {
  const result = engine.evaluateExpr(httpsOnly, link);
  console.log(`${link.url}: ${result.success}`);
});
const file = {
  path: '/app/uploads/images/photo.jpg',
  allowedPrefix: '/app/uploads/'
};

// Ensure file is in allowed directory
const inAllowedDir = {
  startsWith: ['path', 'allowedPrefix']
};

engine.evaluateExpr(inAllowedDir, file);
// Result: { success: true }
const contact = {
  phone: '+1-555-123-4567',
  country: 'US'
};

// Validate US phone number format
const usPhoneRule = { startsWith: ['phone', '+1-'] };

engine.evaluateExpr(usPhoneRule, contact);
// Result: { success: true }

endsWith - Suffix Match

Check if a string ends with a specific suffix.

Syntax

{ endsWith: [string, suffix] }
{ endsWith: [string, suffix, options] }

Parameters

string
string
required
The string to check - field path or literal value
suffix
string
required
The suffix to match - field path or literal value
options
object
Configuration options

Returns

boolean - true if string ends with suffix, false otherwise

Examples

  • Basic EndsWith
  • Dynamic Suffix
  • File Type Validation
const data = {
  user: {
    email: 'john@company.com',
  },
  file: {
    name: 'document.pdf',
    backup: 'document.pdf.bak'
  }
};

// Check email domain
engine.evaluateExpr({ endsWith: ['user.email', '.com'] }, data);
// Result: { success: true }

// Check file extension
engine.evaluateExpr({ endsWith: ['file.name', '.pdf'] }, data);
// Result: { success: true }

// Check backup file
engine.evaluateExpr({ endsWith: ['file.backup', '.bak'] }, data);
// Result: { success: true }

Common Use Cases

const upload = {
  filename: 'profile-photo.jpg',
  allowedTypes: ['.jpg', '.jpeg', '.png', '.gif']
};

// Accept only image files
const imageRule = {
  or: [
    { endsWith: ['filename', '.jpg'] },
    { endsWith: ['filename', '.jpeg'] },
    { endsWith: ['filename', '.png'] },
    { endsWith: ['filename', '.gif'] }
  ]
};

engine.evaluateExpr(imageRule, upload);
// Result: { success: true }
const user = {
  email: 'employee@company.com',
  isInternal: true
};

// Only allow company email
const companyEmailRule = {
  and: [
    { endsWith: ['email', '@company.com'] },
    { eq: ['isInternal', true] }
  ]
};

engine.evaluateExpr(companyEmailRule, user);
// Result: { success: true }
const request = {
  path: '/api/v1/users/profile.json',
  requiresJson: true
};

// API must return JSON
const jsonApiRule = {
  and: [
    { startsWith: ['path', '/api/'] },
    { endsWith: ['path', '.json'] }
  ]
};

engine.evaluateExpr(jsonApiRule, request);
// Result: { success: true }

regex - Regular Expression Match

Match strings against regular expression patterns with support for flags and pattern caching.

Syntax

{ regex: [text, pattern] }
{ regex: [text, pattern, options] }

Parameters

text
string
required
The text to match against - field path or literal value
pattern
string
required
The regular expression pattern - field path or literal value
options
object
Configuration options

Returns

boolean - true if text matches pattern, false otherwise

Examples

  • Email Validation
  • Phone Number Validation
  • Dynamic Patterns
  • Password Strength
const user = {
  email: 'john.doe@company.com'
};

// Validate email format
const emailRule = {
  regex: ['email', '^[\\w\\.-]+@[\\w\\.-]+\\.[a-zA-Z]{2,}$']
};

engine.evaluateExpr(emailRule, user);
// Result: { success: true }

// Test with invalid email
const invalidUser = { email: 'invalid.email' };
engine.evaluateExpr(emailRule, invalidUser);
// Result: { success: false }

Common Use Cases

const link = {
  url: 'https://www.example.com/path?query=value'
};

// Validate URL format
const urlRule = {
  regex: ['url', '^https?:\\/\\/[\\w\\.-]+(:[\\d]+)?(\\/[\\w\\.-]*)*\\??([\\w=&]*)$']
};

engine.evaluateExpr(urlRule, link);
// Result: { success: true }
const payment = {
  cardNumber: '4532-1234-5678-9010'
};

// Validate credit card format (Visa)
const visaRule = {
  regex: ['cardNumber', '^4[0-9]{3}-?[0-9]{4}-?[0-9]{4}-?[0-9]{4}$']
};

engine.evaluateExpr(visaRule, payment);
// Result: { success: true }
const event = {
  date: '2024-12-25',
  time: '14:30:00'
};

// Validate ISO date format
const dateRule = {
  regex: ['date', '^\\d{4}-\\d{2}-\\d{2}$']
};

// Validate time format
const timeRule = {
  regex: ['time', '^([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d$']
};

engine.evaluateExpr({ and: [dateRule, timeRule] }, event);
// Result: { success: true }
const user = {
  username: 'john_doe_123'
};

// Username: 3-20 chars, alphanumeric + underscore
const usernameRule = {
  regex: ['username', '^[a-zA-Z0-9_]{3,20}$']
};

engine.evaluateExpr(usernameRule, user);
// Result: { success: true }
Performance: Regex patterns are cached automatically for better performance when evaluating the same pattern multiple times.

Error Handling

Common Errors

const data = { user: { age: 28 } };

// Trying to use contains on number
const result = engine.evaluateExpr(
  { contains: ['user.age', '28'] },
  data
);

// Returns:
// {
//   success: false,
//   error: "CONTAINS operator requires string operands"
// }
const data = { text: 'hello' };

// Invalid regex syntax
const result = engine.evaluateExpr(
  { regex: ['text', '[invalid('] },
  data
);

// Returns:
// {
//   success: false,
//   error: "Invalid regex pattern: [invalid("
// }
// Missing second argument
const result = engine.evaluateExpr(
  { contains: ['user.email'] },
  data
);

// Returns:
// {
//   success: false,
//   error: "CONTAINS operator requires 2-3 arguments, got 1"
// }
const data = { user: { name: 'John' } };

// Field doesn't exist - undefined coerced to empty string
const result = engine.evaluateExpr(
  { contains: ['user.email', '@'] },
  data
);

// Returns:
// {
//   success: false,
//   error: "CONTAINS operator requires string operands"
// }

Error Recovery

function safeStringMatch(engine, operator, field, pattern, data, fallback = false) {
  const result = engine.evaluateExpr({ [operator]: [field, pattern] }, data);

  if (!result.success) {
    console.error(`String match failed: ${result.error}`);
    return fallback;
  }

  return result.success;
}

// Usage
const hasKeyword = safeStringMatch(
  engine,
  'contains',
  'content',
  'important',
  article,
  false
);

Type Coercion

String operators support automatic type coercion in loose mode:
Input TypeCoercion BehaviorExample
stringNo change"hello""hello"
numberConverted to string123"123"
booleanConverted to stringtrue"true"
nullError thrownnull → Error
undefinedError thrownundefined → Error
objectError thrown{} → Error

Strict Mode

const data = { value: 123 };

// Loose mode (default) - coerces number to string
engine.evaluateExpr({ contains: ['value', '12'] }, data);
// Result: { success: true } - "123" contains "12"

// Strict mode - rejects non-string
const strictEngine = createRuleEngine({ strict: true });
strictEngine.evaluateExpr({ contains: ['value', '12'] }, data);
// Result: { success: false, error: "requires string operands" }

API Reference

For complete API documentation: