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 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 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 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}