iocaine 2.2.0

The deadliest poison known to AI
Documentation
// SPDX-FileCopyrightText: 2025 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: MIT

#![allow(clippy::needless_pass_by_value)]

mod context;
mod error;
mod json;
mod log;
mod network;
mod outcome;
mod request;
mod string_list;
mod tests;
mod version;

pub use context::IocaineContext;
pub use error::Error;
pub use outcome::Outcome;
pub use request::Request;
pub use string_list::MutableStringList;

use axum::http::HeaderMap;
use roto::{FileTree, Runtime, TypedFunc, Val, Verdict};
use std::path::Path;
use std::sync::Arc;

pub type DecideFunc =
    TypedFunc<IocaineContext, fn(Val<Request>) -> Verdict<Val<Outcome>, Val<Outcome>>>;

#[derive(Debug)]
pub struct MeansOfProduction {
    pub decider: DecideFunc,
    pub context: IocaineContext,
}

impl MeansOfProduction {
    pub fn new_runtime() -> Result<Runtime, Error> {
        let mut runtime = Runtime::new();

        string_list::register_string_list(&mut runtime)?;
        request::register_request_type(&mut runtime)?;
        context::register_context(&mut runtime)?;
        outcome::register_outcome(&mut runtime)?;
        version::register_version(&mut runtime)?;
        log::register_log(&mut runtime)?;
        json::register_json(&mut runtime)?;
        network::register_network_type(&mut runtime)?;

        Ok(runtime)
    }

    pub fn run_init(path: &str) -> Result<IocaineContext, Error> {
        let runtime = Self::new_runtime()?;
        let mut compiled = FileTree::directory(Path::new(&path)).compile(runtime)?;
        let init = compiled.get_function::<IocaineContext, fn() -> Verdict<(), Arc<str>>>("init");
        let mut context = IocaineContext::default();

        if let Ok(init) = init {
            init.call(&mut context)
                .into_result()
                .map_err(|e| Error::String(format!("init script failed: {e}")))?;
        }

        Ok(context)
    }

    pub fn new(path: &str) -> Result<Self, Error> {
        let context = Self::run_init(path)?;

        let runtime = Self::new_runtime()?;

        let mut compiled = FileTree::directory(Path::new(path)).compile(runtime)?;
        let decider = compiled
            .get_function::<IocaineContext, fn(Val<Request>) -> Verdict<Val<Outcome>, Val<Outcome>>>(
                "decide",
            )
            .map_err(|e| e.to_string())?;

        Ok(Self { decider, context })
    }

    pub fn decide(
        &self,
        headers: HeaderMap,
        path: Option<String>,
        method: &str,
    ) -> Result<Outcome, Outcome> {
        let request = Request {
            method: method.to_owned(),
            path: path.unwrap_or_default(),
            headers,
        };

        self.decider
            .call(&mut self.context.clone(), Val(request))
            .into_result()
            .map(|a| a.0)
            .map_err(|r| r.0)
    }
}