138 lines
3.7 KiB
Markdown
138 lines
3.7 KiB
Markdown
# Embedding API
|
|
|
|
Decodal core can be embedded without giving the core crate access to a filesystem.
|
|
The host supplies imported sources through `SourceLoader` and may also provide global bindings through the host prelude API.
|
|
|
|
## Host prelude
|
|
|
|
`Engine` owns a prelude environment.
|
|
Bindings in this environment are visible from every module loaded by the engine.
|
|
|
|
```text
|
|
prelude env
|
|
↓
|
|
module root env
|
|
↓
|
|
let / function env
|
|
```
|
|
|
|
Module top-level bindings shadow prelude bindings.
|
|
Primitive type names such as `String`, `Int`, `Float`, `Bool`, and `Array` are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings.
|
|
|
|
## Global bindings
|
|
|
|
The host can bind values before adding or evaluating user sources.
|
|
|
|
```rust
|
|
use decodal::{EmptyLoader, Engine, HostValue};
|
|
|
|
let mut engine = Engine::new(EmptyLoader);
|
|
|
|
engine.bind_global(
|
|
"Service",
|
|
HostValue::object([
|
|
("name", HostValue::string_type()),
|
|
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
|
("enabled", HostValue::bool_type().default_bool(true)?),
|
|
]),
|
|
)?;
|
|
```
|
|
|
|
A user source can then refer to `Service` without importing it.
|
|
|
|
```dcdl
|
|
Service & {
|
|
name = "api";
|
|
port = 9443;
|
|
}
|
|
```
|
|
|
|
## HostValue
|
|
|
|
`HostValue` is the public builder-facing value representation for embedding.
|
|
It keeps host code from constructing internal `ThunkId` or `ObjectValue` values directly.
|
|
|
|
```text
|
|
HostValue =
|
|
String
|
|
Int
|
|
Float
|
|
Bool
|
|
Array(Vec<HostValue>)
|
|
Object(Vec<HostField>)
|
|
Abstract { constraints, default }
|
|
```
|
|
|
|
When a host value is bound, the engine internalizes it into `RuntimeValue` and allocates value thunks for object fields, array items, and defaults.
|
|
|
|
## Abstract host objects
|
|
|
|
A host-provided schema object is represented as a concrete object structure whose fields may contain abstract values.
|
|
|
|
```rust
|
|
HostValue::object([
|
|
("name", HostValue::string_type()),
|
|
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
|
])
|
|
```
|
|
|
|
Conceptually this becomes:
|
|
|
|
```text
|
|
Concrete(Object {
|
|
name -> Thunk(Abstract { constraints: [String], default: none })
|
|
port -> Thunk(Abstract { constraints: [Int, > 443], default: 8443 })
|
|
})
|
|
```
|
|
|
|
This matches the runtime model used for Decodal source-defined schema objects.
|
|
|
|
## Typed Rust integration
|
|
|
|
Hosts can enable the `derive` feature on `decodal` to keep a Rust struct, the Decodal schema, and the decoded result in sync.
|
|
The derive implements two traits from the `decodal` crate:
|
|
|
|
- `DecodalSchema`: builds a `HostValue` schema that can be passed to `Engine::bind_global`.
|
|
- `DecodalDecode`: decodes materialized `Data` back into the Rust type.
|
|
|
|
```rust
|
|
use decodal::{Decodal, DecodalDecode, DecodalSchema, EmptyLoader, Engine};
|
|
|
|
#[derive(Decodal)]
|
|
struct Service {
|
|
name: String,
|
|
|
|
#[decodal(gt = 443, default = 8443)]
|
|
port: i64,
|
|
|
|
#[decodal(rename = "feature.enable", default = true)]
|
|
feature_enabled: bool,
|
|
}
|
|
|
|
let mut engine = Engine::new(EmptyLoader);
|
|
engine.bind_global("Service", Service::decodal_schema())?;
|
|
|
|
let value = engine.eval_module(module)?;
|
|
let data = engine.materialize(&value)?;
|
|
let service = Service::decodal_decode(&data)?;
|
|
```
|
|
|
|
Supported field attributes are intentionally small:
|
|
|
|
- `rename = "path.to.field"`
|
|
- `default`
|
|
- `default = value`
|
|
- numeric constraints: `gt`, `gte`, `lt`, `lte`
|
|
|
|
The derive does not add host callbacks or reflection.
|
|
It only generates schema construction and typed decoding code.
|
|
|
|
## SourceLoader and prelude together
|
|
|
|
`SourceLoader` and host prelude bindings are independent mechanisms.
|
|
|
|
- Use `SourceLoader` when user sources should explicitly import host-provided modules.
|
|
- Use prelude bindings when host-provided schemas or constants should be globally available.
|
|
|
|
Both mechanisms share the same runtime evaluator, thunk model, and materialization rules.
|