coil_wasm/manifest/
config.rs1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Copy)]
4pub enum ExtensionConfigValueType {
5 String,
6 Integer,
7 Boolean,
8}
9
10impl std::fmt::Display for ExtensionConfigValueType {
11 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 match self {
13 Self::String => f.write_str("string"),
14 Self::Integer => f.write_str("integer"),
15 Self::Boolean => f.write_str("boolean"),
16 }
17 }
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum ExtensionConfigValue {
22 String(String),
23 Integer(i64),
24 Boolean(bool),
25}
26
27impl ExtensionConfigValue {
28 pub fn value_type(&self) -> ExtensionConfigValueType {
29 match self {
30 Self::String(_) => ExtensionConfigValueType::String,
31 Self::Integer(_) => ExtensionConfigValueType::Integer,
32 Self::Boolean(_) => ExtensionConfigValueType::Boolean,
33 }
34 }
35
36 pub(crate) fn validate_for_key(&self, key: &str) -> Result<(), WasmModelError> {
37 if let Self::String(value) = self {
38 if value.trim().is_empty() {
39 return Err(WasmModelError::InvalidConfigValue {
40 key: key.to_string(),
41 reason: "string values must not be empty".to_string(),
42 });
43 }
44 }
45
46 Ok(())
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct ExtensionConfigField {
52 pub key: String,
53 pub value_type: ExtensionConfigValueType,
54 pub required: bool,
55 pub default: Option<ExtensionConfigValue>,
56}
57
58impl ExtensionConfigField {
59 pub fn new(
60 key: impl Into<String>,
61 value_type: ExtensionConfigValueType,
62 required: bool,
63 ) -> Result<Self, WasmModelError> {
64 Ok(Self {
65 key: validate_token("extension_config_key", key.into())?,
66 value_type,
67 required,
68 default: None,
69 })
70 }
71
72 pub fn required(
73 key: impl Into<String>,
74 value_type: ExtensionConfigValueType,
75 ) -> Result<Self, WasmModelError> {
76 Self::new(key, value_type, true)
77 }
78
79 pub fn optional(
80 key: impl Into<String>,
81 value_type: ExtensionConfigValueType,
82 ) -> Result<Self, WasmModelError> {
83 Self::new(key, value_type, false)
84 }
85
86 pub fn with_default(mut self, value: ExtensionConfigValue) -> Result<Self, WasmModelError> {
87 if value.value_type() != self.value_type {
88 return Err(WasmModelError::ConfigTypeMismatch {
89 key: self.key.clone(),
90 expected: self.value_type,
91 actual: value.value_type(),
92 });
93 }
94 value.validate_for_key(&self.key)?;
95 self.default = Some(value);
96 Ok(self)
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct ExtensionConfigSchema {
102 pub version: u32,
103 pub fields: Vec<ExtensionConfigField>,
104}
105
106impl ExtensionConfigSchema {
107 pub fn new(version: u32, fields: Vec<ExtensionConfigField>) -> Result<Self, WasmModelError> {
108 if version == 0 {
109 return Err(WasmModelError::ZeroSchemaVersion {
110 field: "extension_config_schema_version",
111 });
112 }
113
114 let schema = Self { version, fields };
115 schema.validate()?;
116 Ok(schema)
117 }
118
119 pub fn validate(&self) -> Result<(), WasmModelError> {
120 let mut seen = std::collections::BTreeSet::new();
121 for field in &self.fields {
122 if !seen.insert(field.key.clone()) {
123 return Err(WasmModelError::DuplicateConfigField {
124 key: field.key.clone(),
125 });
126 }
127
128 if let Some(default) = &field.default {
129 if default.value_type() != field.value_type {
130 return Err(WasmModelError::ConfigTypeMismatch {
131 key: field.key.clone(),
132 expected: field.value_type,
133 actual: default.value_type(),
134 });
135 }
136 default.validate_for_key(&field.key)?;
137 }
138 }
139 Ok(())
140 }
141
142 pub fn effective_values(
143 &self,
144 configured: &std::collections::BTreeMap<String, ExtensionConfigValue>,
145 ) -> Result<std::collections::BTreeMap<String, ExtensionConfigValue>, WasmModelError> {
146 self.validate()?;
147
148 for (key, value) in configured {
149 let Some(field) = self.fields.iter().find(|field| field.key == *key) else {
150 return Err(WasmModelError::UnknownConfigField { key: key.clone() });
151 };
152
153 if value.value_type() != field.value_type {
154 return Err(WasmModelError::ConfigTypeMismatch {
155 key: key.clone(),
156 expected: field.value_type,
157 actual: value.value_type(),
158 });
159 }
160
161 value.validate_for_key(key)?;
162 }
163
164 let mut effective = std::collections::BTreeMap::new();
165 for field in &self.fields {
166 if let Some(value) = configured.get(&field.key) {
167 effective.insert(field.key.clone(), value.clone());
168 } else if let Some(default) = &field.default {
169 effective.insert(field.key.clone(), default.clone());
170 } else if field.required {
171 return Err(WasmModelError::MissingRequiredConfigField {
172 key: field.key.clone(),
173 });
174 }
175 }
176
177 Ok(effective)
178 }
179}