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