# leptris-rs
Rust bindings for [libleptris](https://github.com/leptris/leptris), the
pure C99 XML 1.0 / XPath 1.0 / XSLT / XQuery engine with zero required
runtime dependencies.
This repository owns the publishable `leptris` crate (and its companion
`leptris-descriptor` crate). It is the standalone binding repository for
the engine — the same model as the
[leptris-ruby](https://github.com/leptris/leptris-ruby) and
[leptris-py](https://github.com/leptris/leptris-py) bindings: one engine,
many binding repositories, each with its own CI and releases.
## Workspace layout
| `leptris` | Safe wrapper over the engine's public C API: documents, elements, XPath/XQuery evaluation, serialization. `build.rs` links the engine through the `LEPTRIS_LIB_PATH` environment variable. |
| `leptris-descriptor` | `no_std` `#[repr(C)]` mirror of the engine's `src/include/leptris/descriptor.h` ABI descriptors. With the `link` feature it calls the engine directly and checks its layouts against the engine's own introspection. |
## Mapping to the C engine
The crate is a binding, not a reimplementation. Rust types map onto the
engine's opaque handles (`LeptrisDocument`, `LeptrisElement`, ...) and
every operation is a thin safe wrapper over the corresponding `leptris_*`
C function. Memory ownership follows the engine contract: strings
returned from a document are document-owned and live until
`leptris_document_free`; the wrappers encode that lifetime in Rust
types.
## Pointing the crate at an engine build
The crate does not build the C engine itself. It links a prebuilt shared
library, resolved in this order:
1. `LEPTRIS_LIB_PATH` — full path to the shared library file, or a
directory containing it.
2. Fallback to the system linker search (`-lleptris`).
The simplest engine build for development:
```sh
cmake -B build-shared -S /path/to/leptris \
-DCMAKE_BUILD_TYPE=Release \
-DLEPTRIS_BUILD_SHARED=ON
cmake --build build-shared -j4
```
> **Warning:** the CMake option is `LEPTRIS_BUILD_SHARED`, *not*
> `BUILD_SHARED`.
Then run the crate against it:
```sh
LEPTRIS_LIB_PATH=build-shared/src cargo test
```
On macOS you may also need `DYLD_LIBRARY_PATH` pointing at the same
directory when running the test binaries.
## The descriptor layout gate
`leptris-descriptor` mirrors the `#[repr(C)]` layout of the engine's
descriptor structs. The test suite gates that mirror against the engine
itself: `tests/descriptor_abi.rs` and `descriptor/tests/layout.rs`
compare `core::mem::size_of` on the Rust side with the engine's
`leptris_plan_*_struct_size` introspection functions. If the engine
changes a descriptor layout, these tests fail until the mirror is
updated — the ABI never drifts silently.
## Relationship to bindings/rust in the engine repository
The engine repository keeps an in-tree Rust harness at `bindings/rust/`
for engine-side development. THIS repository (`leptris-rs`) owns the
crate going forward: publishable releases, binding-level issues, and
binding CI. Engine changes that affect the ABI surface are validated
here against real engine builds from `main`.
## CI
`.github/workflows/ci.yml` checks out the engine repository at `main`
into a sibling path, builds the shared library (utf8proc enabled,
`CMAKE_BUILD_TYPE=Release`), then runs `cargo build`, `cargo test` and
`cargo fmt --check` with `LEPTRIS_LIB_PATH` pointing at that build, on
an Ubuntu and macOS matrix.
## License
Same license as the engine — see the LICENSE file.
## Engine rides
The crate links libleptris at build time; CI checks out the engine
repository (main). The 1.6.3 ride carries libleptris 1.9.403:
the leptris#1936 regex-family close (104 engine-wrong W3C XSD
suite rows resolved to zero) and the leptris-rs#25-29 wave —
empty content satisfies a fixed value (the v1.9.402 regression),
nilled elements skip the content model, temporal enumeration
compares by instant, `-0` is a valid nonNegativeInteger lexical,
and whiteSpace=collapse covers every non-string builtin.