oxide_eval 0.1.0

Oxide Eval is a simple Javascript Evaluator based on oxc.
Documentation
use anyhow::{anyhow, bail, Result};
use regex::Regex;
use serde_json::Value;

pub struct StringMethod {
    args: Vec<Value>,
}

impl StringMethod {
    pub fn new(args: Vec<Value>) -> Self {
        StringMethod { args }
    }

    fn get_string_argument(&self, index: usize) -> Result<&str> {
        self.args
            .get(index)
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("Argument {} must be a string", index + 1))
    }

    fn get_number_argument(&self, index: usize) -> Result<usize> {
        self.args
            .get(index)
            .and_then(|v| v.as_f64())
            .map(|v| v as usize)
            .ok_or_else(|| anyhow!("Argument {} must be a number", index + 1))
    }

    pub fn replace(&self, s: &str) -> Result<Value> {
        if self.args.len() != 2 {
            bail!("replace method requires 2 arguments");
        }
        let old = self.get_string_argument(0)?;
        let new = self.get_string_argument(1)?;
        Ok(Value::String(s.replace(old, new)))
    }

    pub fn contains(&self, s: &str) -> Result<Value> {
        if self.args.len() != 1 {
            bail!("contains method requires 1 argument");
        }
        let substring = self.get_string_argument(0)?;
        Ok(Value::Bool(s.contains(substring)))
    }

    pub fn split(&self, s: &str) -> Result<Value> {
        if self.args.len() != 1 {
            bail!("split method requires 1 argument");
        }
        let delimiter = self.get_string_argument(0)?;
        let result: Vec<String> = if delimiter.is_empty() {
            s.chars().map(|c| c.to_string()).collect()
        } else {
            s.split(delimiter).map(|s| s.to_string()).collect()
        };
        Ok(Value::Array(
            result.into_iter().map(Value::String).collect(),
        ))
    }

    pub fn index_of(&self, s: &str) -> Result<Value> {
        if self.args.len() != 1 {
            bail!("indexOf method requires 1 argument");
        }
        let substring = self.get_string_argument(0)?;
        Ok(Value::Number(
            s.find(substring).map_or(-1, |index| index as i32).into(),
        ))
    }

    pub fn last_index_of(&self, s: &str) -> Result<Value> {
        if self.args.len() != 1 {
            bail!("lastIndexOf method requires 1 argument");
        }
        let substring = self.get_string_argument(0)?;
        Ok(Value::Number(
            s.rfind(substring).map_or(-1, |index| index as i32).into(),
        ))
    }

    pub fn to_upper_case(&self, s: &str) -> Result<Value> {
        if self.args.len() != 0 {
            bail!("toUpperCase method requires no arguments");
        }
        Ok(Value::String(s.to_uppercase()))
    }

    pub fn to_lower_case(&self, s: &str) -> Result<Value> {
        if self.args.len() != 0 {
            bail!("toLowerCase method requires no arguments");
        }
        Ok(Value::String(s.to_lowercase()))
    }

    pub fn substring(&self, s: &str) -> Result<Value> {
        let start = self.get_number_argument(0)?;
        let end = if self.args.len() == 2 {
            self.get_number_argument(1)?
        } else {
            s.len()
        };
        Ok(Value::String(s[start..end].to_string()))
    }

    pub fn starts_with(&self, s: &str) -> Result<Value> {
        if self.args.len() != 1 {
            bail!("startsWith method requires 1 argument");
        }
        let prefix = self.get_string_argument(0)?;
        Ok(Value::Bool(s.starts_with(prefix)))
    }

    pub fn ends_with(&self, s: &str) -> Result<Value> {
        if self.args.len() != 1 {
            bail!("endsWith method requires 1 argument");
        }
        let suffix = self.get_string_argument(0)?;
        Ok(Value::Bool(s.ends_with(suffix)))
    }

    pub fn regex_replace(&self, s: &str) -> Result<Value> {
        if self.args.len() != 2 {
            bail!("regexReplace method requires 2 arguments");
        }
        let pattern = self.get_string_argument(0)?;
        let replacement = self.get_string_argument(1)?;
        let re = Regex::new(pattern).map_err(|e| anyhow!(e))?;
        Ok(Value::String(re.replace_all(s, replacement).to_string()))
    }

    pub fn length(&self, s: &str) -> Result<Value> {
        if self.args.len() != 0 {
            bail!("length method requires no arguments");
        }
        Ok(Value::Number(s.chars().count().into()))
    }

    pub fn trim(&self, s: &str) -> Result<Value> {
        if self.args.len() != 0 {
            bail!("trim method requires no arguments");
        }
        Ok(Value::String(s.trim().to_string()))
    }
}