From a shapes file to a validation report
Install the CLI, write three constraints, compile them, and run them against a database. This takes about ten minutes.
-
Install
Prebuilt binaries with both database backends are attached to every GitHub release, for Linux x86_64, Linux arm64 and macOS arm64. Each tarball has a SHA-256 checksum next to it. With cargo:
cargo install --locked shacl2cypher # compile only cargo install --locked shacl2cypher --features neo4j,ladybug # with database backendsThe
ladybugfeature builds C++ and needscmake.--lockedinstalls the dependency versions the release was tested with; Rust 1.87 or newer is required.To work from Python or Node.js instead, the packages bundle both backends:
pip install shacl2cypher # CPython 3.9+ npm install shacl2cypher # Node.js 18+ -
Write a shapes file
Save this as
shapes.ttl. It says that every:Personneeds a name, that a name should start with a capital letter (a warning, not an error), and that a person works for at most one company.@prefix sh: <http://www.w3.org/ns/shacl#> . @prefix ex: <http://example.org/> . ex:PersonShape sh:targetClass ex:Person ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:message "Person needs a name" ] , [ sh:path ex:name ; sh:pattern "^[A-Z]" ; sh:severity sh:Warning ] , [ sh:path ex:worksFor ; sh:maxCount 1 ] .By convention
ex:Personbecomes the label:Personandex:namebecomes the propertyname.ex:worksForbecomes the relationship typeWORKS_FORwhen a schema snapshot declares that type and noworksForproperty. The SHACL guide covers the full rules. -
Compile
shacl2cypher compile shapes.ttl --dialect neo4j --node-key id -o out/This writes two files:
out/manifest.json: the primary artifact. It records provenance (compiler version, options, the SHA-256 of each input) and one entry per rule, with its id, severity, status, cost class, source line and queries.out/queries.cypher: the same queries for people. Each is headed by// name: <name>(summaries by#summary), so you can paste them into any Cypher client. They take two parameters,$limitand$sampleSize.
--node-key idnames the property that identifies nodes in result rows. A shape can override it withs2c:key. -
Validate a database
Neo4j. Every query runs in a transaction that is always rolled back.
NEO4J_PASSWORD=secret shacl2cypher validate shapes.ttl \ --connect bolt://localhost:7687 --node-key idLadybugDB. The file is opened read-only, and the schema is read from it before compiling.
shacl2cypher validate shapes.ttl --ladybug ./graph.lbug --node-key idYou get the table shown on the overview: one line per rule, up to ten violations each, and a totals line. The exit code is
0when nothing at or above--fail-on(defaultviolation) failed,1when something did,2for setup errors and3when a query timed out and the result is incomplete. -
Pick a report format
shacl2cypher validate shapes.ttl --ladybug graph.lbug --node-key id --format json -o report.json shacl2cypher validate shapes.ttl --ladybug graph.lbug --node-key id --format junit -o junit.xml shacl2cypher validate shapes.ttl --ladybug graph.lbug --node-key id --format sarif -o report.sarifUseful options:
--timeout <seconds>per query,--limitfor violations listed per rule (0lists all),--fail-on violation|warning|info, and--verboseto include every focus property in rows. -
Compile once, validate many times
For LadybugDB, compiling ahead of time needs a schema snapshot, because its tables are typed:
shacl2cypher schema dump --ladybug ./graph.lbug -o schema.json shacl2cypher compile shapes.ttl --dialect ladybug --schema schema.json --node-key id -o out/Commit the manifest, review query changes in pull requests, and run it later:
shacl2cypher validate --manifest out/manifest.json --ladybug ./graph.lbug --format junit -o junit.xmlA manifest compiled for one dialect is rejected when run against another database.
From Python
import shacl2cypher
compilation = shacl2cypher.compile(["shapes.ttl"], dialect="neo4j", node_key="id")
compilation.write("out") # manifest.json and queries.cypher
with shacl2cypher.Ladybug("graph.lbug") as db: # or shacl2cypher.Neo4j("bolt://localhost:7687", password="...")
report = db.validate(["shapes.ttl"], node_key="id", timeout=30)
print(report.render("table")) # also "json", "junit", "sarif"
raise SystemExit(report.exit_code("violation"))
Shapes can be paths or in-memory shacl2cypher.Source(name, text) documents. Errors derive from Shacl2CypherError. Calls release the GIL.
From TypeScript
import { compile, Ladybug, renderReport, exitCode } from 'shacl2cypher';
const compilation = await compile({ shapes: ['shapes.ttl'], dialect: 'neo4j', nodeKey: 'id' });
compilation.write('out');
const db = await Ladybug.open('graph.lbug'); // or await Neo4j.connect({ uri: 'bolt://localhost:7687', password: '...' })
try {
const report = await db.validate({ shapes: ['shapes.ttl'], nodeKey: 'id', timeoutMs: 30_000 });
console.log(renderReport(report, 'table'));
process.exitCode = exitCode(report, 'violation');
} finally {
await db.close();
}
Calls return Promises and never block the event loop. Unknown option keys throw TypeError. Types ship in index.d.ts.