use crate::code_block::{Arg, CodeBlockBuilder};
use crate::lang::CodeLang;
use crate::type_name::TypeName;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ParameterSpec {
pub(crate) name: String,
pub(crate) param_type: TypeName,
pub(crate) default_value: Option<crate::code_block::CodeBlock>,
pub(crate) is_variadic: bool,
}
impl ParameterSpec {
pub fn builder(name: &str, param_type: TypeName) -> ParameterSpecBuilder {
ParameterSpecBuilder {
name: name.to_string(),
param_type,
default_value: None,
is_variadic: false,
}
}
pub fn new(name: &str, param_type: TypeName) -> Result<Self, crate::error::SigilStitchError> {
Self::builder(name, param_type).build()
}
pub fn of(name: &str, param_type: TypeName) -> Self {
Self::new(name, param_type).expect("ParameterSpec name must not be empty")
}
pub fn name(&self) -> &str {
&self.name
}
pub fn param_type(&self) -> &TypeName {
&self.param_type
}
pub fn emit_into(&self, cb: &mut CodeBlockBuilder, lang: &dyn CodeLang) {
let mut fmt = String::new();
let mut args: Vec<Arg> = Vec::new();
if lang.type_decl_syntax().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_decl_syntax().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 {
name: String,
param_type: TypeName,
default_value: Option<crate::code_block::CodeBlock>,
is_variadic: bool,
}
impl ParameterSpecBuilder {
pub fn default_value(mut self, value: crate::code_block::CodeBlock) -> Self {
self.default_value = Some(value);
self
}
pub fn variadic(mut self) -> Self {
self.is_variadic = true;
self
}
pub fn build(self) -> Result<ParameterSpec, 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) -> String {
let lang = TypeScript::new();
let mut cb = CodeBlock::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::new("id", TypeName::primitive("string")).unwrap();
let output = emit_param(¶m);
assert_eq!(output, "id: string");
}
#[test]
fn test_variadic_param() {
let param = ParameterSpec::builder("args", TypeName::primitive("string"))
.variadic()
.build()
.unwrap();
let output = emit_param(¶m);
assert_eq!(output, "...args: string");
}
#[test]
fn test_param_with_default() {
let default = CodeBlock::of("0", ()).unwrap();
let param = ParameterSpec::builder("count", TypeName::primitive("number"))
.default_value(default)
.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::primitive("string")).build();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("'name' must not be empty")
);
}
}