use std::{borrow::Cow, time::Duration};
use resolved_shared::ArgType;
use serde::Serialize;
use crate::{Error, ItemRef, owned_script::OwnedScript};
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Script<'c> {
pub(crate) lua: Cow<'c, str>,
pub(crate) args: Vec<ArgData<'c>>,
pub(crate) with: Option<&'c ItemRef>,
pub(crate) timeout: Option<Duration>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum ArgData<'s> {
Arg(Cow<'s, [u8]>),
ArgRef(&'s ItemRef),
NamedArg { key: &'s str, value: Cow<'s, [u8]> },
NamedArgRef { key: &'s str, value: &'s ItemRef },
}
impl ArgData<'_> {
pub const fn arg_type(&self) -> u8 {
(match self {
ArgData::Arg(_) => ArgType::Arg,
ArgData::ArgRef(_) => ArgType::ArgRef,
ArgData::NamedArg { key: _, value: _ } => ArgType::NamedArg,
ArgData::NamedArgRef { key: _, value: _ } => ArgType::NamedArgRef,
}) as u8
}
}
impl<'c> Script<'c> {
#[inline]
pub fn new<S>(lua_script: S) -> Self
where
S: Into<Cow<'c, str>>,
{
Self::new_with_capacity(lua_script, 0)
}
#[inline]
pub fn new_with_capacity<S>(lua_script: S, arg_cap: usize) -> Self
where
S: Into<Cow<'c, str>>,
{
Self {
lua: lua_script.into(),
with: None,
args: Vec::with_capacity(arg_cap),
timeout: None,
}
}
#[inline]
#[must_use]
pub fn timeout(&self) -> Option<Duration> {
self.timeout
}
#[inline]
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub(crate) fn check_args(&self, item_ref: &'c ItemRef) -> Result<(), Error> {
let id = item_ref.resolve().id();
for arg in &self.args {
let arg_id = match arg {
ArgData::ArgRef(r) => r.resolve().id(),
ArgData::NamedArgRef { key: _, value } => value.resolve().id(),
_ => continue,
};
if id != arg_id {
return Err(Error::MismatchedItemRef(id, arg_id));
}
}
Ok(())
}
pub(crate) fn with(mut self, item_ref: &'c ItemRef) -> Result<Self, Error> {
self.check_args(item_ref)?;
self.with = Some(item_ref);
Ok(self)
}
#[inline]
pub fn arg<S: Serialize>(mut self, value: &S) -> Result<Self, Error> {
let arg = ArgData::Arg(Cow::Owned(Self::ser(value)?));
self.args.push(arg);
Ok(self)
}
#[inline]
pub fn arg_ref(mut self, item_ref: &'c ItemRef) -> Result<Self, Error> {
let arg = ArgData::ArgRef(item_ref);
self.args.push(arg);
Ok(self)
}
#[inline]
pub fn named_arg<S: Serialize>(mut self, key: &'c str, value: &S) -> Result<Self, Error> {
let arg = ArgData::NamedArg {
key,
value: Cow::Owned(Self::ser(value)?),
};
self.args.push(arg);
Ok(self)
}
#[inline]
pub fn named_arg_ref(mut self, key: &'c str, item_ref: &'c ItemRef) -> Result<Self, Error> {
let arg = ArgData::NamedArgRef {
key,
value: item_ref,
};
self.args.push(arg);
Ok(self)
}
#[inline]
pub(crate) fn ser<S: Serialize>(value: &S) -> Result<Vec<u8>, Error> {
Ok(rmp_serde::to_vec(value)?)
}
}
impl<'c, T> From<T> for Script<'c>
where
T: Into<Cow<'c, str>>,
{
fn from(value: T) -> Self {
Self::new(value.into())
}
}
impl<T> From<T> for OwnedScript
where
T: Into<String>,
{
fn from(value: T) -> Self {
Self::new(value.into())
}
}
impl<'c> From<&'c OwnedScript> for Script<'c> {
fn from(value: &'c OwnedScript) -> Self {
value.as_ref()
}
}
impl<'c> From<Script<'c>> for OwnedScript {
fn from(value: Script<'c>) -> Self {
value.as_owned()
}
}
impl std::fmt::Display for Script<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.lua)
}
}
impl std::fmt::Display for OwnedScript {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.lua)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn new() -> Result<(), Error> {
let script = Script::new("return 1");
assert_eq!("return 1", script.lua);
Ok(())
}
#[tokio::test]
async fn arg() -> Result<(), Error> {
let script = Script::new("return 1").arg(&95)?;
assert_eq!(1, script.args.len());
Ok(())
}
#[tokio::test]
async fn round_trip() {
let a_script = Script::new("");
let owned = a_script.as_owned();
let b_script = owned.as_ref();
assert_eq!(a_script, b_script);
}
}