Validating Aspect Models

Aspect Models are validated using the AspectModelValidator. Validation returns a list of Violation`s. A violation has a human-readable message and a unique error code and provides access to the `EvaluationContext which contains references to the model element that caused the violation and the SHACL shape that triggered the violation.

Each possible type of violation is a subtype of the Violation interface and provides additional context information specific to this type, for example, the MinLengthViolation provides int min (the allowed length) and int actual (the length that was encountered in the model).

Consider the following example:

Show used imports
import java.io.File;
import java.util.List;

import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
import org.eclipse.esmf.aspectmodel.shacl.fix.Fix;
import org.eclipse.esmf.aspectmodel.shacl.violation.EvaluationContext;
import org.eclipse.esmf.aspectmodel.shacl.violation.Violation;
import org.eclipse.esmf.aspectmodel.validation.services.AspectModelValidator;
import org.eclipse.esmf.aspectmodel.validation.services.DetailedViolationFormatter;
import org.eclipse.esmf.aspectmodel.validation.services.ViolationFormatter;
import org.eclipse.esmf.metamodel.AspectModel;
// AspectModel as returned by the AspectModelLoader
final AspectModel aspectModel = // ...

final List<Violation> violations = new AspectModelValidator().validateModel( aspectModel );
if ( violations.isEmpty() ) {
   // Aspect Model is valid!
   return;
} else {
   for (Violation violation : violations) {
      String errorCode = violation.errorCode();
      String message = violation.message();
      EvaluationContext context = violation.context();
      List<Fix> fixes = violation.fixes();
   }
}

final String validationReport = new ViolationFormatter().apply( violations ); (1)
final String detailedReport = new DetailedViolationFormatter().apply( violations );

class MyViolationVisitor implements Violation.Visitor<String> { (2)
   // ...
}

// Turn the list of Violations into a list of custom descriptions
final Violation.Visitor<String> visitor = new MyViolationVisitor();
final List<String> result = violations.stream()
      .map( violation -> violation.accept( visitor ) )
      .toList();
1 To format the validation result into an human-readable form, use the ViolationFormatter or the DetailedViolationFormatter. Note that those are only intended for text-based interfaces such as CLIs.
2 Every application dealing with validation results that needs to transform the results into some different structure can implement the Violation.Visitor Interface with a suitable target type (String used as an example here) and use the visitor to handle the different types of Violations in a type-safe way.

The AspectModelValidator also provides the loadModel(Supplier<AspectModel>) method that can be used in conjunction with () → new AspectModeLoader().load(…​) to have exceptions that might be created during the Aspect Model loading process, for example due to model resolution failures or syntax errors in the input files, be turned into a List<Violation> that can be passed to the aforementioned violation formatters. This allows for custom structured handling of problems that occur during model loading.

To include the model validator, use the following dependencies:

Maven
<dependency>
    <groupId>org.eclipse.esmf</groupId>
    <artifactId>esmf-aspect-model-validator</artifactId>
    <version>2.12.0</version>
</dependency>
Gradle Groovy DSL
implementation 'org.eclipse.esmf:esmf-aspect-model-validator:2.12.0'
Gradle Kotlin DSL
implementation("org.eclipse.esmf:esmf-aspect-model-validator:2.12.0")

Custom Error Formatting

In order to access validation violations in a type-safe way, implement the interface org.eclipse.esmf.aspectmodel.shacl.violation.Violation.Visitor. This allows you to handle each violation type specifically and format error messages according to your application’s requirements.

Show used imports
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.esmf.aspectmodel.shacl.violation.DatatypeViolation;
import org.eclipse.esmf.aspectmodel.shacl.violation.MaxCountViolation;
import org.eclipse.esmf.aspectmodel.shacl.violation.Violation;
// Implement other visitor methods for different violation types
@Override
public String visit(Violation violation) {
   // Generic fallback for unhandled violation types
   return String.format("Validation error: %s", violation.message());
}

Programmatic Access

When using the Java API, errors are available through the Violation interface:

Show used imports
import java.io.File;
import java.util.List;

import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
import org.eclipse.esmf.aspectmodel.shacl.fix.Fix;
import org.eclipse.esmf.aspectmodel.shacl.violation.EvaluationContext;
import org.eclipse.esmf.aspectmodel.shacl.violation.Violation;
import org.eclipse.esmf.aspectmodel.validation.services.AspectModelValidator;
import org.eclipse.esmf.aspectmodel.validation.services.DetailedViolationFormatter;
import org.eclipse.esmf.aspectmodel.validation.services.ViolationFormatter;
import org.eclipse.esmf.metamodel.AspectModel;
final List<Violation> violations = new AspectModelValidator().validateModel( aspectModel );
if ( violations.isEmpty() ) {
   // Aspect Model is valid!
   return;
} else {
   for (Violation violation : violations) {
      String errorCode = violation.errorCode();
      String message = violation.message();
      EvaluationContext context = violation.context();
      List<Fix> fixes = violation.fixes();
   }
}

Error Aggregation

For applications that need to process multiple violations efficiently, you can group errors by type for batch processing:

public void aggregateErrors(List<Violation> violations) {
    // Group violations by error code for batch processing
    Map<String, List<Violation>> errorsByCode = violations.stream()
        .collect(Collectors.groupingBy(Violation::errorCode));

    // Handle each error type separately
    List<Violation> dataTypeErrors = errorsByCode.get("ERR_TYPE");
    List<Violation> cardinalityErrors = errorsByCode.get("ERR_MAX_COUNT");
    
    // Process each error type with specific handling logic
    if (dataTypeErrors != null && !dataTypeErrors.isEmpty()) {
        System.out.println("Found " + dataTypeErrors.size() + " data type errors");
        // Handle data type errors...
    }
    
    if (cardinalityErrors != null && !cardinalityErrors.isEmpty()) {
        System.out.println("Found " + cardinalityErrors.size() + " cardinality errors");
        // Handle cardinality errors...
    }
}