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