6.3 KiB
Embedding
Decodal runtime は filesystem、network、environment variable を直接読み込まない。
ホストは global bindings と import の解決方法を注入し、評価結果を Data または application type として受け取る。
Rust runtime
Engine は ImportLoader と global bindings を持つ。
外部 resource を必要としない source には EmptyLoader を使える。
use decodal::{EmptyLoader, Engine, Value};
let mut engine = Engine::new(EmptyLoader);
engine.bind_global(
"Service",
Value::object([
("name", Value::string_type()),
("port", Value::int_type().gt(443).default_int(8443)?),
("enabled", Value::bool_type().default_bool(true)?),
]),
)?;
let module = engine.add_root_source(
"service.dcdl",
"service.dcdl",
r#"{ name = "api"; port = 9443; } as Service"#,
)?;
let value = engine.eval_module(module)?;
let data = engine.materialize(&value)?;
Value は concrete value と host-defined range の両方を表す公開型である。
主な constructor は次の通りである。
Value::string、int、float、bool、array、object: concrete value。Value::string_type、int_type、float_type、bool_type: primitive range。Value::unknown: 最上位 rangeUnknown。Value::array_of: 全要素へ同じ range を適用する array range。Value::map_of: 全 field value へ同じ range を適用する associative-array range。Value::object_with_rest: named fields と残りの field range を持つ object。
Typed Rust integration
derive feature の Decodal derive は、Rust struct から DecodalSchema と DecodalDecode を生成する。
use decodal::{Decodal, DecodalDecode, DecodalSchema};
#[derive(Decodal)]
struct Service {
name: String,
#[decodal(gt = 443, default = 8443)]
port: i64,
#[decodal(rename = "feature.enable", default = true)]
feature_enabled: bool,
}
DecodalSchema::decodal_schema() は Engine::bind_global に渡せる Value を返す。
materialize 済みの Data は DecodalDecode::decodal_decode で Rust type に変換できる。
追加 field を受け取る struct では、map field に #[decodal(rest)] を付ける。
use std::collections::BTreeMap;
use decodal::{Data, Decodal};
#[derive(Decodal)]
struct OpenConfig {
enabled: bool,
#[decodal(rest)]
extra: BTreeMap<String, Data>,
}
BTreeMap<String, Data> は ...Unknown、BTreeMap<String, String> は ...String に対応する。
named fields は rest map から除外される。
#[decodal(rest)] は struct ごとに1つだけ指定でき、rename、default、field constraints とは併用できない。
Imports
ImportLoader::load は LoadedImport::Source または LoadedImport::Value を返す。
ホストは specifier の解決、filesystem や他の storage からの読み込み、content type の判定をすべて管理する。
LoadedImport::Source は DCDL source を返す。
LoadedImport::Value は Markdown、JSON、TOML などをホスト独自の規則で構造化して返す用途に使える。
import "./post.md"
-> host reads and parses Markdown
-> LoadedImport::Value {
key: "content/post.md",
value: { frontmatter: {...}, body: "..." }
}
-> normal Decodal validation and materialization
stable key は import の同一性と diagnostic の provenance に使われる。
構造化した値が Decodal constraint に違反した場合、diagnostic は import key と logical value path、および違反した DCDL constraint の source span を示す。
外部形式そのものの parse error と位置情報は loader が報告する。
Shared host environment
HostEnvironment は loader の作成と global binding の設定を1つにまとめる。
同じ environment を runtime、language service、LSP へ渡すことで、実行時と編集時の評価規則を一致させられる。
use decodal::{Engine, HostEnvironment};
struct AppEnvironment;
impl HostEnvironment for AppEnvironment {
type Loader = ContentLoader;
fn create_loader(&self) -> Self::Loader {
ContentLoader::new()
}
fn configure_engine(
&self,
engine: &mut Engine<Self::Loader>,
) -> decodal::Result<()> {
engine.bind_global("Site", site_schema())?;
Ok(())
}
}
transport 非依存の tooling は decodal_language_service::LanguageService::new(&environment) で構築する。
LSP integration では decodal_lsp::LspEnvironment を実装し、environment factory から server を起動する。
use decodal_lsp::{LspEnvironment, run_stdio};
impl LspEnvironment for AppEnvironment {}
run_stdio(|initialize| {
let _ = initialize;
Ok(AppEnvironment)
})?;
factory は server capability の応答前に client の InitializeParams を一度受け取る。
LspEnvironment の document lifecycle hooks を使うと、未保存の DCDL、Markdown、その他の resource を host-owned overlay に反映できる。
JavaScript and browser environments
decodal-wasm の DecodalLanguageService は filesystem や仮想 project を仮定しない。
JavaScript 側が globals、loadImport、completeImport を提供する。
import init, { DecodalLanguageService } from 'decodal-wasm';
await init();
const files = {
'schema.dcdl': 'Server = { port = Int; };',
};
const service = new DecodalLanguageService({
globals: {
App: {
enabled: { $decodal: 'Bool', default: true },
},
},
loadImport(_currentKey, specifier) {
const key = specifier.replace(/^\.\//, '');
return { kind: 'source', key, name: key, source: files[key] };
},
completeImport() {
return ['./schema.dcdl'];
},
});
const result = service.evaluate('main.dcdl', 'main.dcdl', 'App');
JavaScript の primitive、array、plain object は concrete value になる。
range は $decodal descriptor で指定する。
{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }{ $decodal: 'Unknown' }{ $decodal: 'Array', item }{ $decodal: 'Map', value }{ $decodal: 'Object', fields, rest }{ $decodal: 'Range', constraints, default }
import callbacks は同期 API である。 network resource などの非同期入力は、評価の前に preload または cache しておく。