1use 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
30pub 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 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}