use crate::compiler::prelude::*;
use rand::RngCore;
const MAX_LENGTH: i64 = 1024 * 64;
const LENGTH_TOO_LARGE_ERR: &str = "Length is too large. Maximum is 64k";
const LENGTH_TOO_SMALL_ERR: &str = "Length cannot be negative";
fn random_bytes(length: Value) -> Resolved {
let mut output = vec![0_u8; get_length(length)?];
rand::rng().fill_bytes(&mut output);
Ok(Value::Bytes(Bytes::from(output)))
}
#[derive(Clone, Copy, Debug)]
pub struct RandomBytes;
impl Function for RandomBytes {
fn identifier(&self) -> &'static str {
"random_bytes"
}
fn usage(&self) -> &'static str {
"A cryptographically secure random number generator. Returns a string value containing the number of random bytes requested."
}
fn category(&self) -> &'static str {
Category::Random.as_ref()
}
fn internal_failure_reasons(&self) -> &'static [&'static str] {
&[
"`length` is negative.",
"`length` is larger than the maximum value (64k).",
]
}
fn return_kind(&self) -> u16 {
kind::BYTES
}
fn parameters(&self) -> &'static [Parameter] {
const PARAMETERS: &[Parameter] = &[Parameter::required(
"length",
kind::INTEGER,
"The number of bytes to generate. Must not be larger than 64k.",
)];
PARAMETERS
}
fn examples(&self) -> &'static [Example] {
&[
example! {
title: "Generate random base 64 encoded bytes",
source: "encode_base64(random_bytes(16))",
result: Ok("LNu0BBgUbh7XAlXbjSOomQ=="),
deterministic: false,
},
example! {
title: "Generate 16 random bytes",
source: "length(random_bytes(16))",
result: Ok("16"),
},
]
}
fn compile(
&self,
state: &state::TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let length = arguments.required("length");
if let Some(literal) = length.resolve_constant(state) {
let _: usize =
get_length(literal.clone()).map_err(|err| function::Error::InvalidArgument {
keyword: "length",
value: literal,
error: err,
})?;
}
Ok(RandomBytesFn { length }.as_expr())
}
}
fn get_length(value: Value) -> std::result::Result<usize, &'static str> {
let length = value.try_integer().expect("length must be an integer");
if length < 0 {
return Err(LENGTH_TOO_SMALL_ERR);
}
if length > MAX_LENGTH {
return Err(LENGTH_TOO_LARGE_ERR);
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
Ok(length as usize)
}
#[derive(Debug, Clone)]
struct RandomBytesFn {
length: Box<dyn Expression>,
}
impl FunctionExpression for RandomBytesFn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let length = self.length.resolve(ctx)?;
random_bytes(length)
}
fn type_def(&self, state: &state::TypeState) -> TypeDef {
match self.length.resolve_constant(state) {
None => TypeDef::bytes().fallible(),
Some(value) => {
if get_length(value).is_ok() {
TypeDef::bytes()
} else {
TypeDef::bytes().fallible()
}
}
}
}
}