1use crate::types::common::*;
6use crate::{ContextSpanned, Error};
7pub use cm_types::{
8 Availability, BorrowedName, BoundedName, DeliveryType, DependencyType, HandleType, Name,
9 OnTerminate, ParseError, Path, RelativePath, StartupMode, StorageId, Url,
10};
11use serde::{Serialize, de};
12use serde_json::Value;
13
14use std::fmt;
15use std::path::PathBuf;
16use std::sync::Arc;
17
18use indexmap::IndexMap;
19
20#[derive(Debug, PartialEq, Default, Serialize)]
21pub struct Program {
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub runner: Option<Name>,
24 #[serde(flatten)]
25 pub info: IndexMap<String, Value>,
26}
27
28impl<'de> de::Deserialize<'de> for Program {
29 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30 where
31 D: de::Deserializer<'de>,
32 {
33 struct Visitor;
34
35 const EXPECTED_PROGRAM: &'static str =
36 "a JSON object that includes a `runner` string property";
37 const EXPECTED_RUNNER: &'static str = "a non-empty `runner` string property no more than 255 characters in length \
38 that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]";
39
40 impl<'de> de::Visitor<'de> for Visitor {
41 type Value = Program;
42
43 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str(EXPECTED_PROGRAM)
45 }
46
47 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
48 where
49 A: de::MapAccess<'de>,
50 {
51 let mut info = IndexMap::new();
52 let mut runner = None;
53 while let Some(e) = map.next_entry::<String, Value>()? {
54 let (k, v) = e;
55 if &k == "runner" {
56 if let Value::String(s) = v {
57 runner = Some(s);
58 } else {
59 return Err(de::Error::invalid_value(
60 de::Unexpected::Map,
61 &EXPECTED_RUNNER,
62 ));
63 }
64 } else {
65 info.insert(k, v);
66 }
67 }
68 let runner = runner
69 .map(|r| {
70 Name::new(r.clone()).map_err(|e| match e {
71 ParseError::InvalidValue => de::Error::invalid_value(
72 serde::de::Unexpected::Str(&r),
73 &EXPECTED_RUNNER,
74 ),
75 ParseError::TooLong | ParseError::Empty => {
76 de::Error::invalid_length(r.len(), &EXPECTED_RUNNER)
77 }
78 _ => {
79 panic!("unexpected parse error: {:?}", e);
80 }
81 })
82 })
83 .transpose()?;
84 Ok(Program { runner, info })
85 }
86 }
87
88 deserializer.deserialize_map(Visitor)
89 }
90}
91
92impl Hydrate for Program {
93 type Output = ContextProgram;
94
95 fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
96 let runner = self.runner.map(|raw_name| {
97 let validated_name = Name::new(raw_name.clone()).map_err(|e| {
98 let msg = match e {
99 ParseError::InvalidValue => format!(
100 "Runner name '{}' contains invalid characters. Expected [A-Za-z0-9_.-] starting with [A-Za-z0-9_].",
101 raw_name
102 ),
103 ParseError::TooLong | ParseError::Empty => {
104 format!("Runner name must be between 1 and 255 characters long.")
105 }
106 _ => {
107 panic!("unexpected parse error: {:?}", e);
108 }
109 };
110
111 Error::merge(msg, Some(file.clone()))
112 })?;
113 Ok::<ContextSpanned<BoundedName<255>>, Error>(ContextSpanned {
114 value: validated_name,
115 origin: file.clone(),
116 })
117 }).transpose()?;
118
119 Ok(ContextProgram { runner, info: self.info })
120 }
121}
122
123#[derive(Debug, PartialEq, Serialize, Default, Clone)]
124pub struct ContextProgram {
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub runner: Option<ContextSpanned<Name>>,
127 #[serde(flatten)]
128 pub info: IndexMap<String, serde_json::Value>,
129}