Skip to main content

petr_dispatch/
config.rs

1use std::collections::HashMap;
2
3use serde::Deserialize;
4
5use crate::{
6    op_acc_add, op_acc_mul, op_add_prefix, op_add_suffix, op_nop, op_octal_add,
7    op_octal_multiply, op_octet_add, op_octet_encode, DispatchError, DispatchResult, OpArg,
8    OperationTable,
9};
10
11/// Top-level operation TOML config.
12#[derive(Debug, Deserialize)]
13pub struct OperationConfig {
14    /// Operation declarations keyed by hex or decimal selector string.
15    pub operations: HashMap<String, OperationDefinition>,
16}
17
18/// One operation declaration.
19#[derive(Debug, Deserialize)]
20pub struct OperationDefinition {
21    /// Operation kind.
22    #[serde(rename = "type")]
23    pub op_type: String,
24    /// Optional prefix string.
25    pub prefix: Option<String>,
26    /// Optional suffix string.
27    pub suffix: Option<String>,
28    /// Optional multiplier.
29    pub multiplier: Option<i64>,
30    /// Optional numeric value.
31    pub value: Option<i64>,
32}
33
34/// Parse a selector string like `0x10`, `10`, or `32`.
35pub fn parse_selector(text: &str) -> DispatchResult<u8> {
36    let trimmed = text.trim();
37    let parsed = if let Some(hex) = trimmed.strip_prefix("0x").or_else(|| trimmed.strip_prefix("0X")) {
38        u8::from_str_radix(hex, 16)
39    } else {
40        trimmed.parse::<u8>()
41    };
42    parsed.map_err(|_| DispatchError::InvalidInput(format!("bad selector `{text}`")))
43}
44
45/// Build an operation table from TOML text.
46pub fn table_from_toml(text: &str) -> DispatchResult<OperationTable> {
47    let config: OperationConfig = toml::from_str(text).map_err(|e| DispatchError::InvalidInput(e.to_string()))?;
48    table_from_config(config)
49}
50
51/// Build an operation table from a parsed config.
52pub fn table_from_config(config: OperationConfig) -> DispatchResult<OperationTable> {
53    let mut table = OperationTable::new();
54    for (selector_text, op) in config.operations {
55        let selector = parse_selector(&selector_text)?;
56        let name = op.op_type.clone();
57        match op.op_type.as_str() {
58            "nop" => table.register(selector, name, op_nop, OpArg::default()),
59            "add_prefix" => {
60                let prefix = op.prefix.ok_or_else(|| DispatchError::MissingParam { op: name.clone(), param: "prefix" })?;
61                table.register(selector, name, op_add_prefix, OpArg::text(prefix));
62            }
63            "add_suffix" => {
64                let suffix = op.suffix.ok_or_else(|| DispatchError::MissingParam { op: name.clone(), param: "suffix" })?;
65                table.register(selector, name, op_add_suffix, OpArg::text(suffix));
66            }
67            "octal_multiply" => {
68                let n = op.multiplier.ok_or_else(|| DispatchError::MissingParam { op: name.clone(), param: "multiplier" })?;
69                table.register(selector, name, op_octal_multiply, OpArg::n(n));
70            }
71            "octal_add" => {
72                let n = op.value.ok_or_else(|| DispatchError::MissingParam { op: name.clone(), param: "value" })?;
73                table.register(selector, name, op_octal_add, OpArg::n(n));
74            }
75            "octet_encode" => table.register(selector, name, op_octet_encode, OpArg::default()),
76            "octet_add" => {
77                let n = op.value.ok_or_else(|| DispatchError::MissingParam { op: name.clone(), param: "value" })?;
78                table.register(selector, name, op_octet_add, OpArg::n(n));
79            }
80            "acc_add" => table.register(selector, name, op_acc_add, OpArg::n(op.value.unwrap_or(1))),
81            "acc_mul" => table.register(selector, name, op_acc_mul, OpArg::n(op.multiplier.unwrap_or(2))),
82            other => return Err(DispatchError::UnknownOperationType(other.to_string())),
83        }
84    }
85    Ok(table)
86}