stencilr/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(clippy::all, clippy::pedantic)]
3#![allow(clippy::missing_errors_doc, clippy::cast_sign_loss)]
4
5use bytes::{Bytes, BytesMut};
6use flexbuffers::VectorReader;
7use hashbrown::HashMap;
8use ordinary_auth::Auth;
9use ordinary_config::{
10    ContentDefinition, ModelConfig, QueryExpression, StoredCachePolicy, TemplateConfig,
11    TemplateRef, TemplateRefField, TemplateRefFieldBind,
12};
13use ordinary_storage::{ArtifactKind, CacheRead, RefDepth, Storage};
14use ordinary_types::{Field, Kind};
15use std::str::FromStr;
16use std::sync::Arc;
17use std::time::Duration;
18use std::{collections::BTreeMap, error::Error};
19use tracing::{info_span, instrument};
20
21use parking_lot::{Mutex, RwLock};
22use wasi_common::{
23    WasiCtx,
24    sync::{WasiCtxBuilder, add_to_linker},
25};
26use wasmtime::{
27    AsContext, AsContextMut, Caller, Extern, Linker, Memory, MemoryType, Module, Store,
28};
29
30pub use wasmtime::Engine;
31
32#[inline]
33fn read_wasm_mem(
34    caller: &mut Caller<'_, WasiCtx>,
35    ptr: i32,
36    buf: &mut [u8],
37) -> Result<(), Box<dyn Error>> {
38    let Some(Extern::Memory(mem)) = caller.get_export("memory") else {
39        return Err("no mem".into());
40    };
41
42    mem.read(caller.as_context_mut(), ptr as usize, buf)?;
43    Ok(())
44}
45
46#[inline]
47fn write_wasm_mem(caller: &mut Caller<'_, WasiCtx>, bytes: &[u8]) -> Option<i64> {
48    let alloc = match caller.get_export("alloc") {
49        Some(Extern::Func(malloc)) => match malloc.typed::<i32, i32>(caller.as_context()) {
50            Ok(malloc) => malloc,
51            Err(_) => return None,
52        },
53        _ => return None,
54    };
55
56    let len = bytes.len();
57
58    let Ok(params) = i32::try_from(len) else {
59        return None;
60    };
61
62    let Ok(ptr) = alloc.call(caller.as_context_mut(), params) else {
63        return None;
64    };
65
66    let Some(Extern::Memory(mem)) = caller.get_export("memory") else {
67        return None;
68    };
69
70    match mem.write(caller.as_context_mut(), ptr as usize, bytes) {
71        Ok(()) => {}
72        Err(_) => return None,
73    }
74
75    let ptr64 = i64::from(ptr) << 32;
76    let Ok(len64) = i64::try_from(len) else {
77        return None;
78    };
79
80    Some(ptr64 | len64)
81}
82
83#[derive(Clone, Debug)]
84enum Include {
85    Field(u8, Kind),
86    Fields(u8, Vec<Include>, bool),
87}
88
89impl Include {
90    fn build_for_field(
91        field: &Field,
92        ref_field: &TemplateRefField,
93        model_map: &HashMap<String, ModelConfig>,
94        ref_depth: &mut RefDepth,
95    ) -> Result<Self, Box<dyn Error>> {
96        let field_idx = field.idx;
97
98        match &ref_field.fields {
99            Some(nested_ref_fields) => {
100                let mut nested = vec![];
101                let mut is_list = false;
102
103                let mut nested_field_map = HashMap::new();
104
105                match &field.kind {
106                    Kind::List { kind } => {
107                        if let Kind::Object { name: _, fields } = &**kind {
108                            for nested_field in fields {
109                                nested_field_map.insert(nested_field.name.clone(), nested_field);
110                            }
111
112                            is_list = true;
113                        } else {
114                            return Err("cannot have nested fields on non object".into());
115                        }
116                    }
117                    Kind::Object { name: _, fields } => {
118                        for nested_field in fields {
119                            nested_field_map.insert(nested_field.name.clone(), nested_field);
120                        }
121                    }
122                    Kind::Ref {
123                        model,
124                        field: _,
125                        many: _,
126                    } => {
127                        if let Some(model) = model_map.get(model) {
128                            for nested_field in &model.fields {
129                                nested_field_map.insert(nested_field.name.clone(), nested_field);
130                            }
131
132                            ref_depth.0.push(((field.idx, 1, None), RefDepth(vec![])));
133                        }
134                    }
135                    _ => return Err("cannot have nested fields on non object".into()),
136                }
137
138                let mut fields_clone = nested_ref_fields.clone();
139                fields_clone.sort_by(|a, b| a.idx.cmp(&b.idx));
140
141                for nested_ref_field in fields_clone {
142                    if let Some(nested_field) = nested_field_map.get(&nested_ref_field.name) {
143                        nested.push(Self::build_for_field(
144                            nested_field,
145                            &nested_ref_field,
146                            model_map,
147                            ref_depth,
148                        )?);
149                    }
150                }
151
152                nested.shrink_to_fit();
153                Ok(Include::Fields(field_idx, nested, is_list))
154            }
155            None => Ok(Include::Field(field_idx, field.kind.clone())),
156        }
157    }
158
159    fn build_for_content(
160        content_def: &ContentDefinition,
161        template_ref: &TemplateRef,
162    ) -> Result<Vec<Self>, Box<dyn Error>> {
163        let mut include_fields = vec![];
164
165        let mut fields_clone = template_ref.fields.clone();
166        fields_clone.sort_by(|a, b| a.idx.cmp(&b.idx));
167
168        let mut content_def_field_map = HashMap::new();
169        for field in &content_def.fields {
170            content_def_field_map.insert(field.name.clone(), field);
171        }
172
173        for field in fields_clone {
174            if let Some(def_field) = content_def_field_map.get(&field.name) {
175                let include = Self::build_for_field(
176                    def_field,
177                    &field,
178                    &HashMap::new(),
179                    &mut RefDepth(vec![]),
180                )?;
181                include_fields.push(include);
182            }
183        }
184
185        include_fields.shrink_to_fit();
186        Ok(include_fields)
187    }
188
189    fn build_for_model(
190        model_config: &ModelConfig,
191        template_ref: &TemplateRef,
192        model_map: &HashMap<String, ModelConfig>,
193    ) -> Result<(Vec<Self>, RefDepth), Box<dyn Error>> {
194        let mut include_fields = vec![];
195
196        let mut fields_clone = template_ref.fields.clone();
197        fields_clone.sort_by(|a, b| a.idx.cmp(&b.idx));
198
199        let mut model_config_field_map = HashMap::new();
200        for field in &model_config.fields {
201            model_config_field_map.insert(field.name.clone(), field);
202        }
203        let mut ref_depth = RefDepth(vec![]);
204
205        for field in fields_clone {
206            if let Some(def_field) = model_config_field_map.get(&field.name) {
207                let include = Self::build_for_field(def_field, &field, model_map, &mut ref_depth)?;
208                include_fields.push(include);
209            }
210        }
211
212        include_fields.shrink_to_fit();
213        Ok((include_fields, ref_depth))
214    }
215
216    fn marry(
217        &self,
218        builder: &mut flexbuffers::VectorBuilder,
219        reader: &VectorReader<&[u8]>,
220    ) -> Result<(), Box<dyn Error>> {
221        match self {
222            Include::Field(idx, kind) => {
223                kind.copy_to(&reader.idx(*idx as usize), builder)?;
224            }
225            Include::Fields(idx, fields, is_list) => {
226                let mut nested = builder.start_vector();
227                let reader = reader.idx(*idx as usize).as_vector();
228
229                if *is_list {
230                    for list_reader in &reader {
231                        let mut builder = nested.start_vector();
232
233                        for field in fields {
234                            field.marry(&mut builder, &list_reader.as_vector())?;
235                        }
236
237                        builder.end_vector();
238                    }
239                } else {
240                    for field in fields {
241                        field.marry(&mut nested, &reader)?;
242                    }
243                }
244
245                nested.end_vector();
246            }
247        }
248
249        Ok(())
250    }
251}
252
253#[derive(Clone, Debug)]
254enum QueryKind {
255    /// path position, whether it's a wildcard
256    Segment(usize, bool),
257    /// token field idx
258    Token(u8),
259    /// if there is no query
260    None,
261}
262
263#[derive(Clone, Debug)]
264enum Query {
265    /// model kind, field idx, type
266    Model(
267        u8,
268        u8,
269        Kind,
270        RefDepth,
271        Option<ordinary_storage::QueryExpression>,
272    ),
273    /// def idx, field idx, type
274    Content(u8, u8, Kind),
275
276    /// def idx
277    ContentMulti(u8),
278}
279
280#[derive(Clone)]
281pub struct Template {
282    pub idx: u8,
283    pub config: TemplateConfig,
284
285    pub cache_control: Option<String>,
286
287    engine: Engine,
288    module: Arc<RwLock<Option<Module>>>,
289
290    storage: Arc<Storage>,
291
292    queries: Option<Vec<(Query, QueryKind, Vec<Include>)>>,
293}
294
295impl Template {
296    #[allow(clippy::too_many_lines, clippy::too_many_arguments)]
297    #[instrument(
298        name = "template"
299        skip(src, engine, config, auth, storage, model_map, content_map),
300        fields(i, nm),
301        err
302    )]
303    pub fn new(
304        src: Option<Bytes>,
305
306        config: TemplateConfig,
307        auth: Arc<Auth>,
308        model_map: &HashMap<String, ModelConfig>,
309        content_map: &HashMap<String, ContentDefinition>,
310
311        engine: Engine,
312        storage: Arc<Storage>,
313    ) -> Result<Template, Box<dyn Error>> {
314        tracing::Span::current().record("i", config.idx);
315        tracing::Span::current().record("nm", &config.name);
316
317        tracing::info!("init");
318
319        let module = if let Some(src) = src
320            && let Ok(module) = Module::new(&engine, src)
321        {
322            Arc::new(RwLock::new(Some(module)))
323        } else {
324            Arc::new(RwLock::new(None))
325        };
326
327        let mut queries = vec![];
328
329        // todo: disallow conflicting bindings
330
331        if let Some(content_refs) = &config.content {
332            for content_ref in content_refs {
333                if let Some(content_def) = content_map.get(&content_ref.name) {
334                    let mut def_fields_map = BTreeMap::new();
335
336                    for field in &content_def.fields {
337                        def_fields_map.insert(field.name.clone(), field);
338                    }
339
340                    let include_fields = Include::build_for_content(content_def, content_ref)?;
341
342                    if content_ref.all.is_some() {
343                        queries.push((
344                            content_ref.idx,
345                            Query::ContentMulti(content_def.idx),
346                            QueryKind::None,
347                            include_fields.clone(),
348                        ));
349                    }
350
351                    for ref_field in &content_ref.fields {
352                        if let Some(def_field) = def_fields_map.get(&ref_field.name)
353                            && let Some(binding) = &ref_field.bind
354                        {
355                            match binding {
356                                TemplateRefFieldBind::Segment {
357                                    name,
358                                    expression: _,
359                                } => {
360                                    let mut path_pos = None;
361
362                                    for (i, segment) in config.route.split('/').enumerate() {
363                                        if segment == format!("{{{name}}}") {
364                                            path_pos = Some((i, name.starts_with('*')));
365                                        }
366                                    }
367
368                                    if let Some((path_pos, is_wildcard)) = path_pos {
369                                        queries.push((
370                                            content_ref.idx,
371                                            Query::Content(
372                                                content_def.idx,
373                                                def_field.idx,
374                                                def_field.kind.clone(),
375                                            ),
376                                            QueryKind::Segment(path_pos, is_wildcard),
377                                            include_fields.clone(),
378                                        ));
379                                    } else {
380                                        return Err(format!("cannot bind to segment {{{name}}} as it is not present in the route").into());
381                                    }
382                                }
383                                TemplateRefFieldBind::Token {
384                                    field,
385                                    expression: _,
386                                } => {
387                                    let mut auth_field_idx = None;
388
389                                    for auth_field in &auth.config.access_token.claims {
390                                        if &auth_field.name == field {
391                                            auth_field_idx = Some(auth_field.idx);
392                                        }
393                                    }
394
395                                    if let Some(idx) = auth_field_idx {
396                                        queries.push((
397                                            content_ref.idx,
398                                            Query::Content(
399                                                content_def.idx,
400                                                def_field.idx,
401                                                def_field.kind.clone(),
402                                            ),
403                                            QueryKind::Token(idx),
404                                            include_fields.clone(),
405                                        ));
406                                    } else {
407                                        return Err(
408                                            format!("auth field {field} does not exist").into()
409                                        );
410                                    }
411                                }
412                            }
413                        }
414                    }
415                }
416            }
417        }
418
419        if let Some(model_refs) = &config.models {
420            for model_ref in model_refs {
421                if let Some(model_config) = model_map.get(&model_ref.name) {
422                    let mut model_config_fields_map = BTreeMap::new();
423
424                    let mut model_config = (*model_config).clone();
425
426                    let uuid_field = Field {
427                        idx: 0,
428                        name: "uuid".into(),
429                        kind: Kind::Uuid,
430                        indexed: Some(true),
431                        queryable: None,
432                        searchable: None,
433                        mapping: None,
434                        doc: None,
435                        encrypted: None,
436                        compressed: None,
437                    };
438
439                    let mut new_fields = model_config.fields.clone();
440                    new_fields.splice(0..0, vec![uuid_field]);
441
442                    model_config.fields = new_fields;
443
444                    for field in &model_config.fields {
445                        model_config_fields_map.insert(field.name.clone(), field);
446                    }
447
448                    let (include_fields, ref_depth) =
449                        Include::build_for_model(&model_config, model_ref, model_map)?;
450
451                    for ref_field in &model_ref.fields {
452                        if let Some(config_field) = model_config_fields_map.get(&ref_field.name)
453                            && let Some(binding) = &ref_field.bind
454                        {
455                            match binding {
456                                TemplateRefFieldBind::Segment { name, expression } => {
457                                    let mut path_pos = None;
458
459                                    for (i, segment) in config.route.split('/').enumerate() {
460                                        if segment == format!("{{{name}}}") {
461                                            path_pos = Some((i, name.starts_with('*')));
462                                        }
463                                    }
464
465                                    let exp = match expression {
466                                        Some(exp) => match exp {
467                                            QueryExpression::Gte => {
468                                                Some(ordinary_storage::QueryExpression::Gte)
469                                            }
470                                            QueryExpression::Lte => {
471                                                Some(ordinary_storage::QueryExpression::Lte)
472                                            }
473                                            QueryExpression::Gt => {
474                                                Some(ordinary_storage::QueryExpression::Gt)
475                                            }
476                                            QueryExpression::Lt => {
477                                                Some(ordinary_storage::QueryExpression::Lt)
478                                            }
479                                            QueryExpression::Eq => {
480                                                Some(ordinary_storage::QueryExpression::Eq)
481                                            }
482                                            QueryExpression::BeginsWith => {
483                                                Some(ordinary_storage::QueryExpression::BeginsWith)
484                                            }
485                                        },
486                                        None => None,
487                                    };
488
489                                    if let Some((path_pos, is_wildcard)) = path_pos {
490                                        queries.push((
491                                            model_ref.idx,
492                                            Query::Model(
493                                                model_config.idx,
494                                                config_field.idx,
495                                                config_field.kind.clone(),
496                                                ref_depth.clone(),
497                                                exp,
498                                            ),
499                                            QueryKind::Segment(path_pos, is_wildcard),
500                                            include_fields.clone(),
501                                        ));
502                                    } else {
503                                        return Err(format!("cannot bind to segment {{{name}}} as it is not present in the route").into());
504                                    }
505                                }
506                                TemplateRefFieldBind::Token { field, expression } => {
507                                    let mut auth_field_idx = None;
508
509                                    for auth_field in &auth.config.access_token.claims {
510                                        if &auth_field.name == field {
511                                            auth_field_idx = Some(auth_field.idx);
512                                        }
513                                    }
514
515                                    let exp = match expression {
516                                        Some(exp) => match exp {
517                                            QueryExpression::Gte => {
518                                                Some(ordinary_storage::QueryExpression::Gte)
519                                            }
520                                            QueryExpression::Lte => {
521                                                Some(ordinary_storage::QueryExpression::Lte)
522                                            }
523                                            QueryExpression::Gt => {
524                                                Some(ordinary_storage::QueryExpression::Gt)
525                                            }
526                                            QueryExpression::Lt => {
527                                                Some(ordinary_storage::QueryExpression::Lt)
528                                            }
529                                            QueryExpression::Eq => {
530                                                Some(ordinary_storage::QueryExpression::Eq)
531                                            }
532                                            QueryExpression::BeginsWith => {
533                                                Some(ordinary_storage::QueryExpression::BeginsWith)
534                                            }
535                                        },
536                                        None => None,
537                                    };
538
539                                    if let Some(idx) = auth_field_idx {
540                                        queries.push((
541                                            model_ref.idx,
542                                            Query::Model(
543                                                model_config.idx,
544                                                config_field.idx,
545                                                config_field.kind.clone(),
546                                                ref_depth.clone(),
547                                                exp,
548                                            ),
549                                            QueryKind::Token(idx),
550                                            include_fields.clone(),
551                                        ));
552                                    } else {
553                                        return Err(
554                                            format!("auth field {field} does not exist").into()
555                                        );
556                                    }
557                                }
558                            }
559                        }
560                    }
561                }
562            }
563        }
564
565        let mut cache_control = String::new();
566
567        if let Some(cache) = &config.cache
568            && let Some(http_cache) = &cache.http
569            && let Some(http_cache_control) = &http_cache.cache_control
570        {
571            // todo: make the default configurable by the admin server
572            http_cache_control.header_value(&mut cache_control, "")?;
573        } else {
574            // todo: set a default for the cache control (dictated by admin)
575        }
576
577        Ok(Template {
578            idx: config.idx,
579            config,
580
581            cache_control: if cache_control.is_empty() {
582                None
583            } else {
584                Some(cache_control)
585            },
586
587            engine,
588            module,
589            storage,
590
591            queries: if queries.is_empty() {
592                None
593            } else {
594                queries.sort_by(|a, b| a.0.cmp(&b.0));
595                let mut queries = queries
596                    .iter()
597                    .map(|(_, q, qk, i)| (q.clone(), qk.clone(), i.clone()))
598                    .collect::<Vec<_>>();
599
600                queries.shrink_to_fit();
601
602                Some(queries)
603            },
604        })
605    }
606
607    pub fn start_tasks(&self) {
608        if let Some(cache_config) = &self.config.cache
609            && let Some(stored_cache) = &cache_config.stored
610        {
611            let storage_clone = self.storage.clone();
612            let storage_clone2 = self.storage.clone();
613
614            let cache_config_clone = stored_cache.clone();
615            let idx = self.config.idx;
616
617            let cache_span = info_span!("cache");
618            let cache_span_clone = cache_span.clone();
619
620            // todo: pass this default and a range check from admin
621            let (mut sync_min, mut sync_max) =
622                cache_config_clone.sync_interval.unwrap_or((60, 60 * 3));
623
624            sync_min *= 1000;
625            sync_max *= 1000;
626
627            tokio::spawn(async move {
628                loop {
629                    // todo: break on app kill
630
631                    let cache_sync_interval_ms = rand::random_range(sync_min..sync_max);
632
633                    tokio::time::sleep(Duration::from_millis(cache_sync_interval_ms)).await;
634
635                    cache_span.in_scope(|| {
636                        if let Err(err) = storage_clone.cache.sync_cache(&CacheRead::Template, idx)
637                        {
638                            tracing::error!(%err, "failed to sync cache");
639                        }
640                    });
641                }
642            });
643
644            if let StoredCachePolicy::Permanent = stored_cache.policy {
645                // don't start the cache_clean task
646            } else {
647                // todo: pass this default and a range check from admin
648                let (mut clean_min, mut clean_max) =
649                    cache_config_clone.clean_interval.unwrap_or((60, 60 * 3));
650
651                clean_min *= 1000;
652                clean_max *= 1000;
653
654                tokio::spawn(async move {
655                    loop {
656                        // todo: break on app kill
657
658                        let cache_clean_interval_ms = rand::random_range(clean_min..clean_max);
659
660                        tokio::time::sleep(Duration::from_millis(cache_clean_interval_ms)).await;
661
662                        cache_span_clone.in_scope(|| {
663                            if let Err(err) = storage_clone2.cache.clean_cache(
664                                &CacheRead::Template,
665                                &cache_config_clone,
666                                idx,
667                            ) {
668                                tracing::error!(%err, "failed to clean cache");
669                            }
670                        });
671                    }
672                });
673            }
674        }
675    }
676
677    // accepts token + bound arguments and then uses config to go request all the stuff
678    #[allow(clippy::too_many_lines)]
679    #[instrument(skip(self, path, params, error, totp, claims), err)]
680    pub fn query(
681        &self,
682        path: String,
683        params: Option<String>,
684        // message and code
685        error: Option<(String, u16)>,
686        // svg qr_code, account and recovery_codes
687        totp: Option<(String, String, String)>,
688        claims: &Option<VectorReader<&[u8]>>,
689    ) -> Result<Bytes, Box<dyn Error>> {
690        let mut builder = flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
691        let mut vec_builder = builder.start_vector();
692
693        if let Some(queries) = &self.queries {
694            let segments: Vec<&str> = path.split('/').collect();
695
696            let _params_map = match params {
697                Some(_params) => {
698                    let params_map: BTreeMap<String, String> = BTreeMap::new();
699                    // todo: update params map
700                    Some(params_map)
701                }
702                None => None,
703            };
704
705            // todo: push params into
706
707            // todo: push flags into
708
709            for (query, query_kind, includes) in queries {
710                let kind = match query {
711                    Query::Model(_, _, kind, _, _) | Query::Content(_, _, kind) => kind,
712                    Query::ContentMulti(_) => &Kind::Void,
713                };
714
715                let indexed_val: Bytes = match query_kind {
716                    QueryKind::Segment(pos, is_wildcard) => {
717                        if pos <= &segments.len() {
718                            let val = if *is_wildcard {
719                                let mut out = String::new();
720
721                                for (i, segment) in segments.iter().enumerate() {
722                                    if i == *pos {
723                                        (*segment).clone_into(&mut out);
724                                    } else if i > *pos {
725                                        out = format!("{out}/{segment}");
726                                    }
727                                }
728
729                                out
730                            } else {
731                                segments[*pos].to_string()
732                            };
733
734                            match kind {
735                                Kind::Uuid => {
736                                    let uuid = uuid::Uuid::from_str(&val)?;
737                                    Bytes::copy_from_slice(uuid.as_bytes())
738                                }
739                                _ => Bytes::copy_from_slice(val.as_bytes()),
740                            }
741                        } else {
742                            return Err("position out of bounds for route".into());
743                        }
744                    }
745                    QueryKind::Token(field_idx) => {
746                        if let Some(claims) = claims {
747                            let reader = claims.idx(*field_idx as usize);
748
749                            match kind {
750                                Kind::Uuid => Bytes::copy_from_slice(reader.as_blob().0),
751                                Kind::String => Bytes::copy_from_slice(reader.as_str().as_bytes()),
752                                _ => Bytes::new(),
753                            }
754                        } else {
755                            return Err("no claims on query".into());
756                        }
757                    }
758                    QueryKind::None => Bytes::new(),
759                };
760
761                let span = info_span!("storage");
762
763                match query {
764                    Query::Content(def_idx, field_idx, _kind) => {
765                        let content_obj = span.in_scope(|| {
766                            self.storage
767                                .content
768                                .get_content(*def_idx, *field_idx, &indexed_val)
769                        })?;
770
771                        let root = flexbuffers::Reader::get_root(&content_obj[..])?;
772
773                        let mut fields_builder = vec_builder.start_vector();
774
775                        for include in includes {
776                            include.marry(&mut fields_builder, &root.as_vector())?;
777                        }
778
779                        fields_builder.end_vector();
780                    }
781                    Query::ContentMulti(def_idx) => {
782                        let content_objs =
783                            span.in_scope(|| self.storage.content.list_content(*def_idx))?;
784
785                        let root = flexbuffers::Reader::get_root(&content_objs[..])?;
786
787                        let mut object_vector = vec_builder.start_vector();
788
789                        for object in &root.as_vector() {
790                            let obj_bytes = object.as_blob().0;
791
792                            let object = flexbuffers::Reader::get_root(obj_bytes)?;
793
794                            let mut fields_builder = object_vector.start_vector();
795
796                            for include in includes {
797                                include.marry(&mut fields_builder, &object.as_vector())?;
798                            }
799
800                            fields_builder.end_vector();
801                        }
802
803                        object_vector.end_vector();
804                    }
805                    Query::Model(model_kind, field_idx, _kind, ref_depth, expression) => {
806                        if let Some(exp) = expression {
807                            let items = span.in_scope(|| {
808                                self.storage.model.query_model(
809                                    *model_kind,
810                                    *field_idx,
811                                    exp.as_byte(),
812                                    &indexed_val,
813                                    ref_depth,
814                                    None,
815                                )
816                            })?;
817
818                            let root = flexbuffers::Reader::get_root(&items[..])?;
819
820                            let mut item_builder = vec_builder.start_vector();
821
822                            for item in &root.as_vector() {
823                                let mut fields_builder = item_builder.start_vector();
824
825                                for include in includes {
826                                    include.marry(&mut fields_builder, &item.as_vector())?;
827                                }
828
829                                fields_builder.end_vector();
830                            }
831
832                            item_builder.end_vector();
833                        } else {
834                            let model_item = span.in_scope(|| {
835                                self.storage.model.get_model(
836                                    *model_kind,
837                                    *field_idx,
838                                    &indexed_val,
839                                    ref_depth,
840                                    None,
841                                )
842                            })?;
843
844                            let root = flexbuffers::Reader::get_root(&model_item[..])?;
845
846                            let mut fields_builder = vec_builder.start_vector();
847
848                            for include in includes {
849                                include.marry(&mut fields_builder, &root.as_vector())?;
850                            }
851
852                            fields_builder.end_vector();
853                        }
854                    }
855                }
856            }
857        }
858
859        if let Some(error) = error {
860            let mut error_builder = vec_builder.start_vector();
861
862            error_builder.push(error.0.as_str());
863            error_builder.push(error.1);
864
865            error_builder.end_vector();
866        }
867
868        if let Some(totp) = totp {
869            let mut totp_builder = vec_builder.start_vector();
870
871            totp_builder.push(totp.0.as_str());
872            totp_builder.push(totp.1.as_str());
873
874            let mut recovery_codes_vec = totp_builder.start_vector();
875
876            let mut chunk = String::new();
877
878            for (i, c) in totp.2.chars().enumerate() {
879                if i != 0 && i % 11 == 0 {
880                    recovery_codes_vec.push(chunk.as_str());
881                    chunk = String::new();
882                } else {
883                    chunk = format!("{chunk}{c}");
884                }
885            }
886
887            recovery_codes_vec.end_vector();
888
889            totp_builder.end_vector();
890        }
891
892        vec_builder.end_vector();
893
894        Ok(Bytes::copy_from_slice(builder.view()))
895    }
896
897    #[instrument(skip(self, src), err)]
898    pub fn set_wasm(&self, src: &[u8]) -> Result<(), Box<dyn Error>> {
899        self.storage
900            .artifact
901            .put_artifact(self.idx, ArtifactKind::Template, src)?;
902
903        self.storage
904            .cache
905            .artifact_evict(CacheRead::Template, self.idx)?;
906
907        let mut lock = self.module.write();
908        let module = Module::new(&self.engine, src)?;
909
910        *lock = Some(module);
911        drop(lock);
912
913        Ok(())
914    }
915
916    #[instrument(skip(self, path, params, error, totp, claims), err)]
917    pub fn render(
918        &self,
919        path: String,
920        params: Option<String>,
921        // message and code
922        error: Option<(String, u16)>,
923        // svg qr_code, account and recovery_codes
924        totp: Option<(String, String, String)>,
925        claims: &Option<VectorReader<&[u8]>>,
926    ) -> Result<Bytes, Box<dyn Error>> {
927        let args = self.query(path, params, error, totp, claims)?;
928
929        let mut linker = Linker::new(&self.engine);
930        add_to_linker(&mut linker, |s| s)?;
931
932        let wasi = WasiCtxBuilder::new().build();
933        let mut store = Store::new(&self.engine, wasi);
934
935        let memory_ty = MemoryType::new(1, None);
936        Memory::new(&mut store, memory_ty)?;
937
938        let args = args.to_vec();
939
940        linker.func_wrap(
941            "env",
942            "host_get_input",
943            move |mut caller: Caller<'_, WasiCtx>| -> i64 {
944                let args = args.clone();
945                if let Some(res) = write_wasm_mem(&mut caller, &args) {
946                    return res;
947                }
948
949                0
950            },
951        )?;
952
953        let out = Arc::new(Mutex::new(Bytes::new()));
954        let out_clone = Arc::clone(&out);
955
956        linker.func_wrap(
957            "env",
958            "host_set_output",
959            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i32 {
960                // todo: evaluate whether `zeroed` or `resize` is faster
961                let mut buf = BytesMut::zeroed(len as usize);
962
963                if read_wasm_mem(&mut caller, ptr, &mut buf).is_ok() {
964                    let mut out = out_clone.lock();
965                    *out = buf.into();
966
967                    drop(out);
968
969                    return 1;
970                }
971
972                0
973            },
974        )?;
975
976        let lock = self.module.read();
977
978        if let Some(module) = &*lock {
979            linker.module(&mut store, "", module)?;
980        }
981
982        drop(lock);
983
984        if linker
985            .get_default(&mut store, "")?
986            .typed::<(), ()>(&store)?
987            .call(&mut store, ())
988            .is_err()
989        {
990            return Err("wasm call failed".into());
991        }
992
993        let out = out.lock();
994        Ok(out.clone())
995    }
996}