or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.cucumber/cucumber-expressions@19.0.x

docs

index.md
tile.json

tessl/maven-io-cucumber--cucumber-expressions

tessl install tessl/maven-io-cucumber--cucumber-expressions@19.0.0

Cucumber Expressions are simple patterns for matching Step Definitions with Gherkin steps

exceptions.mddocs/reference/

Exception Handling

The library provides comprehensive exception types for handling parameter type conflicts, undefined types, duplicate registrations, and expression errors. All exceptions extend from the base CucumberExpressionException.

Exception Hierarchy

RuntimeException (unchecked)
└── CucumberExpressionException
    ├── AmbiguousParameterTypeException
    ├── UndefinedParameterTypeException
    └── DuplicateTypeNameException

Key Characteristics:

  • All exceptions are unchecked (extend RuntimeException)
  • Exceptions are thrown synchronously during operation execution
  • Transformer exceptions are preserved and re-thrown when getValue() is called
  • Exception messages are descriptive and include diagnostic information

Capabilities

CucumberExpressionException

Base exception class for all cucumber expression errors.

package io.cucumber.cucumberexpressions;

/**
 * Base exception for cucumber expression errors
 * Extends RuntimeException for unchecked exception handling
 * All library exceptions inherit from this class
 */
public class CucumberExpressionException extends RuntimeException {
    // Package-private constructors - instantiated by library
}

Usage:

import io.cucumber.cucumberexpressions.*;

try {
    // Operations that may throw CucumberExpressionException
    Expression expr = factory.createExpression("I have {int} cucumbers^");
    // Note: This does NOT throw - anchors are literal text in Cucumber Expressions
} catch (CucumberExpressionException e) {
    System.err.println("Expression error: " + e.getMessage());
    // Handle cucumber expression specific errors
}

// Catch all library exceptions
try {
    ParameterType<Custom> type = new ParameterType<>(
        "custom", "pattern", Custom.class, Custom::new
    );
    registry.defineParameterType(type);
    Expression expr = factory.createExpression("Test {custom}");
} catch (CucumberExpressionException e) {
    // Catches: DuplicateTypeNameException, UndefinedParameterTypeException,
    // AmbiguousParameterTypeException, and any other library exceptions
    System.err.println("Library error: " + e.getMessage());
}

AmbiguousParameterTypeException

Thrown when multiple parameter types match the same text pattern in a regular expression, making it impossible to determine which type to use.

package io.cucumber.cucumberexpressions;

import java.util.List;
import java.util.SortedSet;
import java.util.regex.Pattern;

/**
 * Thrown when multiple parameter types match
 * Provides information about conflicting types and suggestions for resolution
 */
public final class AmbiguousParameterTypeException extends CucumberExpressionException {
    /**
     * Get the regular expression pattern where ambiguity occurred
     * @return Pattern with ambiguous matches
     */
    public Pattern getRegexp();
    
    /**
     * Get the parameter type regexp that caused ambiguity
     * This is the specific pattern fragment that matched multiple types
     * @return String representation of parameter type regexp
     */
    public String getParameterTypeRegexp();
    
    /**
     * Get the conflicting parameter types
     * Sorted set of types that matched the same pattern
     * Types are sorted by name for consistent ordering
     * @return SortedSet of conflicting ParameterTypes
     */
    public SortedSet<ParameterType<?>> getParameterTypes();
    
    /**
     * Get suggested alternative expressions
     * Provides candidate expressions that avoid ambiguity by using
     * Cucumber Expression syntax instead of regex
     * @return List of GeneratedExpression suggestions
     */
    public List<GeneratedExpression> getGeneratedExpressions();
}

Usage Examples:

import io.cucumber.cucumberexpressions.*;
import java.util.Locale;
import java.util.SortedSet;
import java.util.List;

ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);

// Register two parameter types with overlapping patterns
ParameterType<Currency> currencyType1 = new ParameterType<>(
    "currency",
    "USD|EUR|GBP",
    Currency.class,
    (String s) -> Currency.valueOf(s)
);

