iocaine 2.2.0

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

use roto::{Runtime, Val, roto_method, roto_static_method};
use serde::Serialize;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;

use crate::means_of_production::{Error, Outcome};

#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct Metadata(pub HashMap<String, String>);
pub type MutableMetadata = Rc<RefCell<Metadata>>;

#[derive(Debug, Serialize, Clone)]
pub struct Log {
    #[serde(rename = "_msg")]
    pub msg: String,
    pub service: String,
    pub verdict: LogVerdict,
    #[serde(flatten)]
    pub metadata: MutableMetadata,
}

impl Default for Log {
    fn default() -> Self {
        Self {
            msg: String::default(),
            service: "iocaine".to_owned(),
            verdict: LogVerdict::default(),
            metadata: MutableMetadata::default(),
        }
    }
}

#[derive(Debug, Serialize, Default, Clone)]
pub struct LogVerdict {
    pub r#type: Arc<str>,
    #[serde(flatten)]
    pub outcome: Outcome,
}

#[derive(Debug, Clone, Default, Serialize)]
pub struct LogLine(Rc<RefCell<Log>>);

impl LogLine {
    pub fn to_json(&self) -> Result<String, ()> {
        serde_json::to_string(&self.0.borrow().clone()).map_err(|e| {
            tracing::error!("Unable to format LogLine to JSON: {e}");
        })
    }
}

pub fn register_log(runtime: &mut Runtime) -> Result<(), Error> {
    runtime.register_clone_type_with_name::<LogLine>("Logger", "Logging framework")?;
    runtime.register_clone_type_with_name::<MutableMetadata>(
        "Metadata",
        "Metadata (key-value pairs) associated with a log message",
    )?;

    {
        #[roto_static_method(runtime, MutableMetadata)]
        fn new() -> Val<MutableMetadata> {
            Rc::new(RefCell::new(Metadata::default())).into()
        }

        #[roto_static_method(runtime, MutableMetadata)]
        fn with(key: Arc<str>, value: Arc<str>) -> Val<MutableMetadata> {
            Rc::new(RefCell::new(Metadata(HashMap::from([(
                key.to_string(),
                value.to_string(),
            )]))))
            .into()
        }

        {
            #[roto_method(runtime, MutableMetadata)]
            fn with(
                md: Val<MutableMetadata>,
                key: Arc<str>,
                value: Arc<str>,
            ) -> Val<MutableMetadata> {
                (*md)
                    .borrow_mut()
                    .0
                    .insert(key.to_string(), value.to_string());
                md
            }
        }
    }

    {
        #[roto_static_method(runtime, LogLine)]
        fn with_metadata(metadata: Val<MutableMetadata>) -> Val<LogLine> {
            LogLine(Rc::new(RefCell::new(Log {
                service: "iocaine".to_owned(),
                msg: "poisoning request: unknown".to_owned(),
                metadata: metadata.0,
                ..Default::default()
            })))
            .into()
        }

        #[roto_static_method(runtime, LogLine)]
        fn with_verdict(verdict: Arc<str>, outcome: Val<Outcome>) -> Val<LogLine> {
            LogLine(Rc::new(RefCell::new(Log {
                service: "iocaine".to_owned(),
                msg: format!("poisoning request: {verdict}"),
                verdict: LogVerdict {
                    r#type: verdict,
                    outcome: outcome.0,
                },
                ..Default::default()
            })))
            .into()
        }

        #[roto_static_method(runtime, LogLine)]
        fn new() -> Val<LogLine> {
            LogLine::default().into()
        }

        #[roto_static_method(runtime, LogLine)]
        fn debug(msg: Arc<str>) {
            tracing::debug!("{msg}");
        }
    }

    #[roto_method(runtime, LogLine)]
    fn with_metadata(log: Val<LogLine>, metadata: Val<MutableMetadata>) -> Val<LogLine> {
        (*log).0.borrow_mut().metadata = metadata.0;
        log
    }

    #[roto_method(runtime, LogLine)]
    fn with_verdict(log: Val<LogLine>, verdict: Arc<str>, outcome: Val<Outcome>) -> Val<LogLine> {
        {
            let mut line = log.0.0.borrow_mut();
            line.msg = format!("poisoning request: {verdict}");
            line.verdict = LogVerdict {
                r#type: verdict,
                outcome: outcome.0,
            };
        }
        log
    }

    #[roto_method(runtime, LogLine)]
    fn stdout(log: Val<LogLine>) {
        let _ = (*log).to_json().map(|json| println!("{json}"));
    }

    #[roto_method(runtime, LogLine)]
    fn to_json(log: Val<LogLine>) -> Arc<str> {
        #[allow(clippy::ignored_unit_patterns)]
        (*log).to_json().map_or_else(|_| Arc::default(), Arc::from)
    }

    Ok(())
}