1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4use crate::artifact::{HostRequirementsRef, ModuleArtifact, ModuleRef, ProcessRef};
5use crate::runtime::{
6 LASH_HOST_REQUIREMENTS_REF_KEY, LASH_MODULE_REF_KEY, LASH_PROCESS_NAME_KEY,
7 LASH_PROCESS_REF_KEY, LASH_PROCESS_VALUE_KEY,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
11pub struct ProcessDefinitionIdentity {
12 pub module_ref: ModuleRef,
13 pub host_requirements_ref: HostRequirementsRef,
14 pub process_ref: ProcessRef,
15 pub process_name: String,
16}
17
18impl ProcessDefinitionIdentity {
19 pub fn new(
20 module_ref: ModuleRef,
21 host_requirements_ref: HostRequirementsRef,
22 process_ref: ProcessRef,
23 process_name: impl Into<String>,
24 ) -> Self {
25 Self {
26 module_ref,
27 host_requirements_ref,
28 process_ref,
29 process_name: process_name.into(),
30 }
31 }
32
33 pub fn from_process_value(
34 value: &serde_json::Value,
35 ) -> Result<Self, ProcessDefinitionIdentityError> {
36 if value
37 .get(LASH_PROCESS_VALUE_KEY)
38 .and_then(serde_json::Value::as_bool)
39 != Some(true)
40 {
41 return Err(ProcessDefinitionIdentityError::NotProcessValue);
42 }
43 Ok(Self {
44 module_ref: decode_field(value, LASH_MODULE_REF_KEY)?,
45 host_requirements_ref: decode_field(value, LASH_HOST_REQUIREMENTS_REF_KEY)?,
46 process_ref: decode_field(value, LASH_PROCESS_REF_KEY)?,
47 process_name: value
48 .get(LASH_PROCESS_NAME_KEY)
49 .and_then(serde_json::Value::as_str)
50 .ok_or(ProcessDefinitionIdentityError::MissingField {
51 field: LASH_PROCESS_NAME_KEY,
52 })?
53 .to_string(),
54 })
55 }
56
57 pub fn to_process_value(&self) -> serde_json::Value {
58 serde_json::json!({
59 LASH_PROCESS_VALUE_KEY: true,
60 LASH_MODULE_REF_KEY: self.module_ref,
61 LASH_HOST_REQUIREMENTS_REF_KEY: self.host_requirements_ref,
62 LASH_PROCESS_REF_KEY: self.process_ref,
63 LASH_PROCESS_NAME_KEY: self.process_name,
64 })
65 }
66
67 pub fn from_artifact_export(artifact: &ModuleArtifact, process_name: &str) -> Option<Self> {
68 let process_ref = artifact.process_ref(process_name)?.clone();
69 Some(Self::new(
70 artifact.module_ref.clone(),
71 artifact.host_requirements_ref.clone(),
72 process_ref,
73 process_name,
74 ))
75 }
76
77 pub fn matches_input_refs(
78 &self,
79 module_ref: &ModuleRef,
80 host_requirements_ref: &HostRequirementsRef,
81 process_ref: &ProcessRef,
82 process_name: &str,
83 ) -> bool {
84 self.module_ref == *module_ref
85 && self.host_requirements_ref == *host_requirements_ref
86 && self.process_ref == *process_ref
87 && self.process_name == process_name
88 }
89
90 pub fn matches_artifact_export(&self, artifact: &ModuleArtifact) -> bool {
91 if self.module_ref != artifact.module_ref
92 || self.host_requirements_ref != artifact.host_requirements_ref
93 {
94 return false;
95 }
96 artifact
97 .process_name_for_ref(&self.process_ref)
98 .is_some_and(|export_name| export_name == self.process_name)
99 }
100}
101
102#[derive(Clone, Debug, PartialEq, Eq, Error)]
103pub enum ProcessDefinitionIdentityError {
104 #[error("definition must be a process definition value")]
105 NotProcessValue,
106 #[error("definition is missing {field}")]
107 MissingField { field: &'static str },
108 #[error("definition has invalid {field}: {message}")]
109 InvalidField {
110 field: &'static str,
111 message: String,
112 },
113}
114
115fn decode_field<T: serde::de::DeserializeOwned>(
116 value: &serde_json::Value,
117 field: &'static str,
118) -> Result<T, ProcessDefinitionIdentityError> {
119 serde_json::from_value(
120 value
121 .get(field)
122 .cloned()
123 .ok_or(ProcessDefinitionIdentityError::MissingField { field })?,
124 )
125 .map_err(|err| ProcessDefinitionIdentityError::InvalidField {
126 field,
127 message: err.to_string(),
128 })
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn process_definition_identity_round_trips_process_value() {
137 let identity = ProcessDefinitionIdentity::new(
138 ModuleRef::new(&crate::ContentHash::new("mod")),
139 HostRequirementsRef::new(&crate::ContentHash::new("host")),
140 ProcessRef::new(crate::ContentHash::new("proc"), 7),
141 "scan",
142 );
143
144 let decoded = ProcessDefinitionIdentity::from_process_value(&identity.to_process_value())
145 .expect("process value should decode");
146
147 assert_eq!(decoded, identity);
148 }
149}