ParameterType<String> currencyType2 = new ParameterType<>(
    "currencyCode",
    "USD|EUR|GBP",  // Same pattern!
    String.class,
    (String s) -> s
);

registry.defineParameterType(currencyType1);
registry.defineParameterType(currencyType2);

ExpressionFactory factory = new ExpressionFactory(registry);

try {
    // This will throw because "USD" matches both types in regex mode
    Expression expr = factory.createExpression("^Price in (USD|EUR|GBP)$");
} catch (AmbiguousParameterTypeException e) {
    System.err.println("Ambiguous parameter types detected:");
    
    // Get the conflicting types
    SortedSet<ParameterType<?>> types = e.getParameterTypes();
    for (ParameterType<?> type : types) {
        System.err.println("  - " + type.getName() + " (" + type.getType() + ")");
    }
    // Output:
    //   - currency (class Currency)
    //   - currencyCode (class java.lang.String)
    
    // Get the pattern that caused ambiguity
    String problematicPattern = e.getParameterTypeRegexp();
    System.err.println("Ambiguous pattern: " + problematicPattern);
    // Output: "USD|EUR|GBP"
    
    // Get the full regex
    Pattern regex = e.getRegexp();
    System.err.println("Full regex: " + regex.pattern());
    
    // Get suggestions
    List<GeneratedExpression> suggestions = e.getGeneratedExpressions();
    System.err.println("Suggestions:");
    for (GeneratedExpression suggestion : suggestions) {
        System.err.println("  " + suggestion.getSource());
    }
    // Suggestions use Cucumber Expression syntax to avoid ambiguity
}

Avoiding Ambiguity:

// Option 1: Use preferForRegexpMatch to indicate preference
ParameterType<Currency> currencyType = new ParameterType<>(
    "currency",
    "USD|EUR|GBP",
    Currency.class,
    (String s) -> Currency.valueOf(s),
    true,  // useForSnippets
    true   // preferForRegexpMatch = true (marks as preferred)
);

ParameterType<String> currencyCodeType = new ParameterType<>(
    "currencyCode",
    "USD|EUR|GBP",
    String.class,
    (String s) -> s,
    true,
    false  // preferForRegexpMatch = false (not preferred)
);

registry.defineParameterType(currencyType);
registry.defineParameterType(currencyCodeType);

// Now regex will use the preferred type
Expression expr = factory.createExpression("^Price in (USD|EUR|GBP)$");
// Uses currencyType (the preferred one)

// Option 2: Use different patterns
ParameterType<Currency> currencyType2 = new ParameterType<>(
    "currency",
    "USD|EUR|GBP",
    Currency.class,
    (String s) -> Currency.valueOf(s)
);

ParameterType<String> currencyCodeType2 = new ParameterType<>(
    "currencyCode",
    "[A-Z]{3}",  // Different pattern
    String.class,
    (String s) -> s
);

// Option 3: Use Cucumber Expression syntax instead of regex
// Cucumber Expressions don't have ambiguity issues
Expression expr2 = factory.createExpression("Price in {currency}");
// Cucumber Expressions handle type selection by parameter name

UndefinedParameterTypeException

Thrown when an expression references a parameter type that hasn't been registered.

package io.cucumber.cucumberexpressions;

/**
 * Thrown when undefined parameter type referenced
 * Indicates that an expression uses {typeName} but typeName
 * has not been registered in the ParameterTypeRegistry
 */
public final class UndefinedParameterTypeException extends CucumberExpressionException {
    /**
     * Get the name of the undefined parameter type
     * @return Parameter type name that was not found (without braces)
     */
    public String getUndefinedParameterTypeName();
}

Usage Examples:

import io.cucumber.cucumberexpressions.*;
import java.util.Locale;

ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
ExpressionFactory factory = new ExpressionFactory(registry);

