1use std::collections::HashMap;
2use std::env;
3use std::fmt::Display;
4use std::path::PathBuf;
5
6use commands::{PostConditionEvaluatableInput, PreConditionEvaluatableInput};
7use diagnostics::Diagnostic;
8use dyn_clone::DynClone;
9use hcl_edit::expr::Expression;
10use hcl_edit::structure::Block;
11use serde::de::Error;
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13use sha2::{Digest, Sha256};
14use types::{ObjectDefinition, ObjectProperty, Type};
15use uuid::Uuid;
16
17use crate::helpers::fs::FileLocation;
18
19pub mod block_id;
20pub mod cloud_interface;
21pub mod commands;
22pub mod construct_type;
23pub mod typed_block;
24pub mod diagnostic_types;
25pub mod diagnostics;
26
27pub use diagnostic_types::{DiagnosticLevel, DiagnosticSpan, RelatedLocation};
29
30pub mod embedded_runbooks;
31pub mod frontend;
32pub mod functions;
33pub mod package;
34pub mod signers;
35pub mod stores;
36pub mod types;
37
38pub const CACHED_NONCE: &str = "cached_nonce";
39
40#[cfg(test)]
41mod tests;
42
43#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
44pub struct Did(pub [u8; 32]);
45
46impl Did {
47 pub fn from_components(comps: Vec<impl AsRef<[u8]>>) -> Self {
48 let mut hasher = Sha256::new();
49 for comp in comps {
50 hasher.update(comp);
51 }
52 let hash = hasher.finalize();
53 Did(hash.into())
54 }
55
56 pub fn from_hex_string(source_bytes_str: &str) -> Self {
57 let bytes = hex::decode(source_bytes_str).expect("invalid hex_string");
58 Self::from_bytes(&bytes)
59 }
60
61 pub fn from_bytes(source_bytes: &Vec<u8>) -> Self {
62 let mut bytes = [0u8; 32];
63 bytes.copy_from_slice(&source_bytes);
64 Did(bytes)
65 }
66
67 pub fn zero() -> Self {
68 Self([0u8; 32])
69 }
70
71 pub fn to_string(&self) -> String {
72 hex::encode(self.0)
73 }
74
75 pub fn as_bytes(&self) -> &[u8] {
76 self.0.as_slice()
77 }
78
79 pub fn as_uuid(&self) -> Uuid {
80 Uuid::from_bytes(self.0[0..16].try_into().unwrap())
81 }
82}
83
84impl Serialize for Did {
85 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86 where
87 S: Serializer,
88 {
89 serializer.serialize_str(&format!("0x{}", self))
90 }
91}
92
93impl<'de> Deserialize<'de> for Did {
94 fn deserialize<D>(deserializer: D) -> Result<Did, D::Error>
95 where
96 D: Deserializer<'de>,
97 {
98 let bytes_hex: String = serde::Deserialize::deserialize(deserializer)?;
99 let bytes = hex::decode(&bytes_hex[2..]).map_err(|e| D::Error::custom(e.to_string()))?;
100 Ok(Did::from_bytes(&bytes))
101 }
102}
103
104impl std::fmt::Display for Did {
105 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
106 write!(f, "{}", self.to_string())
107 }
108}
109
110impl std::fmt::Debug for Did {
111 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
112 write!(f, "0x{}", self.to_string())
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Hash)]
117pub struct RunbookDid(pub Did);
118
119impl RunbookDid {
120 pub fn value(&self) -> Did {
121 self.0.clone()
122 }
123
124 pub fn as_bytes(&self) -> &[u8] {
125 self.0.as_bytes()
126 }
127
128 pub fn to_string(&self) -> String {
129 self.0.to_string()
130 }
131}
132
133#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
134pub struct RunbookId {
135 pub org: Option<String>,
137 pub workspace: Option<String>,
139 pub name: String,
141}
142
143impl RunbookId {
144 pub fn new(org: Option<String>, workspace: Option<String>, name: &str) -> RunbookId {
145 RunbookId { org, workspace, name: name.into() }
146 }
147 pub fn did(&self) -> RunbookDid {
148 let mut comps = vec![];
149 if let Some(ref org) = self.org {
150 comps.push(org.as_bytes());
151 }
152 if let Some(ref workspace) = self.workspace {
153 comps.push(workspace.as_bytes());
154 }
155 comps.push(self.name.as_bytes());
156 let did = Did::from_components(comps);
157 RunbookDid(did)
158 }
159
160 pub fn zero() -> RunbookId {
161 RunbookId { org: None, workspace: None, name: "".into() }
162 }
163}
164
165pub struct RunbookInstanceContext {
166 pub runbook_id: RunbookId,
167 pub workspace_location: FileLocation,
168 pub environment_selector: Option<String>,
169}
170
171impl RunbookInstanceContext {
172 pub fn get_workspace_root(&self) -> Result<FileLocation, String> {
173 self.workspace_location.get_parent_location()
174 }
175 pub fn environment_selector<'a>(&'a self, default: &'a str) -> &'a str {
176 self.environment_selector.as_deref().unwrap_or(default)
177 }
178}
179
180#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
181pub struct PackageDid(pub Did);
182
183impl PackageDid {
184 pub fn as_bytes(&self) -> &[u8] {
185 self.0.as_bytes()
186 }
187
188 pub fn to_string(&self) -> String {
189 self.0.to_string()
190 }
191}
192
193#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
194pub struct PackageId {
195 pub runbook_id: RunbookId,
197 pub package_location: FileLocation,
199 pub package_name: String,
201}
202
203impl PackageId {
204 pub fn did(&self) -> PackageDid {
205 let did = Did::from_components(vec![
206 self.runbook_id.did().as_bytes(),
207 self.package_name.to_string().as_bytes(),
208 serde_json::json!(self.package_location).to_string().as_bytes(),
211 ]);
212 PackageDid(did)
213 }
214
215 pub fn zero() -> PackageId {
216 PackageId {
217 runbook_id: RunbookId::zero(),
218 package_location: FileLocation::working_dir(),
219 package_name: "".into(),
220 }
221 }
222 pub fn from_file(
223 location: &FileLocation,
224 runbook_id: &RunbookId,
225 package_name: &str,
226 ) -> Result<Self, Diagnostic> {
227 let package_location = location.get_parent_location().map_err(|e| {
228 Diagnostic::error_from_string(format!("{}", e.to_string())).location(&location)
229 })?;
230 Ok(PackageId {
231 runbook_id: runbook_id.clone(),
232 package_location: package_location.clone(),
233 package_name: package_name.to_string(),
234 })
235 }
236}
237
238#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd)]
239pub struct ConstructDid(pub Did);
240
241impl ConstructDid {
242 pub fn value(&self) -> Did {
243 self.0.clone()
244 }
245
246 pub fn as_bytes(&self) -> &[u8] {
247 self.0.as_bytes()
248 }
249
250 pub fn to_string(&self) -> String {
251 self.0.to_string()
252 }
253
254 pub fn from_hex_string(did_str: &str) -> Self {
255 ConstructDid(Did::from_hex_string(did_str))
256 }
257
258 pub fn as_uuid(&self) -> Uuid {
259 self.0.as_uuid()
260 }
261}
262
263impl Display for ConstructDid {
264 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
265 write!(f, "{}", self.to_string())
266 }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct ConstructId {
271 pub package_id: PackageId,
273 pub construct_location: FileLocation,
275 pub construct_type: construct_type::ConstructType,
277 pub construct_name: String,
279}
280
281impl ConstructId {
282 pub fn did(&self) -> ConstructDid {
283 let did = Did::from_components(vec![
284 self.package_id.did().as_bytes(),
285 self.construct_type.as_ref().as_bytes(), self.construct_name.to_string().as_bytes(),
287 serde_json::json!(self.construct_location).to_string().as_bytes(),
290 ]);
291 ConstructDid(did)
292 }
293
294 pub fn construct_type_str(&self) -> &str {
298 self.construct_type.as_ref()
299 }
300}
301
302#[derive(Debug, Clone)]
303pub struct Construct {
304 pub construct_id: ConstructId,
306}
307
308#[derive(Debug, Clone)]
309pub struct AuthorizationContext {
310 pub workspace_location: FileLocation,
311}
312
313impl AuthorizationContext {
314 pub fn new(workspace_location: FileLocation) -> Self {
315 Self { workspace_location }
316 }
317
318 pub fn empty() -> Self {
319 Self { workspace_location: FileLocation::working_dir() }
320 }
321
322 pub fn get_file_location_from_path_buf(&self, input: &PathBuf) -> Result<FileLocation, String> {
323 let path_str = input.to_string_lossy();
324
325 let loc = if let Some(stripped) = path_str.strip_prefix("~/") {
326 let home = PathBuf::from(get_home_dir());
327 FileLocation::from_path(home.join(stripped))
328 }
329 else if input.is_absolute() {
331 FileLocation::from_path(input.clone())
332 } else {
333 let mut workspace_loc = self
334 .workspace_location
335 .get_parent_location()
336 .map_err(|e| format!("unable to read workspace location: {e}"))?;
337
338 workspace_loc
339 .append_path(&path_str.to_string())
340 .map_err(|e| format!("invalid path: {}", e))?;
341 workspace_loc
342 };
343
344 Ok(loc)
345 }
346}
347
348fn get_home_dir() -> String {
353 if let Ok(real_home) = env::var("SNAP_REAL_HOME") {
354 let path_buf = PathBuf::from(real_home);
355 path_buf.display().to_string()
356 } else {
357 dirs::home_dir().unwrap().display().to_string()
358 }
359}
360
361#[derive(Debug)]
362pub enum ContractSourceTransform {
363 FindAndReplace(String, String),
364 RemapDownstreamDependencies(String, String),
365}
366
367pub struct AddonPostProcessingResult {
368 pub dependencies: HashMap<ConstructDid, Vec<ConstructDid>>,
369 pub transforms: HashMap<ConstructDid, Vec<ContractSourceTransform>>,
370}
371
372impl AddonPostProcessingResult {
373 pub fn new() -> AddonPostProcessingResult {
374 AddonPostProcessingResult { dependencies: HashMap::new(), transforms: HashMap::new() }
375 }
376}
377
378#[derive(Debug, Clone)]
379pub struct AddonInstance {
380 pub addon_id: String,
381 pub package_id: PackageId,
382 pub block: Block,
383}
384
385pub trait WithEvaluatableInputs {
386 fn name(&self) -> String;
387 fn block(&self) -> &Block;
388 fn get_expression_from_input(&self, input_name: &str) -> Option<Expression>;
389 fn get_blocks_for_map(
390 &self,
391 input_name: &str,
392 input_typing: &Type,
393 input_optional: bool,
394 ) -> Result<Option<Vec<Block>>, Vec<Diagnostic>>;
395 fn get_expression_from_block(&self, block: &Block, prop: &ObjectProperty)
396 -> Option<Expression>;
397 fn get_expression_from_object(
398 &self,
399 input_name: &str,
400 input_typing: &Type,
401 ) -> Result<Option<Expression>, Vec<Diagnostic>>;
402 fn get_expression_from_object_property(
403 &self,
404 input_name: &str,
405 prop: &ObjectProperty,
406 ) -> Option<Expression>;
407
408 fn _spec_inputs(&self) -> Vec<Box<dyn EvaluatableInput>>;
410
411 fn spec_inputs(&self) -> Vec<Box<dyn EvaluatableInput>> {
414 let mut spec_inputs = self._spec_inputs();
415 spec_inputs.push(Box::new(PreConditionEvaluatableInput::new()));
416 spec_inputs
417 }
418
419 fn self_referencing_inputs(&self) -> Vec<Box<dyn EvaluatableInput>> {
420 vec![Box::new(PostConditionEvaluatableInput::new())]
421 }
422}
423
424pub trait EvaluatableInput: DynClone {
425 fn documentation(&self) -> String;
426 fn optional(&self) -> bool;
427 fn typing(&self) -> &Type;
428 fn name(&self) -> String;
429 fn as_object(&self) -> Option<&ObjectDefinition> {
430 self.typing().as_object()
431 }
432 fn as_array(&self) -> Option<&Box<Type>> {
433 self.typing().as_array()
434 }
435 fn as_action(&self) -> Option<&String> {
436 self.typing().as_action()
437 }
438 fn as_map(&self) -> Option<&ObjectDefinition> {
439 self.typing().as_map()
440 }
441}
442
443dyn_clone::clone_trait_object!(EvaluatableInput);