Skip to main content

lashlang/runtime/
value.rs

1//! Value types: the dynamically-typed `Value` enum, its projection wrapper,
2//! the `ImageValue` attachment descriptor, and the public projection traits
3//! (`ProjectedHostDescriptor`, `ProjectedReadRequest`, `ProjectedFuture`).
4//!
5//! The `Value` enum is the universal currency of the lashlang runtime: every
6//! load, every binary op, every host-tool argument, every JSON round-trip
7//! flows through it. `ProjectedValue` wraps host-side bindings the runtime
8//! can read but should not own; field/index access on a projected source
9//! propagates the wrapper so downstream consumers can tell that this came
10//! from a projected binding.
11
12use std::fmt;
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16
17use compact_str::CompactString;
18use rustc_hash::FxHashMap;
19use serde::ser::SerializeMap;
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21
22use super::record::{Symbol, intern_symbol, symbol_name};
23use super::{
24    Name, Record, RuntimeError, RuntimeJson, append_tuple_literal_direct, execute_contains_direct,
25    from_json, is_truthy as value_truthy, materialize_projected_async, read_field_ref_direct,
26    read_index_ref_direct, stringify_value_async, value_contains_projected, value_len,
27    value_type_name, write_number,
28};
29
30/// Marker key that wraps a Type literal at its outermost level so a host-side
31/// consumer can tell a Type value apart from a plain record. The inner value
32/// is the JSON-Schema representation of the type.
33pub const LASH_TYPE_KEY: &str = "$lash_type";
34pub const LASH_HOST_DESCRIPTOR_TYPE_KEY: &str = "$lash_host_descriptor_type";
35pub const LASH_HOST_DESCRIPTOR_VALUE_KEY: &str = "$lash_host_descriptor_value";
36pub const LASH_PROCESS_VALUE_KEY: &str = "$lash_process";
37pub const LASH_PROCESS_NAME_KEY: &str = "process_name";
38pub const LASH_MODULE_REF_KEY: &str = "module_ref";
39pub const LASH_PROCESS_REF_KEY: &str = "process_ref";
40pub const LASH_HOST_REQUIREMENTS_REF_KEY: &str = "host_requirements_ref";
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct ListValue {
44    values: Arc<Vec<Value>>,
45}
46
47impl ListValue {
48    pub fn into_vec(self) -> Vec<Value> {
49        match Arc::try_unwrap(self.values) {
50            Ok(values) => values,
51            Err(values) => values.as_ref().clone(),
52        }
53    }
54
55    pub(crate) fn make_mut(&mut self) -> &mut Vec<Value> {
56        Arc::make_mut(&mut self.values)
57    }
58}
59
60impl std::ops::Deref for ListValue {
61    type Target = [Value];
62
63    fn deref(&self) -> &Self::Target {
64        self.values.as_slice()
65    }
66}
67
68impl From<Vec<Value>> for ListValue {
69    fn from(values: Vec<Value>) -> Self {
70        Self {
71            values: Arc::new(values),
72        }
73    }
74}
75
76impl FromIterator<Value> for ListValue {
77    fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
78        iter.into_iter().collect::<Vec<_>>().into()
79    }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct ImageValue {
84    pub id: String,
85    pub mime: String,
86    pub label: String,
87    pub size: u64,
88    pub width: Option<u32>,
89    pub height: Option<u32>,
90}
91
92impl ImageValue {
93    pub fn new(
94        id: impl Into<String>,
95        mime: impl Into<String>,
96        label: impl Into<String>,
97        size: u64,
98        width: Option<u32>,
99        height: Option<u32>,
100    ) -> Self {
101        Self {
102            id: id.into(),
103            mime: mime.into(),
104            label: label.into(),
105            size,
106            width,
107            height,
108        }
109    }
110}
111
112impl Serialize for ImageValue {
113    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
114    where
115        S: Serializer,
116    {
117        let mut map = serializer.serialize_map(Some(7))?;
118        map.serialize_entry("type", "image")?;
119        map.serialize_entry("id", &self.id)?;
120        map.serialize_entry("mime", &self.mime)?;
121        map.serialize_entry("label", &self.label)?;
122        map.serialize_entry("size", &self.size)?;
123        map.serialize_entry("width", &self.width)?;
124        map.serialize_entry("height", &self.height)?;
125        map.end()
126    }
127}
128
129impl<'de> Deserialize<'de> for ImageValue {
130    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131    where
132        D: Deserializer<'de>,
133    {
134        #[derive(Deserialize)]
135        struct ImageDescriptor {
136            #[serde(rename = "type")]
137            kind: String,
138            id: String,
139            mime: String,
140            label: String,
141            size: u64,
142            #[serde(default)]
143            width: Option<u32>,
144            #[serde(default)]
145            height: Option<u32>,
146        }
147
148        let descriptor = ImageDescriptor::deserialize(deserializer)?;
149        if descriptor.kind != "image" {
150            return Err(serde::de::Error::custom("expected image descriptor"));
151        }
152        Ok(Self {
153            id: descriptor.id,
154            mime: descriptor.mime,
155            label: descriptor.label,
156            size: descriptor.size,
157            width: descriptor.width,
158            height: descriptor.height,
159        })
160    }
161}
162
163#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
164pub struct ResourceHandle {
165    pub resource_type: String,
166    pub alias: String,
167}
168
169impl ResourceHandle {
170    pub fn new(resource_type: impl Into<String>, alias: impl Into<String>) -> Self {
171        Self {
172            resource_type: resource_type.into(),
173            alias: alias.into(),
174        }
175    }
176}
177
178#[derive(Clone, Debug)]
179pub enum Value {
180    Null,
181    Bool(bool),
182    Number(f64),
183    String(CompactString),
184    // Boxed: `ImageValue` is by far the largest variant, and images are rare in
185    // value streams. Storing it inline would inflate `size_of::<Value>()` (and
186    // therefore every `Vec<Value>`/record allocation) for the common case, so we
187    // keep the payload behind a pointer.
188    Image(Box<ImageValue>),
189    Resource(ResourceHandle),
190    Tuple(ListValue),
191    List(ListValue),
192    Record(Arc<Record>),
193    Projected(ProjectedValue),
194}
195
196impl Value {
197    pub fn as_record(&self) -> Option<&Record> {
198        match self {
199            Self::Record(record) => Some(record.as_ref()),
200            _ => None,
201        }
202    }
203
204    pub fn contains_projected(&self) -> bool {
205        value_contains_projected(self)
206    }
207}
208
209impl PartialEq for Value {
210    fn eq(&self, other: &Self) -> bool {
211        match (self, other) {
212            (Self::Null, Self::Null) => true,
213            (Self::Bool(left), Self::Bool(right)) => left == right,
214            (Self::Number(left), Self::Number(right)) => left == right,
215            (Self::String(left), Self::String(right)) => left == right,
216            (Self::Image(left), Self::Image(right)) => left == right,
217            (Self::Resource(left), Self::Resource(right)) => left == right,
218            (Self::Tuple(left), Self::Tuple(right)) => left == right,
219            (Self::List(left), Self::List(right)) => left == right,
220            (Self::Record(left), Self::Record(right)) => left == right,
221            (Self::Projected(left), Self::Projected(right)) => left == right,
222            (Self::Projected(left), right) => left.materialize() == *right,
223            (left, Self::Projected(right)) => *left == right.materialize(),
224            _ => false,
225        }
226    }
227}
228
229impl Serialize for Value {
230    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231    where
232        S: Serializer,
233    {
234        RuntimeJson(self).serialize(serializer)
235    }
236}
237
238impl<'de> Deserialize<'de> for Value {
239    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240    where
241        D: Deserializer<'de>,
242    {
243        serde_json::Value::deserialize(deserializer).map(from_json)
244    }
245}
246
247#[derive(Clone, Default)]
248pub struct ProjectedBindings {
249    bindings: FxHashMap<Symbol, ProjectedValue>,
250}
251
252#[derive(Clone, Debug, PartialEq, Eq)]
253pub struct ProjectedBindingError {
254    name: String,
255}
256
257impl ProjectedBindingError {
258    pub fn duplicate(name: impl Into<String>) -> Self {
259        Self { name: name.into() }
260    }
261
262    pub fn name(&self) -> &str {
263        &self.name
264    }
265}
266
267impl std::fmt::Display for ProjectedBindingError {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        write!(f, "projected binding `{}` is already bound", self.name)
270    }
271}
272
273impl std::error::Error for ProjectedBindingError {}
274
275impl ProjectedBindings {
276    pub fn new() -> Self {
277        Self::default()
278    }
279
280    pub fn insert(&mut self, name: impl Into<String>, value: ProjectedValue) {
281        let name = name.into();
282        self.try_insert(name, value)
283            .expect("projected binding should not be inserted twice");
284    }
285
286    pub fn try_insert(
287        &mut self,
288        name: impl Into<String>,
289        value: ProjectedValue,
290    ) -> Result<(), ProjectedBindingError> {
291        let name = name.into();
292        let symbol = intern_symbol(&name);
293        if self.bindings.contains_key(&symbol) {
294            return Err(ProjectedBindingError::duplicate(name));
295        }
296        self.bindings.insert(intern_symbol(&name), value);
297        Ok(())
298    }
299
300    pub(crate) fn get_symbol(&self, symbol: Symbol) -> Option<ProjectedValue> {
301        self.bindings.get(&symbol).cloned()
302    }
303
304    pub fn get(&self, name: &str) -> Option<ProjectedValue> {
305        self.bindings.get(&intern_symbol(name)).cloned()
306    }
307
308    pub fn names(&self) -> impl Iterator<Item = String> + '_ {
309        self.bindings
310            .keys()
311            .map(|symbol| symbol_name(*symbol).to_string())
312    }
313}
314
315#[derive(Clone)]
316pub struct ProjectedValue {
317    name: Arc<str>,
318    kind: ProjectedKind,
319    projection_ref: Option<serde_json::Value>,
320}
321
322#[derive(Clone)]
323enum ProjectedKind {
324    Scalar(Arc<Value>),
325    Custom(Arc<dyn ProjectedHostDescriptor>),
326}
327
328pub type ProjectedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
329
330#[derive(Clone, Debug, PartialEq)]
331pub enum ProjectedReadRequest {
332    Len,
333    Empty,
334    Truthy,
335    Field(Arc<str>),
336    Index(Value),
337    Contains(Value),
338    Find {
339        needle: Value,
340        start: usize,
341    },
342    GrepText(Value),
343    Keys,
344    Values,
345    StartsWith(Value),
346    EndsWith(Value),
347    Split(Value),
348    Join(Value),
349    Trim,
350    Slice {
351        start: Option<isize>,
352        end: Option<isize>,
353    },
354    Push(Value),
355    ToNumber,
356    JsonParse,
357    SliceBound,
358    RangeBound,
359    Render,
360    Materialize,
361}
362
363#[derive(Clone, Debug, PartialEq)]
364pub enum ProjectedReadResponse {
365    Missing,
366    Value(Value),
367    Text(String),
368    Bool(bool),
369    Len(usize),
370    Keys(Vec<String>),
371}
372
373pub trait ProjectedHostDescriptor: Send + Sync {
374    fn type_name(&self) -> &str;
375
376    fn read_one(
377        &self,
378        _request: ProjectedReadRequest,
379    ) -> ProjectedFuture<'_, ProjectedReadResponse> {
380        Box::pin(async { ProjectedReadResponse::Missing })
381    }
382
383    fn read_many(
384        &self,
385        requests: Vec<ProjectedReadRequest>,
386    ) -> ProjectedFuture<'_, Vec<ProjectedReadResponse>> {
387        Box::pin(async move {
388            let mut responses = Vec::with_capacity(requests.len());
389            for request in requests {
390                responses.push(self.read_one(request).await);
391            }
392            responses
393        })
394    }
395}
396
397impl ProjectedValue {
398    pub fn scalar(name: impl Into<Arc<str>>, value: Value) -> Self {
399        Self {
400            name: name.into(),
401            kind: ProjectedKind::Scalar(Arc::new(value)),
402            projection_ref: None,
403        }
404    }
405
406    pub fn custom(name: impl Into<Arc<str>>, value: Arc<dyn ProjectedHostDescriptor>) -> Self {
407        Self::custom_inner(name, value, None)
408    }
409
410    pub fn custom_with_projection_ref(
411        name: impl Into<Arc<str>>,
412        value: Arc<dyn ProjectedHostDescriptor>,
413        projection_ref: serde_json::Value,
414    ) -> Self {
415        Self::custom_inner(name, value, Some(projection_ref))
416    }
417
418    fn custom_inner(
419        name: impl Into<Arc<str>>,
420        value: Arc<dyn ProjectedHostDescriptor>,
421        projection_ref: Option<serde_json::Value>,
422    ) -> Self {
423        Self {
424            name: name.into(),
425            kind: ProjectedKind::Custom(value),
426            projection_ref,
427        }
428    }
429
430    pub(crate) fn unavailable_after_restore_with_projection_ref(
431        name: impl Into<Arc<str>>,
432        type_name: impl Into<Arc<str>>,
433        projection_ref: Option<serde_json::Value>,
434    ) -> Self {
435        let name = name.into();
436        Self {
437            name: name.clone(),
438            kind: ProjectedKind::Custom(Arc::new(UnavailableProjectedValue::new(name, type_name))),
439            projection_ref,
440        }
441    }
442
443    pub fn name(&self) -> &str {
444        &self.name
445    }
446
447    pub fn projection_ref(&self) -> Option<&serde_json::Value> {
448        self.projection_ref.as_ref()
449    }
450
451    /// Wrap a derived value as a `ProjectedValue` carrying a path-extended name
452    /// (e.g. `parent.field`). Pass-through if the inner value is already a
453    /// `Value::Projected` so we never double-wrap. Used by field/index access on
454    /// projected sources to keep "this came from a projected source" alive
455    /// across path expressions; non-path operations (binary ops, builtins,
456    /// formatters) auto-strip via their existing materialise-and-evaluate code
457    /// paths and so naturally lose the wrapper.
458    pub fn propagate_field(parent_name: &str, field: &str, inner: Value) -> Value {
459        match inner {
460            Value::Projected(_) => inner,
461            other => Value::Projected(ProjectedValue::scalar(
462                Arc::<str>::from(format!("{parent_name}.{field}")),
463                other,
464            )),
465        }
466    }
467
468    pub fn propagate_index(parent_name: &str, index: &Value, inner: Value) -> Value {
469        match inner {
470            Value::Projected(_) => inner,
471            other => {
472                let suffix = match index {
473                    Value::String(s) => format!("[{s:?}]"),
474                    Value::Number(n) => format!("[{n}]"),
475                    other => format!("[{other}]"),
476                };
477                Value::Projected(ProjectedValue::scalar(
478                    Arc::<str>::from(format!("{parent_name}{suffix}")),
479                    other,
480                ))
481            }
482        }
483    }
484
485    pub(crate) async fn len(&self) -> usize {
486        match &self.kind {
487            ProjectedKind::Scalar(value) => value_len(value).unwrap_or(0),
488            ProjectedKind::Custom(value) => match value.read_one(ProjectedReadRequest::Len).await {
489                ProjectedReadResponse::Len(value) => value,
490                ProjectedReadResponse::Value(value) => value_len(&value).unwrap_or(0),
491                ProjectedReadResponse::Missing
492                | ProjectedReadResponse::Text(_)
493                | ProjectedReadResponse::Bool(_)
494                | ProjectedReadResponse::Keys(_) => 0,
495            },
496        }
497    }
498
499    pub(crate) async fn empty(&self) -> Option<bool> {
500        match &self.kind {
501            ProjectedKind::Scalar(value) => value_len(value).map(|len| len == 0),
502            ProjectedKind::Custom(value) => match value.read_one(ProjectedReadRequest::Empty).await
503            {
504                ProjectedReadResponse::Bool(value) => Some(value),
505                ProjectedReadResponse::Value(Value::Bool(value)) => Some(value),
506                ProjectedReadResponse::Value(value) => Some(value_truthy(&value)),
507                ProjectedReadResponse::Missing
508                | ProjectedReadResponse::Text(_)
509                | ProjectedReadResponse::Len(_)
510                | ProjectedReadResponse::Keys(_) => None,
511            },
512        }
513    }
514
515    pub(crate) async fn truthy(&self) -> bool {
516        match &self.kind {
517            ProjectedKind::Scalar(value) => value_truthy(value),
518            ProjectedKind::Custom(value) => {
519                match value.read_one(ProjectedReadRequest::Truthy).await {
520                    ProjectedReadResponse::Bool(value) => value,
521                    ProjectedReadResponse::Value(value) => value_truthy(&value),
522                    _ => false,
523                }
524            }
525        }
526    }
527
528    pub(crate) async fn get_index(&self, index: &Value) -> Result<Value, RuntimeError> {
529        let index = materialize_projected_async(index.clone()).await;
530        match &self.kind {
531            ProjectedKind::Scalar(value) => read_index_ref_direct(value, &index),
532            ProjectedKind::Custom(value) => {
533                match value.read_one(ProjectedReadRequest::Index(index)).await {
534                    ProjectedReadResponse::Value(value) => Ok(value),
535                    _ => Ok(Value::Null),
536                }
537            }
538        }
539    }
540
541    pub(crate) async fn get_field(&self, field: &Name) -> Result<Value, RuntimeError> {
542        match &self.kind {
543            ProjectedKind::Scalar(value) => read_field_ref_direct(value, field),
544            ProjectedKind::Custom(value) => match value
545                .read_one(ProjectedReadRequest::Field(field.text.clone()))
546                .await
547            {
548                ProjectedReadResponse::Value(value) => Ok(value),
549                _ => Ok(Value::Null),
550            },
551        }
552    }
553
554    pub(crate) async fn contains(&self, needle: &Value) -> Result<bool, RuntimeError> {
555        match &self.kind {
556            ProjectedKind::Scalar(value) => execute_contains_direct(value, needle),
557            ProjectedKind::Custom(value) => Ok(
558                match value
559                    .read_one(ProjectedReadRequest::Contains(needle.clone()))
560                    .await
561                {
562                    ProjectedReadResponse::Bool(value) => value,
563                    ProjectedReadResponse::Value(value) => value_truthy(&value),
564                    _ => false,
565                },
566            ),
567        }
568    }
569
570    pub(crate) async fn find(&self, needle: Value, start: usize) -> Option<Value> {
571        self.custom_read_or_missing(ProjectedReadRequest::Find { needle, start })
572            .await
573    }
574
575    pub(crate) async fn grep_text(&self, needle: Value) -> Option<Value> {
576        self.custom_read_or_missing(ProjectedReadRequest::GrepText(needle))
577            .await
578    }
579
580    pub(crate) async fn keys(&self) -> Vec<String> {
581        match &self.kind {
582            ProjectedKind::Scalar(value) => match value.as_ref() {
583                Value::Record(record) => record.keys().map(ToString::to_string).collect(),
584                _ => Vec::new(),
585            },
586            ProjectedKind::Custom(value) => {
587                match value.read_one(ProjectedReadRequest::Keys).await {
588                    ProjectedReadResponse::Keys(value) => value,
589                    ProjectedReadResponse::Value(Value::List(values)) => values
590                        .iter()
591                        .filter_map(|value| match value {
592                            Value::String(value) => Some(value.to_string()),
593                            _ => None,
594                        })
595                        .collect(),
596                    _ => Vec::new(),
597                }
598            }
599        }
600    }
601
602    pub(crate) async fn values(&self) -> Option<Value> {
603        match &self.kind {
604            ProjectedKind::Scalar(value) => match value.as_ref() {
605                Value::Record(record) => Some(Value::List(
606                    record.values().cloned().collect::<Vec<_>>().into(),
607                )),
608                Value::Null => Some(Value::List(Vec::new().into())),
609                _ => None,
610            },
611            ProjectedKind::Custom(_) => {
612                self.custom_read_or_missing(ProjectedReadRequest::Values)
613                    .await
614            }
615        }
616    }
617
618    pub(crate) async fn starts_with(&self, prefix: Value) -> Option<Value> {
619        self.custom_read_or_missing(ProjectedReadRequest::StartsWith(prefix))
620            .await
621    }
622
623    pub(crate) async fn ends_with(&self, suffix: Value) -> Option<Value> {
624        self.custom_read_or_missing(ProjectedReadRequest::EndsWith(suffix))
625            .await
626    }
627
628    pub(crate) async fn split(&self, needle: Value) -> Option<Value> {
629        self.custom_read_or_missing(ProjectedReadRequest::Split(needle))
630            .await
631    }
632
633    pub(crate) async fn join(&self, sep: Value) -> Option<Value> {
634        self.custom_read_or_missing(ProjectedReadRequest::Join(sep))
635            .await
636    }
637
638    pub(crate) async fn trim(&self) -> Option<Value> {
639        self.custom_read_or_missing(ProjectedReadRequest::Trim)
640            .await
641    }
642
643    pub(crate) async fn slice(&self, start: Option<isize>, end: Option<isize>) -> Option<Value> {
644        self.custom_read_or_missing(ProjectedReadRequest::Slice { start, end })
645            .await
646    }
647
648    pub(crate) async fn push(&self, item: Value) -> Option<Value> {
649        self.custom_read_or_missing(ProjectedReadRequest::Push(item))
650            .await
651    }
652
653    pub(crate) async fn to_number(&self) -> Option<Value> {
654        self.custom_read_or_missing(ProjectedReadRequest::ToNumber)
655            .await
656    }
657
658    pub(crate) async fn json_parse(&self) -> Option<Value> {
659        self.custom_read_or_missing(ProjectedReadRequest::JsonParse)
660            .await
661    }
662
663    pub(crate) async fn slice_bound(&self) -> Option<Value> {
664        self.custom_read_or_missing(ProjectedReadRequest::SliceBound)
665            .await
666    }
667
668    pub(crate) async fn range_bound(&self) -> Option<Value> {
669        self.custom_read_or_missing(ProjectedReadRequest::RangeBound)
670            .await
671    }
672
673    async fn custom_read_or_missing(&self, request: ProjectedReadRequest) -> Option<Value> {
674        match &self.kind {
675            ProjectedKind::Scalar(_) => None,
676            ProjectedKind::Custom(value) => match value.read_one(request).await {
677                ProjectedReadResponse::Value(value) => Some(value),
678                ProjectedReadResponse::Bool(value) => Some(Value::Bool(value)),
679                ProjectedReadResponse::Len(value) => Some(Value::Number(value as f64)),
680                ProjectedReadResponse::Text(value) => Some(Value::String(value.into())),
681                ProjectedReadResponse::Keys(values) => Some(Value::List(
682                    values
683                        .into_iter()
684                        .map(|value| Value::String(value.into()))
685                        .collect::<Vec<_>>()
686                        .into(),
687                )),
688                ProjectedReadResponse::Missing => None,
689            },
690        }
691    }
692
693    pub async fn render(&self) -> String {
694        match &self.kind {
695            ProjectedKind::Scalar(value) => stringify_value_async(value).await.unwrap_or_default(),
696            ProjectedKind::Custom(value) => {
697                match value.read_one(ProjectedReadRequest::Render).await {
698                    ProjectedReadResponse::Text(value) => value,
699                    ProjectedReadResponse::Value(value) => {
700                        stringify_value_async(&value).await.unwrap_or_default()
701                    }
702                    _ => String::new(),
703                }
704            }
705        }
706    }
707
708    pub async fn materialize_async(&self) -> Value {
709        match &self.kind {
710            ProjectedKind::Scalar(value) => (**value).clone(),
711            ProjectedKind::Custom(value) => {
712                match value.read_one(ProjectedReadRequest::Materialize).await {
713                    ProjectedReadResponse::Value(value) => value,
714                    ProjectedReadResponse::Text(value) => Value::String(value.into()),
715                    ProjectedReadResponse::Bool(value) => Value::Bool(value),
716                    ProjectedReadResponse::Len(value) => Value::Number(value as f64),
717                    ProjectedReadResponse::Keys(values) => Value::List(
718                        values
719                            .into_iter()
720                            .map(|value| Value::String(value.into()))
721                            .collect::<Vec<_>>()
722                            .into(),
723                    ),
724                    ProjectedReadResponse::Missing => Value::Null,
725                }
726            }
727        }
728    }
729
730    pub fn materialize(&self) -> Value {
731        futures_executor::block_on(self.materialize_async())
732    }
733}
734
735struct UnavailableProjectedValue {
736    name: Arc<str>,
737    type_name: Arc<str>,
738}
739
740impl UnavailableProjectedValue {
741    fn new(name: Arc<str>, type_name: impl Into<Arc<str>>) -> Self {
742        Self {
743            name,
744            type_name: type_name.into(),
745        }
746    }
747
748    fn message(&self) -> String {
749        format!(
750            "projected host descriptor `{}` ({}) is unavailable after snapshot restore; rerun the producing tool to recreate it",
751            self.name, self.type_name
752        )
753    }
754}
755
756impl ProjectedHostDescriptor for UnavailableProjectedValue {
757    fn type_name(&self) -> &str {
758        &self.type_name
759    }
760
761    fn read_one(
762        &self,
763        request: ProjectedReadRequest,
764    ) -> ProjectedFuture<'_, ProjectedReadResponse> {
765        Box::pin(async move {
766            match request {
767                ProjectedReadRequest::Render => ProjectedReadResponse::Text(self.message()),
768                ProjectedReadRequest::Materialize => {
769                    ProjectedReadResponse::Value(Value::String(self.message().into()))
770                }
771                _ => ProjectedReadResponse::Missing,
772            }
773        })
774    }
775}
776
777impl fmt::Debug for ProjectedValue {
778    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
779        f.debug_struct("ProjectedValue")
780            .field("name", &self.name)
781            .field("kind", &self.value_type_name())
782            .finish()
783    }
784}
785
786impl PartialEq for ProjectedValue {
787    fn eq(&self, other: &Self) -> bool {
788        self.materialize() == other.materialize()
789    }
790}
791
792impl ProjectedValue {
793    pub(crate) fn value_type_name(&self) -> &str {
794        match &self.kind {
795            ProjectedKind::Scalar(value) => value_type_name(value),
796            ProjectedKind::Custom(value) => value.type_name(),
797        }
798    }
799}
800
801impl fmt::Display for Value {
802    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803        match self {
804            Self::Null => write!(f, "null"),
805            Self::Bool(value) => write!(f, "{value}"),
806            Self::Number(value) => write_number(f, *value),
807            Self::String(value) => write!(f, "{value}"),
808            Self::Tuple(values) => {
809                let mut output = String::new();
810                append_tuple_literal_direct(&mut output, values).map_err(|_| fmt::Error)?;
811                write!(f, "{output}")
812            }
813            Self::Image(_)
814            | Self::Resource(_)
815            | Self::List(_)
816            | Self::Record(_)
817            | Self::Projected(_) => write!(
818                f,
819                "{}",
820                serde_json::to_string(&RuntimeJson(self)).unwrap_or_default()
821            ),
822        }
823    }
824}
825
826#[cfg(test)]
827mod tests {
828    use super::*;
829
830    fn projected(name: &str) -> Value {
831        Value::Projected(ProjectedValue::scalar(name, Value::String("host".into())))
832    }
833
834    #[test]
835    fn contains_projected_returns_true_for_direct_projected_values() {
836        assert!(projected("input").contains_projected());
837    }
838
839    #[test]
840    fn contains_projected_returns_true_for_nested_projected_values() {
841        let mut record = Record::new();
842        record.insert("title".to_string(), Value::String("local".into()));
843        record.insert(
844            "items".to_string(),
845            Value::List(vec![Value::Number(1.0), projected("input.items")].into()),
846        );
847
848        assert!(Value::Record(Arc::new(record)).contains_projected());
849    }
850
851    #[test]
852    fn contains_projected_returns_false_for_ordinary_values() {
853        let mut record = Record::new();
854        record.insert("ok".to_string(), Value::Bool(true));
855        record.insert(
856            "items".to_string(),
857            Value::List(
858                vec![
859                    Value::Null,
860                    Value::Number(2.0),
861                    Value::String("plain".into()),
862                ]
863                .into(),
864            ),
865        );
866
867        assert!(!Value::Record(Arc::new(record)).contains_projected());
868    }
869}