use super::helpers::{extract_named_string, has_flag};
use super::validators::{ValidatorSpec, extract_validators};
use crate::ts_syn::abi::{DecoratorIR, DiagnosticCollector};
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum RenameAll {
#[default]
None,
CamelCase,
SnakeCase,
ScreamingSnakeCase,
KebabCase,
PascalCase,
}
impl std::str::FromStr for RenameAll {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().replace(['-', '_'], "").as_str() {
"camelcase" => Ok(Self::CamelCase),
"snakecase" => Ok(Self::SnakeCase),
"screamingsnakecase" => Ok(Self::ScreamingSnakeCase),
"kebabcase" => Ok(Self::KebabCase),
"pascalcase" => Ok(Self::PascalCase),
_ => Err(()),
}
}
}
impl RenameAll {
pub fn apply(&self, name: &str) -> String {
use convert_case::{Case, Casing};
match self {
Self::None => name.to_string(),
Self::CamelCase => name.to_case(Case::Camel),
Self::SnakeCase => name.to_case(Case::Snake),
Self::ScreamingSnakeCase => name.to_case(Case::UpperSnake),
Self::KebabCase => name.to_case(Case::Kebab),
Self::PascalCase => name.to_case(Case::Pascal),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TaggingMode {
InternallyTagged { tag: String },
ExternallyTagged,
AdjacentlyTagged { tag: String, content: String },
Untagged,
}
impl Default for TaggingMode {
fn default() -> Self {
Self::InternallyTagged {
tag: "__type".to_string(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SerdeContainerOptions {
pub rename_all: RenameAll,
pub deny_unknown_fields: bool,
pub tagging: TaggingMode,
}
impl SerdeContainerOptions {
pub fn from_decorators(decorators: &[DecoratorIR]) -> Self {
let mut opts = Self::default();
for decorator in decorators {
if !decorator.name.eq_ignore_ascii_case("serde") {
continue;
}
let args = decorator.args_src.trim();
if let Some(rename_all) = extract_named_string(args, "renameAll")
&& let Ok(convention) = rename_all.parse::<RenameAll>()
{
opts.rename_all = convention;
}
if has_flag(args, "denyUnknownFields") {
opts.deny_unknown_fields = true;
}
let tag = extract_named_string(args, "tag");
let content = extract_named_string(args, "content");
let untagged = has_flag(args, "untagged");
let externally_tagged = has_flag(args, "externallyTagged");
if untagged {
opts.tagging = TaggingMode::Untagged;
} else if externally_tagged {
opts.tagging = TaggingMode::ExternallyTagged;
} else if let Some(tag_name) = tag {
if let Some(content_name) = content {
opts.tagging = TaggingMode::AdjacentlyTagged {
tag: tag_name,
content: content_name,
};
} else {
opts.tagging = TaggingMode::InternallyTagged { tag: tag_name };
}
}
}
opts
}
pub fn tag_field(&self) -> Option<&str> {
match &self.tagging {
TaggingMode::InternallyTagged { tag } => Some(tag.as_str()),
TaggingMode::AdjacentlyTagged { tag, .. } => Some(tag.as_str()),
_ => None,
}
}
pub fn tag_field_or_default(&self) -> &str {
match &self.tagging {
TaggingMode::InternallyTagged { tag } => tag.as_str(),
TaggingMode::AdjacentlyTagged { tag, .. } => tag.as_str(),
_ => "__type",
}
}
pub fn content_field(&self) -> Option<&str> {
match &self.tagging {
TaggingMode::AdjacentlyTagged { content, .. } => Some(content.as_str()),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SerdeFieldOptions {
pub skip: bool,
pub skip_serializing: bool,
pub skip_deserializing: bool,
pub rename: Option<String>,
pub default: bool,
pub default_expr: Option<String>,
pub flatten: bool,
pub validators: Vec<ValidatorSpec>,
pub serialize_with: Option<String>,
pub deserialize_with: Option<String>,
pub format: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct SerdeFieldParseResult {
pub options: SerdeFieldOptions,
pub diagnostics: DiagnosticCollector,
}
impl SerdeFieldOptions {
pub fn from_decorators(decorators: &[DecoratorIR], field_name: &str) -> SerdeFieldParseResult {
let mut opts = Self::default();
let mut diagnostics = DiagnosticCollector::new();
for decorator in decorators {
if !decorator.name.eq_ignore_ascii_case("serde") {
continue;
}
let args = decorator.args_src.trim();
let decorator_span = decorator.span;
if has_flag(args, "skip") {
opts.skip = true;
}
if has_flag(args, "skipSerializing") {
opts.skip_serializing = true;
}
if has_flag(args, "skipDeserializing") {
opts.skip_deserializing = true;
}
if has_flag(args, "flatten") {
opts.flatten = true;
}
if let Some(default_expr) = extract_named_string(args, "default") {
opts.default = true;
opts.default_expr = Some(default_expr);
} else if has_flag(args, "default") {
opts.default = true;
}
if let Some(rename) = extract_named_string(args, "rename") {
opts.rename = Some(rename);
}
if let Some(fn_name) = extract_named_string(args, "serializeWith") {
opts.serialize_with = Some(fn_name);
}
if let Some(fn_name) = extract_named_string(args, "deserializeWith") {
opts.deserialize_with = Some(fn_name);
}
if let Some(format) = extract_named_string(args, "format") {
opts.format = Some(format);
}
let validators = extract_validators(args, decorator_span, field_name, &mut diagnostics);
opts.validators.extend(validators);
}
SerdeFieldParseResult {
options: opts,
diagnostics,
}
}
pub fn should_serialize(&self) -> bool {
!self.skip && !self.skip_serializing
}
pub fn should_deserialize(&self) -> bool {
!self.skip && !self.skip_deserializing
}
}