alixt 0.8.0

A command line http API testing utility with TOML configuration
Documentation
// This file is part of alixt.
// Copyright (C) 2025 Devon Harley Offutt
//
// alixt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.


use std::{collections::HashMap, sync::Arc};

use regex::Regex;

#[derive(Debug)]
pub struct Global {
    pub env_variables: HashMap<String, String>,
    pub variables: HashMap<String, String>,
    matcher: Regex,
}

impl Global {
    pub fn new() -> Self {
        Self {
            env_variables: HashMap::new(),
            variables: HashMap::new(),
            matcher: Regex::new(r"\{\{\s*(.*?)\s*\}\}").expect("Failed to compile regex"),
        }
    }
    pub fn substitute_values_in_text(&self, input: &str) -> String {
        self.matcher
            .replace_all(input, |caps: &regex::Captures| {
                let key = &caps[1].trim();

                self.resolve(key).unwrap_or(&caps[0]).to_string()
            })
            .to_string()
    }
    pub fn resolve(&self, key: &str) -> Option<&str> {
        if let Some(identifier) = key.strip_prefix("env.") {
            self.env_variables.get(identifier).map(|v| v.as_str())
        } else if let Some(identifier) = key.strip_prefix("global.") {
            self.variables.get(identifier).map(|v| v.as_str())
        } else if let Some(value) = self.variables.get(key) {
            Some(value.as_str())
        } else if let Some(value) = self.env_variables.get(key) {
            Some(value.as_str())
        } else {
            None
        }
    }
}

impl Default for Global {
    fn default() -> Self {
        Self::new()
    }
}

pub struct RunState {
    pub run_variables: HashMap<String, String>,
    pub global: Arc<Global>,
}

impl RunState {
    pub fn new(global: Arc<Global>) -> Self {
        Self {
            run_variables: HashMap::new(),
            global,
        }
    }
    fn resolve(&self, key: &str) -> Option<&str> {
        if let Some(identifier) = key.strip_prefix("env.") {
            return self.global.env_variables.get(identifier).map(|v| v.as_str());
        }

        if let Some(identifier) = key.strip_prefix("global.") {
            return self.global.variables.get(identifier).map(|v| v.as_str());
        }

        if let Some(identifier) = key.strip_prefix("run.") {
            return self.run_variables.get(identifier).map(|v| v.as_str());
        }

        if let Some(value) = self.run_variables.get(key) {
            Some(value.as_str())
        } else if let Some(value) = self.global.variables.get(key) {
            Some(value.as_str())
        } else if let Some(value) = self.global.env_variables.get(key) {
            Some(value.as_str())
        } else {
            None
        }
    }

    pub fn substitute_values_in_text(&self, input: &str) -> String {
        self.global.matcher
            .replace_all(input, |caps: &regex::Captures| {
                let key = &caps[1].trim();

                self.resolve(key).unwrap_or(&caps[0]).to_string()
            })
            .to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_substitution() {
        let mut global = Global::new();
        global
            .env_variables
            .insert("one".to_string(), "Hello,_".to_string());

        let mut state = RunState::new(Arc::new(global));


        state
            .run_variables
            .insert("two".to_string(), "World!".to_string());

        let input = r"{{one}}{{two}}".to_string();

        let output = state.substitute_values_in_text(&input);

        assert_eq!("Hello,_World!".to_string(), output);
    }
}