use syn::{
Expr, Ident, LitStr, Token, TypePath,
parse::{Parse, ParseStream},
};
pub(crate) const MAX_PLUGIN_NAME_LEN: usize = 64;
pub(crate) struct PluginArgs {
pub name: LitStr,
pub description: LitStr,
pub version: Expr,
pub license: Expr,
pub author: LitStr,
pub handlerton: Option<TypePath>,
}
impl Parse for PluginArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut name: Option<LitStr> = None;
let mut description: Option<LitStr> = None;
let mut version: Option<Expr> = None;
let mut license: Option<Expr> = None;
let mut author: Option<LitStr> = None;
let mut handlerton: Option<TypePath> = None;
while !input.is_empty() {
let key: Ident = input.parse()?;
input.parse::<Token![=]>()?;
match key.to_string().as_str() {
"name" => set_once(&mut name, input.parse()?, &key)?,
"description" => set_once(&mut description, input.parse()?, &key)?,
"version" => set_once(&mut version, input.parse()?, &key)?,
"license" => set_once(&mut license, input.parse()?, &key)?,
"author" => set_once(&mut author, input.parse()?, &key)?,
"handlerton" => set_once(&mut handlerton, input.parse()?, &key)?,
other => {
return Err(syn::Error::new(
key.span(),
format!(
"unknown #[plugin] argument `{other}` (expected one of: name, description, version, license, author, handlerton)"
),
));
}
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
let name = name
.ok_or_else(|| syn::Error::new(input.span(), "#[plugin] missing `name = \"...\"`"))?;
validate_name(&name)?;
Ok(PluginArgs {
name,
description: description.ok_or_else(|| {
syn::Error::new(input.span(), "#[plugin] missing `description = \"...\"`")
})?,
version: version.ok_or_else(|| {
syn::Error::new(input.span(), "#[plugin] missing `version = ...`")
})?,
license: license.ok_or_else(|| {
syn::Error::new(input.span(), "#[plugin] missing `license = ...`")
})?,
author: author.ok_or_else(|| {
syn::Error::new(input.span(), "#[plugin] missing `author = \"...\"`")
})?,
handlerton,
})
}
}
fn set_once<T>(slot: &mut Option<T>, value: T, key: &Ident) -> syn::Result<()> {
if slot.is_some() {
return Err(syn::Error::new(
key.span(),
format!("#[plugin] argument `{key}` set more than once"),
));
}
*slot = Some(value);
Ok(())
}
fn validate_name(name: &LitStr) -> syn::Result<()> {
let value = name.value();
if value.is_empty() {
return Err(syn::Error::new(
name.span(),
"#[plugin] `name` must be non-empty",
));
}
if value.len() > MAX_PLUGIN_NAME_LEN {
return Err(syn::Error::new(
name.span(),
format!(
"#[plugin] `name` must be at most {MAX_PLUGIN_NAME_LEN} bytes (mysqld plugin name limit)"
),
));
}
if value.bytes().any(|b| b == 0) {
return Err(syn::Error::new(
name.span(),
"#[plugin] `name` must not contain a NUL byte",
));
}
Ok(())
}
#[cfg(test)]
mod tests;