try {
    // Reference undefined parameter type
    Expression expr = factory.createExpression("I have a {color} ball");
    // Throws: UndefinedParameterTypeException
} catch (UndefinedParameterTypeException e) {
    String typeName = e.getUndefinedParameterTypeName();
    System.err.println("Parameter type not found: " + typeName);
    // Output: "Parameter type not found: color"
    
    // Fix: Define the missing parameter type
    ParameterType<Color> colorType = new ParameterType<>(
        typeName,
        "red|blue|green",
        Color.class,
        (String s) -> new Color(s)
    );
    registry.defineParameterType(colorType);
    
    // Now it works
    Expression expr = factory.createExpression("I have a {color} ball");
}

Prevention:

// Always define parameter types before using them
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);

// Define all custom types first
ParameterType<Color> colorType = new ParameterType<>(
    "color", "red|blue|green", Color.class, (String s) -> new Color(s)
);
ParameterType<Size> sizeType = new ParameterType<>(
    "size", "small|medium|large", Size.class, (String s) -> new Size(s)
);

registry.defineParameterType(colorType);
registry.defineParameterType(sizeType);

// Then create expressions
ExpressionFactory factory = new ExpressionFactory(registry);
Expression expr1 = factory.createExpression("I have a {color} ball");
Expression expr2 = factory.createExpression("The ball is {size}");
// Both work without errors

// Validation helper
public boolean isTypeDefined(ParameterTypeRegistry registry, String typeName) {
    try {
        ExpressionFactory factory = new ExpressionFactory(registry);
        factory.createExpression("Test {" + typeName + "}");
        return true;
    } catch (UndefinedParameterTypeException e) {
        return false;
    }
}

DuplicateTypeNameException

Thrown when attempting to register a parameter type with a name that's already registered.

package io.cucumber.cucumberexpressions;

/**
 * Thrown when duplicate parameter type name registered
 * Indicates that defineParameterType was called with a name
 * that is already in the registry
 */
public class DuplicateTypeNameException extends CucumberExpressionException {
    // Package-private constructor - instantiated by library
}

Usage Examples:

import io.cucumber.cucumberexpressions.*;
import java.util.Locale;

ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);

// Register first parameter type
ParameterType<Color> colorType1 = new ParameterType<>(
    "color",
    "red|blue|green",
    Color.class,
    (String s) -> new Color(s)
);
registry.defineParameterType(colorType1);

try {
    // Try to register another type with same name
    ParameterType<String> colorType2 = new ParameterType<>(
        "color",  // Duplicate name!
        ".*",
        String.class,
        (String s) -> s
    );
    registry.defineParameterType(colorType2);
    // Throws: DuplicateTypeNameException
} catch (DuplicateTypeNameException e) {
    System.err.println("Duplicate parameter type name: " + e.getMessage());
}

Prevention:

// Use unique names for each parameter type
ParameterType<Color> colorType = new ParameterType<>(
    "color",
    "red|blue|green",
    Color.class,
    (String s) -> new Color(s)
);

ParameterType<String> colorNameType = new ParameterType<>(
    "colorName",  // Different name
    "red|blue|green",
    String.class,
    (String s) -> s
);

registry.defineParameterType(colorType);
registry.defineParameterType(colorNameType);

// Or check before registering
public void registerIfNotExists(ParameterTypeRegistry registry, 
                                 ParameterType<?> parameterType) {
    try {
        registry.defineParameterType(parameterType);
    } catch (DuplicateTypeNameException e) {
        System.out.println("Type '" + parameterType.getName() + 
            "' already registered, skipping");
    }
}

// Or maintain a set of registered types
Set<String> registeredTypes = new HashSet<>();
if (!registeredTypes.contains(typeName)) {
    registry.defineParameterType(parameterType);
    registeredTypes.add(typeName);
}

Expression Syntax Errors

Invalid Cucumber Expression syntax throws CucumberExpressionException:

import io.cucumber.cucumberexpressions.*;

// Note: Anchors in Cucumber Expressions are treated as literal text, not regex anchors
Expression expr1 = factory.createExpression("I have {int} cucumbers^");
// This DOES NOT throw - anchors are accepted as literal characters
// Matches: "I have 42 cucumbers^" (with literal ^ at end)

