panopticon_core/operation/
registry.rs1use crate::imports::*;
2
3#[derive(Default)]
16pub struct Registry {
17 entries: HashMap<std::any::TypeId, OperationEntry>,
18}
19
20pub(crate) struct OperationEntry {
21 pub metadata: OperationMetadata,
22 pub factory: fn(&mut Context) -> Result<(), OperationError>,
23}
24
25impl OperationEntry {
26 pub fn execute(&self, context: &mut Context) -> Result<(), OperationError> {
27 (self.factory)(context)
28 }
29}
30
31impl Registry {
32 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub fn register<O: Operation + 'static>(&mut self) -> Result<(), DraftError> {
44 let id = std::any::TypeId::of::<O>();
45 if self.entries.contains_key(&id) {
46 return Ok(());
47 }
48 let metadata = O::metadata();
49 Self::validate_metadata(&metadata)?;
50 self.entries.insert(
51 id,
52 OperationEntry {
53 metadata,
54 factory: |context| O::execute(context),
55 },
56 );
57 Ok(())
58 }
59
60 fn validate_metadata(metadata: &OperationMetadata) -> Result<(), DraftError> {
61 for output in metadata.outputs {
62 Self::validate_derived_name(metadata, &output.name)?;
63 }
64 for ext in metadata.requires_extensions {
65 Self::validate_derived_name(metadata, &ext.name)?;
66 }
67 Ok(())
68 }
69
70 fn validate_derived_name(
71 metadata: &OperationMetadata,
72 name: &NameSpec,
73 ) -> Result<(), DraftError> {
74 if let NameSpec::DerivedFrom(input_name)
75 | NameSpec::DerivedWithDefault { input_name, .. } = name
76 {
77 let input = metadata.inputs.iter().find(|i| i.name == *input_name);
78 match input {
79 None => {
80 return Err(DraftError::InvalidMetadata {
81 operation: metadata.name,
82 reason: format!(
83 "DerivedFrom('{}') references a non-existent input",
84 input_name
85 ),
86 });
87 }
88 Some(spec) if spec.ty != Type::Text => {
89 return Err(DraftError::InvalidMetadata {
90 operation: metadata.name,
91 reason: format!(
92 "DerivedFrom('{}') requires input type Text, found {}",
93 input_name, spec.ty
94 ),
95 });
96 }
97 _ => {}
98 }
99 }
100 if let NameSpec::DerivedWithDefault { input_name, .. } = name {
101 let input = metadata.inputs.iter().find(|i| i.name == *input_name);
102 if let Some(input) = input
103 && input.required
104 {
105 return Err(DraftError::InvalidMetadata {
106 operation: metadata.name,
107 reason: format!(
108 "DerivedWithDefault('{}') requires an optional input, but '{}' is required",
109 input_name, input_name
110 ),
111 });
112 }
113 }
114 Ok(())
115 }
116
117 pub(crate) fn get(&self, id: &std::any::TypeId) -> Option<&OperationEntry> {
118 self.entries.get(id)
119 }
120
121 pub(crate) fn merge(&mut self, other: Registry) {
122 for (id, entry) in other.entries {
123 self.entries.entry(id).or_insert(entry);
124 }
125 }
126}