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, 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
30pub 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 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}