use crate::code_block::{Arg, CodeBlockBuilder};
use crate::lang::CodeLang;
use crate::type_name::TypeName;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(bound = "")]
pub struct ParameterSpec<L: CodeLang> {
pub(crate) name: String,
pub(crate) param_type: TypeName<L>,
pub(crate) default_value: Option<crate::code_block::CodeBlock<L>>,
pub(crate) is_variadic: bool,
}
impl<L: CodeLang> ParameterSpec<L> {
pub fn builder(name: &str, param_type: TypeName<L>) -> ParameterSpecBuilder<L> {
ParameterSpecBuilder {
name: name.to_string(),
param_type,
default_value: None,
is_variadic: false,
}
}
pub fn new(
name: &str,
param_type: TypeName<L>,
) -> Result<Self, crate::error::SigilStitchError> {
Self::builder(name, param_type).build()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn param_type(&self) -> &TypeName<L> {
&self.param_type
}
pub fn emit_into(&self, cb: &mut CodeBlockBuilder<L>, lang: &L) {
let mut fmt = String::new();
let mut args: Vec<Arg<L>> = Vec::new();
if lang.type_before_name() {
if !self.param_type.is_empty() {
fmt.push_str("%T");
args.push(Arg::TypeName(self.param_type.clone()));
fmt.push(' ');
}
fmt.push_str(&lang.escape_reserved(&self.name));
} else {
if self.is_variadic {
fmt.push_str("...");
}
fmt.push_str(&lang.escape_reserved(&self.name));
if !self.param_type.is_empty() {
let sep = lang.type_annotation_separator();
fmt.push_str(sep);
fmt.push_str("%T");
args.push(Arg::TypeName(self.param_type.clone()));
}
}
if let Some(default) = &self.default_value {
fmt.push_str(" = %L");
args.push(Arg::Code(default.clone()));
}
cb.add(&fmt, args);
}
}
#[derive(Debug)]
pub struct ParameterSpecBuilder<L: CodeLang> {
name: String,
param_type: TypeName<L>,
default_value: Option<crate::code_block::CodeBlock<L>>,
is_variadic: bool,
}
impl<L: CodeLang> ParameterSpecBuilder<L> {
pub fn default_value(&mut self, value: crate::code_block::CodeBlock<L>) -> &mut Self {
self.default_value = Some(value);
self
}
pub fn variadic(&mut self) -> &mut Self {
self.is_variadic = true;
self
}
pub fn build(self) -> Result<ParameterSpec<L>, crate::error::SigilStitchError> {
snafu::ensure!(
!self.name.is_empty(),
crate::error::EmptyNameSnafu {
builder: "ParameterSpecBuilder",
}
);
Ok(ParameterSpec {
name: self.name,
param_type: self.param_type,
default_value: self.default_value,
is_variadic: self.is_variadic,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::code_block::CodeBlock;
use crate::lang::typescript::TypeScript;
fn emit_param(spec: &ParameterSpec<TypeScript>) -> String {
let lang = TypeScript::new();
let mut cb = CodeBlock::<TypeScript>::builder();
spec.emit_into(&mut cb, &lang);
let block = cb.build().unwrap();
let imports = crate::import::ImportGroup::new();
let mut renderer = crate::code_renderer::CodeRenderer::new(&lang, &imports, 80);
renderer.render(&block).unwrap()
}
#[test]
fn test_simple_param() {
let param = ParameterSpec::<TypeScript>::new("id", TypeName::primitive("string")).unwrap();
let output = emit_param(¶m);
assert_eq!(output, "id: string");
}
#[test]
fn test_variadic_param() {
let mut pb = ParameterSpec::builder("args", TypeName::<TypeScript>::primitive("string"));
pb.variadic();
let param = pb.build().unwrap();
let output = emit_param(¶m);
assert_eq!(output, "...args: string");
}
#[test]
fn test_param_with_default() {
let default = CodeBlock::<TypeScript>::of("0", ()).unwrap();
let mut pb = ParameterSpec::builder("count", TypeName::<TypeScript>::primitive("number"));
pb.default_value(default);
let param = pb.build().unwrap();
let output = emit_param(¶m);
assert_eq!(output, "count: number = 0");
}
#[test]
fn test_build_empty_name_errors() {
let result =
ParameterSpec::builder("", TypeName::<TypeScript>::primitive("string")).build();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("'name' must not be empty")
);
}
}