Skip to main content

lashlang/
identity.rs

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}