// To use regex anchors, the expression must start with ^ or end with $
// to trigger regex mode
Expression regexExpr = factory.createExpression("^I have {int} cucumbers$");
// This creates a RegularExpression with proper anchors

// Invalid parameter type name
try {
    ParameterType<String> invalid = new ParameterType<>(
        "{color}",  // Name contains invalid characters
        ".*",
        String.class,
        (String s) -> s
    );
    // This may not throw immediately during construction,
    // but will cause issues when registered
} catch (CucumberExpressionException e) {
    System.err.println("Invalid parameter type name: " + e.getMessage());
}

// Invalid characters in parameter type names:
// {, }, (, ), /, \, whitespace

Regular Expression Syntax Errors

Invalid regular expression syntax throws PatternSyntaxException:

import io.cucumber.cucumberexpressions.*;
import java.util.regex.PatternSyntaxException;

try {
    // Invalid regex syntax
    Expression expr = factory.createExpression("^I have (\\d+ cucumbers$");
    // Missing closing parenthesis - throws PatternSyntaxException
} catch (PatternSyntaxException e) {
    System.err.println("Regex syntax error: " + e.getMessage());
    System.err.println("At index: " + e.getIndex());
    System.err.println("Description: " + e.getDescription());
    System.err.println("Pattern: " + e.getPattern());
}

// Other regex syntax errors:
try {
    factory.createExpression("^I have [\\d+ cucumbers$");
    // Unclosed character class
} catch (PatternSyntaxException e) {
    // Handle error
}

try {
    factory.createExpression("^I have (?P<count>\\d+) cucumbers$");
    // Named groups not supported in Java regex (use (?<count>) instead)
} catch (PatternSyntaxException e) {
    // Handle error
}

Transformer Exception Handling

Exceptions thrown from transformers are preserved and re-thrown during getValue():

import io.cucumber.cucumberexpressions.*;

// Transformer with validation
Transformer<Age> ageTransformer = (String arg) -> {
    if (arg == null) {
        throw new IllegalArgumentException("Age cannot be null");
    }
    int value = Integer.parseInt(arg);
    if (value < 0 || value > 150) {
        throw new IllegalArgumentException("Age must be between 0 and 150");
    }
    return new Age(value);
};

ParameterType<Age> ageType = new ParameterType<>(
    "age", "\\d+", Age.class, ageTransformer
);
registry.defineParameterType(ageType);

Expression expr = factory.createExpression("Person is {age} years old");

// Valid age
Optional<List<Argument<?>>> match1 = expr.match("Person is 25 years old");
if (match1.isPresent()) {
    Age age = (Age) match1.get().get(0).getValue();
    // Success
}

// Invalid age - transformer throws
Optional<List<Argument<?>>> match2 = expr.match("Person is 200 years old");
if (match2.isPresent()) {
    try {
        Age age = (Age) match2.get().get(0).getValue();
        // getValue() throws the IllegalArgumentException from transformer
    } catch (IllegalArgumentException e) {
        System.err.println("Validation error: " + e.getMessage());
        // Output: "Validation error: Age must be between 0 and 150"
    }
}

Error Handling Best Practices

Comprehensive Error Handling:

import io.cucumber.cucumberexpressions.*;
import java.util.regex.PatternSyntaxException;

public Expression createExpressionSafely(String expressionString) {
    try {
        return factory.createExpression(expressionString);
    } catch (UndefinedParameterTypeException e) {
        System.err.println("Missing parameter type: " + e.getUndefinedParameterTypeName());
        System.err.println("Please define the parameter type before using it");
        throw e;
    } catch (AmbiguousParameterTypeException e) {
        System.err.println("Ambiguous parameter types: " + e.getParameterTypes());
        System.err.println("Ambiguous pattern: " + e.getParameterTypeRegexp());
        System.err.println("Suggestions:");
        for (GeneratedExpression suggestion : e.getGeneratedExpressions()) {
            System.err.println("  " + suggestion.getSource());
        }
        throw e;
    } catch (PatternSyntaxException e) {
        System.err.println("Invalid regex syntax: " + e.getMessage());
        System.err.println("At position: " + e.getIndex());
        throw e;
    } catch (CucumberExpressionException e) {
        System.err.println("Expression error: " + e.getMessage());
        throw e;
    }
}

