Guide ‐ Writing a Validator Plugin¶
Polychro's SPI allows you to add custom validators without modifying the core library. This guide walks through creating, packaging, and registering a custom validator.
Step 1 — Add the API Dependency¶
<dependency>
<groupId>io.polychro</groupId>
<artifactId>polychro-api</artifactId>
<version>0.1.0</version>
</dependency>
Step 2 — Implement ValidatorFactory¶
package com.example.polychro;
import io.polychro.api.Validator;
import io.polychro.api.ValidatorConfig;
import io.polychro.api.ValidatorFactory;
public class MyValidatorFactory implements ValidatorFactory {
@Override
public String name() {
return "my-validator";
}
@Override
public Validator create(ValidatorConfig config) {
return new MyValidator(config);
}
}
Step 3 — Implement Validator¶
package com.example.polychro;
import io.polychro.api.Diagnostic;
import io.polychro.api.Document;
import io.polychro.api.Range;
import io.polychro.api.Severity;
import io.polychro.api.Validator;
import io.polychro.api.ValidatorConfig;
import java.util.ArrayList;
import java.util.List;
public class MyValidator implements Validator {
private final ValidatorConfig config;
MyValidator(ValidatorConfig config) {
this.config = config;
}
@Override
public String name() {
return "my-validator";
}
@Override
public int priority() {
// Run after schema (200) but before ruleset (300)
return 250;
}
@Override
public List<Diagnostic> validate(Document document, ValidatorConfig config) {
List<Diagnostic> diagnostics = new ArrayList<>();
// Your validation logic here
String content = document.content();
if (!content.contains("version:")) {
diagnostics.add(new Diagnostic(
"my-version-required",
Severity.ERROR,
"Document must declare a version",
Range.at(1, 1),
name(),
"Add a 'version' field at the root level"
));
}
return diagnostics;
}
}
Step 4 — Register via ServiceLoader¶
Create the file META-INF/services/io.polychro.api.ValidatorFactory:
In Maven, place this at:
Step 5 — Package and Use¶
Add your validator JAR to the classpath alongside polychro-core, and it will be discovered automatically:
// Your validator participates without any explicit registration
Linter linter = Linter.builder()
.config(Path.of(".polychro.yml"))
.build();
List<Diagnostic> issues = linter.lint(doc);
// Includes diagnostics from your validator
Configuration¶
Your validator can read configuration from .polychro.yml:
Access it in your validator:
@Override
public List<Diagnostic> validate(Document document, ValidatorConfig config) {
String customOption = config.getString("customOption", "default");
int threshold = config.getInt("threshold", 5);
// ...
}
Priority Ordering¶
Built-in validator priorities:
| Validator | Priority | Rationale |
|---|---|---|
| Well-formedness | 100 | Must run first — structural issues invalidate everything else |
| Schema | 200 | Shape validation before semantic checks |
| JSON Structure | 250 | Strict typing after schema |
| Ruleset | 300 | Semantic rules assume valid structure |
| Markdown | 400 | Document-level checks run last |
Choose a priority that positions your validator correctly in the pipeline.
Testing¶
Test your validator independently using the Polychro API:
@Test
void shouldDetectMissingVersion() {
MyValidator validator = new MyValidator(ValidatorConfig.empty());
Document doc = Document.fromString("name: test\n", "yaml");
List<Diagnostic> diagnostics = validator.validate(doc, ValidatorConfig.empty());
assertThat(diagnostics).hasSize(1);
assertThat(diagnostics.get(0).code()).isEqualTo("my-version-required");
}
Or test through the full pipeline: