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