Defensive Parameter Type Registration:

public void registerParameterTypeSafely(
    ParameterTypeRegistry registry,
    ParameterType<?> parameterType
) {
    try {
        registry.defineParameterType(parameterType);
    } catch (DuplicateTypeNameException e) {
        System.out.println("Type '" + parameterType.getName() +
            "' already registered, skipping");
    } catch (CucumberExpressionException e) {
        System.err.println("Failed to register parameter type: " + e.getMessage());
        throw e;
    }
}

Validation Before Expression Creation:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public List<String> validateExpression(String expressionString) {
    List<String> errors = new ArrayList<>();
    
    // Check for common issues
    if (expressionString.contains("^") || expressionString.contains("$")) {
        if (!expressionString.startsWith("^") && !expressionString.endsWith("$")) {
            // Anchors in middle might be unintentional
            errors.add("Anchors (^ or $) found in middle of expression. " +
                      "Did you mean to use regex mode? " +
                      "Start with ^ or end with $ to enable regex mode.");
        }
    }
    
    // Check for undefined parameter types
    Pattern paramPattern = Pattern.compile("\\{([^}]+)\\}");
    Matcher matcher = paramPattern.matcher(expressionString);
    while (matcher.find()) {
        String typeName = matcher.group(1);
        if (!typeName.isEmpty() && !isBuiltInType(typeName)) {
            errors.add("Parameter type '" + typeName + "' may not be defined");
        }
    }
    
    // Check for unmatched braces
    int openBraces = 0;
    for (char c : expressionString.toCharArray()) {
        if (c == '{') openBraces++;
        if (c == '}') openBraces--;
        if (openBraces < 0) {
            errors.add("Unmatched closing brace }");
            break;
        }
    }
    if (openBraces > 0) {
        errors.add("Unmatched opening brace {");
    }
    
    return errors;
}

private boolean isBuiltInType(String typeName) {
    return Arrays.asList(
        "int", "byte", "short", "long", "float", "double",
        "biginteger", "bigdecimal", "word", "string"
    ).contains(typeName);
}

Error Recovery:

public class ExpressionManager {
    private final ExpressionFactory factory;
    private final Map<String, Expression> cache = new ConcurrentHashMap<>();
    private final Map<String, String> errorCache = new ConcurrentHashMap<>();
    
    public ExpressionManager(ExpressionFactory factory) {
        this.factory = factory;
    }
    
    public Result<Expression> getExpression(String expressionString) {
        // Check error cache
        String cachedError = errorCache.get(expressionString);
        if (cachedError != null) {
            return Result.error(cachedError);
        }
        
        // Check success cache
        Expression cached = cache.get(expressionString);
        if (cached != null) {
            return Result.success(cached);
        }
        
        // Try to create expression
        try {
            Expression expr = factory.createExpression(expressionString);
            cache.put(expressionString, expr);
            return Result.success(expr);
        } catch (UndefinedParameterTypeException e) {
            String error = "Undefined type: " + e.getUndefinedParameterTypeName();
            errorCache.put(expressionString, error);
            return Result.error(error);
        } catch (AmbiguousParameterTypeException e) {
            String error = "Ambiguous types: " + e.getParameterTypes();
            errorCache.put(expressionString, error);
            return Result.error(error);
        } catch (Exception e) {
            String error = "Invalid expression: " + e.getMessage();
            errorCache.put(expressionString, error);
            return Result.error(error);
        }
    }
    
    public static class Result<T> {
        private final T value;
        private final String error;
        
        private Result(T value, String error) {
            this.value = value;
            this.error = error;
        }
        
        public static <T> Result<T> success(T value) {
            return new Result<>(value, null);
        }
        
        public static <T> Result<T> error(String error) {
            return new Result<>(null, error);
        }
        
