Skip to content

Commit

Permalink
feat: validate field and variant name uniqueness
Browse files Browse the repository at this point in the history
Ensure that all fields in a struct or enum variant are uniquely named,
as well as all variants within a single enum.
  • Loading branch information
dnaka91 committed Oct 23, 2023
1 parent f31f94e commit 9137e20
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 16 deletions.
25 changes: 13 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 25 additions & 4 deletions crates/stef-compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ pub use ids::{DuplicateFieldId, DuplicateId, DuplicateVariantId};
use stef_parser::{Definition, Schema};
use thiserror::Error;

use self::names::{DuplicateFieldName, DuplicateName};

mod ids;
mod names;

#[derive(Debug, Error)]
pub enum Error {
#[error("duplicate ID found")]
DuplicateId(#[from] DuplicateId),
#[error("duplicate name found")]
DuplicateName(#[from] DuplicateName),
}

impl From<DuplicateFieldId> for Error {
Expand All @@ -16,15 +21,31 @@ impl From<DuplicateFieldId> for Error {
}
}

impl From<DuplicateFieldName> for Error {
fn from(v: DuplicateFieldName) -> Self {
Self::DuplicateName(v.into())
}
}

pub fn validate_schema(value: &Schema<'_>) -> Result<(), Error> {
value.definitions.iter().try_for_each(validate_definition)
}

fn validate_definition(value: &Definition<'_>) -> Result<(), Error> {
match value {
Definition::Module(m) => m.definitions.iter().try_for_each(validate_definition),
Definition::Struct(s) => ids::validate_struct_ids(s).map_err(Into::into),
Definition::Enum(e) => ids::validate_enum_ids(e).map_err(Into::into),
Definition::TypeAlias(_) | Definition::Const(_) | Definition::Import(_) => Ok(()),
Definition::Module(m) => {
m.definitions.iter().try_for_each(validate_definition)?;
}
Definition::Struct(s) => {
ids::validate_struct_ids(s)?;
names::validate_struct_names(s)?;
}
Definition::Enum(e) => {
ids::validate_enum_ids(e)?;
names::validate_enum_names(e)?;
}
Definition::TypeAlias(_) | Definition::Const(_) | Definition::Import(_) => {}
}

Ok(())
}
73 changes: 73 additions & 0 deletions crates/stef-compiler/src/names.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::collections::HashSet;

use stef_parser::{Enum, Fields, Struct};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum DuplicateName {
#[error("duplicate name in an enum variant")]
EnumVariant(#[from] DuplicateVariantName),
#[error("duplicate name in a field")]
Field(#[from] DuplicateFieldName),
}

#[derive(Debug, Error)]
#[error("duplicate variant name `{name}` in enum")]
pub struct DuplicateVariantName {
pub name: String,
}

#[derive(Debug, Error)]
#[error("duplicate field name `{name}`")]
pub struct DuplicateFieldName {
pub name: String,
}

/// Ensure all field names inside a struct are unique.
pub(crate) fn validate_struct_names(value: &Struct<'_>) -> Result<(), DuplicateFieldName> {
validate_field_names(&value.fields)
}

/// Ensure all names inside an enum are unique, which means all variants have a unique name, plus
/// all potential fields in a variant are unique (within that variant).
pub(crate) fn validate_enum_names(value: &Enum<'_>) -> Result<(), DuplicateName> {
let mut visited = HashSet::with_capacity(value.variants.len());
value
.variants
.iter()
.find_map(|variant| {
(!visited.insert(variant.name))
.then(|| {
DuplicateVariantName {
name: variant.name.to_owned(),
}
.into()
})
.or_else(|| {
validate_field_names(&variant.fields)
.err()
.map(DuplicateName::from)
})
})
.map_or(Ok(()), Err)
}

/// Ensure all field names of a struct or enum are unique.
fn validate_field_names(value: &Fields) -> Result<(), DuplicateFieldName> {
match value {
Fields::Named(named) => {
let mut visited = HashSet::with_capacity(named.len());
named
.iter()
.find_map(|field| {
(!visited.insert(field.name)).then(|| DuplicateFieldName {
name: field.name.to_owned(),
})
})
.map_or(Ok(()), Err)?;
}
Fields::Unnamed(_) | Fields::Unit => {}
}

Ok(())
}

0 comments on commit 9137e20

Please sign in to comment.