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