        public boolean isSuccess() { return value != null; }
        public T getValue() { return value; }
        public String getError() { return error; }
    }
}

Advanced Error Handling Patterns

Pattern 1: Exception Translator

public class ExpressionExceptionTranslator {
    public String translateToUserMessage(Exception e) {
        if (e instanceof UndefinedParameterTypeException) {
            UndefinedParameterTypeException upe = (UndefinedParameterTypeException) e;
            return String.format(
                "The expression uses '{%s}' but this type is not defined. " +
                "Please register a parameter type named '%s' before using it.",
                upe.getUndefinedParameterTypeName(),
                upe.getUndefinedParameterTypeName()
            );
        } else if (e instanceof AmbiguousParameterTypeException) {
            AmbiguousParameterTypeException ape = (AmbiguousParameterTypeException) e;
            StringBuilder sb = new StringBuilder();
            sb.append("Multiple types match the same pattern:\n");
            for (ParameterType<?> type : ape.getParameterTypes()) {
                sb.append("  - ").append(type.getName()).append("\n");
            }
            sb.append("Use preferForRegexpMatch or different patterns to resolve.");
            return sb.toString();
        } else if (e instanceof DuplicateTypeNameException) {
            return "A parameter type with this name is already registered. " +
                   "Use a different name or check if the type was already added.";
        } else if (e instanceof PatternSyntaxException) {
            PatternSyntaxException pse = (PatternSyntaxException) e;
            return String.format(
                "Invalid regular expression: %s at position %d",
                pse.getDescription(),
                pse.getIndex()
            );
        } else if (e instanceof CucumberExpressionException) {
            return "Expression error: " + e.getMessage();
        } else {
            return "Unexpected error: " + e.getMessage();
        }
    }
}

Pattern 2: Retry with Suggestions

public Expression createWithFallback(String expressionString) {
    try {
        return factory.createExpression(expressionString);
    } catch (UndefinedParameterTypeException e) {
        // Try to auto-register common types
        String typeName = e.getUndefinedParameterTypeName();
        if (autoRegisterType(typeName)) {
            // Retry after registration
            return factory.createExpression(expressionString);
        }
        throw e;
    } catch (AmbiguousParameterTypeException e) {
        // Convert to Cucumber Expression syntax
        List<GeneratedExpression> suggestions = e.getGeneratedExpressions();
        if (!suggestions.isEmpty()) {
            String suggested = suggestions.get(0).getSource();
            System.err.println("Using suggested expression: " + suggested);
            return factory.createExpression(suggested);
        }
        throw e;
    }
}

private boolean autoRegisterType(String typeName) {
    // Auto-register common custom types
    switch (typeName) {
        case "email":
            registry.defineParameterType(new ParameterType<>(
                "email", "[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}", 
                String.class, s -> s
            ));
            return true;
        case "uuid":
            registry.defineParameterType(new ParameterType<>(
                "uuid", "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
                String.class, s -> s
            ));
            return true;
        default:
            return false;
    }
}

Pattern 3: Batch Validation

public class BatchExpressionValidator {
    private final ExpressionFactory factory;
    
    public BatchExpressionValidator(ExpressionFactory factory) {
        this.factory = factory;
    }
    
    public Map<String, Either<Expression, Exception>> validateExpressions(
        List<String> expressionStrings
    ) {
        Map<String, Either<Expression, Exception>> results = new LinkedHashMap<>();
        
        for (String exprStr : expressionStrings) {
            try {
                Expression expr = factory.createExpression(exprStr);
                results.put(exprStr, Either.left(expr));
            } catch (Exception e) {
                results.put(exprStr, Either.right(e));
            }
        }
        
        return results;
    }
    
    public ValidationReport generateReport(
        Map<String, Either<Expression, Exception>> results
    ) {
        ValidationReport report = new ValidationReport();
        
        for (Map.Entry<String, Either<Expression, Exception>> entry : results.entrySet()) {
            String exprStr = entry.getKey();
            Either<Expression, Exception> result = entry.getValue();
            
            if (result.isLeft()) {
                report.addSuccess(exprStr);
            } else {
                Exception e = result.getRight();
                report.addFailure(exprStr, e);
            }
        }
        
        return report;
    }
}

