Skip to content

Commit

Permalink
feat: Add field_ne methods (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
poi2 authored Jun 19, 2023
1 parent 82ccbe9 commit 48a6713
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "fluent_field_assertions"
description = "FluentFieldAssertions is a procedural macro for generating fluent assertions on fields."
version = "0.1.1"
version = "0.1.2"
edition = "2021"
authors = ["Daisuke Ito <daisuke.ito.cs@gmail.com>"]
license = "MIT"
Expand Down
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ struct User {
name: String,
}

User {
let user = User {
id: 1,
name: "Alice".to_string(),
}
.id_eq(1)
.name_eq("Alice".to_string());
};

user.id_eq(1)
.name_eq("Alice".to_string());

user.id_ne(2)
.name_ne("Bob".to_string());
```

You can also use in generic struct.
Expand All @@ -38,5 +42,9 @@ where
y: T,
}

Point { x: 1, y: 2 }.x_eq(1).y_eq(2);
let point = Point { x: 1, y: 2 };

point.x_eq(1).y_eq(2);

point.x_ne(9).y_ne(9);
```
51 changes: 42 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use syn::{
parse_macro_input, Data, DataStruct, DeriveInput, Field, ImplGenerics, TypeGenerics,
parse_macro_input, Data, DataStruct, DeriveInput, Field, ImplGenerics, Type, TypeGenerics,
WhereClause,
};

Expand All @@ -23,12 +23,16 @@ extern crate proc_macro2;
/// name: String,
/// }
///
/// User {
/// let user = User {
/// id: 1,
/// name: "Alice".to_string(),
/// }
/// .id_eq(1)
/// .name_eq("Alice".to_string());
/// };
///
/// user.id_eq(1)
/// .name_eq("Alice".to_string());
///
/// user.id_ne(2)
/// .name_ne("Bob".to_string());
/// ```
///
/// # Example for generics struct
Expand All @@ -45,7 +49,10 @@ extern crate proc_macro2;
/// y: T,
/// }
///
/// Point { x: 1, y: 2 }.x_eq(1).y_eq(2);
/// let point = Point { x: 1, y: 2 };
///
/// point.x_eq(1).y_eq(2);
/// point.x_ne(9).y_ne(9);
/// ```
#[proc_macro_derive(FluentFieldAssertions)]
pub fn fluent_field_assertions(input: TokenStream) -> TokenStream {
Expand All @@ -55,7 +62,10 @@ pub fn fluent_field_assertions(input: TokenStream) -> TokenStream {

let gen = if let Data::Struct(DataStruct { ref fields, .. }) = ast.data {
let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl();
let method_tokens = fields.iter().map(|field| generate_method(field)).collect();
let method_tokens = fields
.iter()
.flat_map(|field| generate_methods(field))
.collect();

generate_impl(
impl_generics,
Expand Down Expand Up @@ -85,14 +95,22 @@ fn generate_impl(
}
}

fn generate_method(field: &Field) -> TokenStream2 {
fn generate_methods(field: &Field) -> Vec<TokenStream2> {
let field_name = field
.clone()
.ident
.unwrap_or_else(|| panic!("Field name must be present."));
let method_name = Ident::new(&format!("{}_eq", field_name), Span::call_site());
let field_type = field.ty.clone();

vec![
generate_eq_method(&field_name, &field_type),
generate_ne_method(&field_name, &field_type),
]
}

fn generate_eq_method(field_name: &Ident, field_type: &Type) -> TokenStream2 {
let method_name = Ident::new(&format!("{}_eq", field_name), Span::call_site());

quote! {
#[inline(always)]
fn #method_name(&self, expected: #field_type) -> &Self
Expand All @@ -104,3 +122,18 @@ fn generate_method(field: &Field) -> TokenStream2 {
}
}
}

fn generate_ne_method(field_name: &Ident, field_type: &Type) -> TokenStream2 {
let method_name = Ident::new(&format!("{}_ne", field_name), Span::call_site());

quote! {
#[inline(always)]
fn #method_name(&self, expected: #field_type) -> &Self
where
#field_type: Eq + core::fmt::Debug
{
assert_ne!(self.#field_name, expected);
self
}
}
}
20 changes: 18 additions & 2 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,31 @@ mod test {
#[case::valid_name("Alice".to_string())]
#[should_panic]
#[case::invalid_name("Bob".to_string())]
fn can_verify_struct(#[case] expected: String) {
fn can_be_checked_equal_of_struct(#[case] expected: String) {
alice().name_eq(expected);
}

#[rstest]
#[should_panic]
#[case::valid_name("Alice".to_string())]
#[case::invalid_name("Bob".to_string())]
fn can_be_checked_not_equal_of_struct(#[case] expected: String) {
alice().name_ne(expected);
}

#[rstest]
#[case::valid_value("Hello, world!".to_string())]
#[should_panic]
#[case::invalid_value("Hello, Rust!".to_string())]
fn can_verify_for_generics_struct(#[case] expected: String) {
fn can_be_checked_equal_of_generics_struct(#[case] expected: String) {
hello_world().value_eq(expected);
}

#[rstest]
#[should_panic]
#[case::valid_value("Hello, world!".to_string())]
#[case::invalid_value("Hello, Rust!".to_string())]
fn can_be_checked_not_equal_of_generics_struct(#[case] expected: String) {
hello_world().value_ne(expected);
}
}

0 comments on commit 48a6713

Please sign in to comment.