Skip to main content

sim_lib_intent/
codec.rs

1//! `codec:intent`: the domain codec for Intent values.
2//!
3//! Like `codec:scene`, this codec round-trips Intent values only and fails
4//! closed outside its domain: it validates the Intent on the way in and out and
5//! serializes through the codec-neutral portable value form in `sim-codec`. A
6//! malformed Intent becomes a structured `CodecError`, never a panic. It is
7//! built on the shared [`DomainCodecLib`] scaffold and registers the Intent kind
8//! Shapes through `with_shapes`.
9
10use std::sync::Arc;
11
12use sim_codec::{
13    Decoder, DomainCodecLib, Encoder, Input, Output, ReadCx, decode_portable, domain_input_text,
14    encode_portable,
15};
16use sim_kernel::{CodecId, Error, Lib, LibManifest, Linker, LoadCx, Result, Symbol, WriteCx};
17use sim_shape::shape_value;
18
19use crate::model::validate_intent;
20use crate::shapes::{intent_shape_specs, intent_shape_symbol};
21
22/// Stable codec symbol for the intent domain codec.
23pub fn intent_codec_symbol() -> Symbol {
24    Symbol::qualified("codec", "intent")
25}
26
27/// The Intent domain codec object.
28pub struct IntentCodec;
29
30impl Decoder for IntentCodec {
31    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
32        let source = domain_input_text(cx.codec, input)?;
33        let expr = decode_portable(cx.codec, &source)?;
34        validate(cx.codec, &expr)?;
35        Ok(expr)
36    }
37}
38
39impl Encoder for IntentCodec {
40    fn encode(&self, cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
41        validate(cx.codec, expr)?;
42        Ok(Output::Text(encode_portable(cx.codec, expr)?))
43    }
44}
45
46fn validate(codec: CodecId, expr: &sim_kernel::Expr) -> Result<()> {
47    validate_intent(expr).map_err(|error| Error::CodecError {
48        codec,
49        message: format!("malformed intent at {error}"),
50    })
51}
52
53/// Library that registers the Intent kind Shapes and `codec:intent`.
54pub struct IntentCodecLib {
55    symbol: Symbol,
56    codec_id: CodecId,
57}
58
59impl IntentCodecLib {
60    /// Build the lib with a freshly allocated codec id.
61    pub fn new(codec_id: CodecId) -> Self {
62        Self {
63            symbol: intent_codec_symbol(),
64            codec_id,
65        }
66    }
67
68    fn domain_lib(&self) -> DomainCodecLib {
69        let shapes = intent_shape_specs()
70            .into_iter()
71            .map(|(symbol, shape)| (symbol.clone(), shape_value(symbol, shape)))
72            .collect();
73        DomainCodecLib::new(
74            self.symbol.clone(),
75            self.codec_id,
76            Arc::new(IntentCodec),
77            Arc::new(IntentCodec),
78            intent_shape_symbol(),
79        )
80        .with_shapes(shapes)
81    }
82}
83
84impl Lib for IntentCodecLib {
85    fn manifest(&self) -> LibManifest {
86        self.domain_lib().manifest()
87    }
88
89    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
90        self.domain_lib().load(cx, linker)
91    }
92}