Testing and Debugging

Testing Exception Scenarios:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class ExceptionTest {
    @Test
    public void testUndefinedParameterType() {
        ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
        ExpressionFactory factory = new ExpressionFactory(registry);
        
        UndefinedParameterTypeException exception = assertThrows(
            UndefinedParameterTypeException.class,
            () -> factory.createExpression("I have {color} ball")
        );
        
        assertEquals("color", exception.getUndefinedParameterTypeName());
    }
    
    @Test
    public void testAmbiguousParameterType() {
        ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
        registry.defineParameterType(new ParameterType<>(
            "type1", "\\d+", Integer.class, Integer::parseInt
        ));
        registry.defineParameterType(new ParameterType<>(
            "type2", "\\d+", Long.class, Long::parseLong
        ));
        
        ExpressionFactory factory = new ExpressionFactory(registry);
        
        AmbiguousParameterTypeException exception = assertThrows(
            AmbiguousParameterTypeException.class,
            () -> factory.createExpression("^Count: (\\d+)$")
        );
        
        assertEquals(2, exception.getParameterTypes().size());
        assertEquals("\\d+", exception.getParameterTypeRegexp());
    }
    
    @Test
    public void testDuplicateTypeName() {
        ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
        ParameterType<String> type1 = new ParameterType<>(
            "color", "red|blue", String.class, s -> s
        );
        registry.defineParameterType(type1);
        
        ParameterType<String> type2 = new ParameterType<>(
            "color", "green|yellow", String.class, s -> s
        );
        
        assertThrows(
            DuplicateTypeNameException.class,
            () -> registry.defineParameterType(type2)
        );
    }
    
    @Test
    public void testTransformerException() {
        ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
        
        Transformer<Integer> strictPositive = (String s) -> {
            int value = Integer.parseInt(s);
            if (value <= 0) {
                throw new IllegalArgumentException("Must be positive");
            }
            return value;
        };
        
        registry.defineParameterType(new ParameterType<>(
            "positive", "\\d+", Integer.class, strictPositive
        ));
        
        ExpressionFactory factory = new ExpressionFactory(registry);
        Expression expr = factory.createExpression("Value is {positive}");
        
        Optional<List<Argument<?>>> match = expr.match("Value is 0");
        assertTrue(match.isPresent());
        
        // getValue() throws transformer exception
        assertThrows(
            IllegalArgumentException.class,
            () -> match.get().get(0).getValue()
        );
    }
}

Exception Chaining and Context

Adding Context to Exceptions:

public class ContextualExpressionFactory {
    private final ExpressionFactory delegate;
    private final String context;
    
    public ContextualExpressionFactory(ExpressionFactory delegate, String context) {
        this.delegate = delegate;
        this.context = context;
    }
    
    public Expression createExpression(String expressionString) {
        try {
            return delegate.createExpression(expressionString);
        } catch (UndefinedParameterTypeException e) {
            throw new CucumberExpressionException(
                "In context '" + context + "': " + e.getMessage(), e
            );
        } catch (AmbiguousParameterTypeException e) {
            throw new CucumberExpressionException(
                "In context '" + context + "': " + e.getMessage(), e
            );
        } catch (CucumberExpressionException e) {
            throw new CucumberExpressionException(
                "In context '" + context + "': " + e.getMessage(), e
            );
        }
    }
}

// Usage
ContextualExpressionFactory factory = new ContextualExpressionFactory(
    new ExpressionFactory(registry),
    "User authentication step definitions"
);

try {
    factory.createExpression("User {user} logs in");
} catch (CucumberExpressionException e) {
    System.err.println(e.getMessage());
    // Output: "In context 'User authentication step definitions': 
    //          Undefined parameter type 'user'"
}

Related Documentation

  • Expressions - Create and use expressions
  • Parameter Types - Register parameter types
  • Transformers - Handle transformation errors
  • Matching - Access matched arguments