Skip to content

[WJ-1168] Add proc macro for generating relation structs and methods - #3039

Open
emmiegit wants to merge 104 commits into
developfrom
WJ-1168-site-ban-macro
Open

[WJ-1168] Add proc macro for generating relation structs and methods#3039
emmiegit wants to merge 104 commits into
developfrom
WJ-1168-site-ban-macro

Conversation

@emmiegit

@emmiegit emmiegit commented Jul 28, 2026

Copy link
Copy Markdown
Member

Up to this point, we used a normal macro_rules! macro to generate the code for relations. This had a few problems, such as it being cumbersome to disable generation of creation structs or methods, and the inability to exclude metadata as a field if it wasn't used for that relation (e.g. is ()).

With the new requirement to permit disabling remove_*() method generation as well, I decided that the code was getting too complicated and it was time to use a procedural macro to generate the code instead. This allows for specifying a relation in a more readable syntax, as well as handling some niceties for us (such as doing the PascalCase to snake_case conversion for fields automatically).

The arguments accepted are as follows:

impl_relation! {
    // Required. Name of relation in PascalCase.
    name => NameOfRelation,

    // Required. Name and object type of "dest".
    dest => destination_field: Page,

    // Required. Name and object type of "from".
    from => from_field: User,

    // Optional. Associated data type. Default is ().
    data => RelationExtraData,

    // Optional. How to generate the creation code for the relation. Default is true.
    //
    // Values are:
    //  * true   - Generate public struct and public creation method.
    //  * false  - Generate public struct and private "inner" creation method.
    //             Public creation method must be defined by the programmer.
    //  * extern - Generate neither a struct nor creation method.
    //             Both must be defined by the programmer.
    create_fn => false,

    // Optional. How to generate the removal code for the relation. Default is true.
    // Same values as above.
    remove_fn => true,
}

With this done, I changed the site member and ban relations to add the audit logging, and did some refactoring to make the struct handling in endpoints/ consistent.

Naturally, I also deleted the old macro definition.

@emmiegit
emmiegit requested review from WackDog and Zokhoi July 28, 2026 08:08
@emmiegit emmiegit self-assigned this Jul 28, 2026
@emmiegit
emmiegit force-pushed the WJ-1168-site-ban-macro branch 4 times, most recently from a397e57 to 503186b Compare July 28, 2026 09:05
@emmiegit
emmiegit marked this pull request as ready for review July 28, 2026 09:09
@emmiegit
emmiegit force-pushed the WJ-1168-site-ban-macro branch from 503186b to 2911363 Compare July 28, 2026 09:25
@emmiegit
emmiegit force-pushed the WJ-1168-site-ban-macro branch from 2911363 to 7fbe212 Compare July 28, 2026 09:28
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 52.28758% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 26.54%. Comparing base (a76ba79) to head (c2be791).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
deepwell/src/services/audit/structs.rs 18.75% 13 Missing ⚠️
deepwell/src/services/relation/user_block.rs 0.00% 11 Missing ⚠️
deepwell/src/services/relation/user_bot_owner.rs 0.00% 11 Missing ⚠️
deepwell/src/services/relation/user_follow.rs 0.00% 10 Missing ⚠️
deepwell/relation-impl-derive/src/types.rs 0.00% 6 Missing ⚠️
deepwell/src/endpoints/site_ban.rs 66.66% 6 Missing ⚠️
deepwell/relation-impl-derive/src/util.rs 0.00% 4 Missing ⚠️
deepwell/src/endpoints/site_member.rs 33.33% 4 Missing ⚠️
deepwell/src/services/relation/page_star.rs 0.00% 3 Missing ⚠️
deepwell/src/services/relation/page_watch.rs 0.00% 3 Missing ⚠️
... and 1 more
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #3039      +/-   ##
===========================================
- Coverage    26.62%   26.54%   -0.09%     
===========================================
  Files          198      201       +3     
  Lines        13451    13470      +19     
===========================================
- Hits          3582     3576       -6     
- Misses        9869     9894      +25     
Files with missing lines Coverage Δ
deepwell/src/services/relation/mod.rs 63.86% <ø> (ø)
deepwell/src/services/relation/site_ban.rs 93.04% <100.00%> (+0.45%) ⬆️
deepwell/src/services/relation/site_user.rs 35.61% <100.00%> (+7.92%) ⬆️
deepwell/src/services/site/service.rs 37.44% <ø> (ø)
deepwell/src/services/relation/site_member.rs 95.83% <95.12%> (-4.17%) ⬇️
deepwell/src/services/relation/page_star.rs 0.00% <0.00%> (ø)
deepwell/src/services/relation/page_watch.rs 0.00% <0.00%> (ø)
deepwell/relation-impl-derive/src/util.rs 0.00% <0.00%> (ø)
deepwell/src/endpoints/site_member.rs 51.61% <33.33%> (-3.95%) ⬇️
deepwell/relation-impl-derive/src/types.rs 0.00% <0.00%> (ø)
... and 5 more

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@emmiegit
emmiegit enabled auto-merge July 28, 2026 10:26
dest => bot_user: User,
from => owner_user: User,
data => UserBotMetadata,
create_fn => false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be create_fn => extern rather than false? The previous invocation used NO_CREATE_IMPL_OR_STRUCT and the parser test also models UserBotOwner using extern . My understanding is that false still generates the public creation struct, whereas extern generates neither the struct or method.

@emmiegit emmiegit Jul 30, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it's false, it creates a private creation method and struct, which is used in the custom public creation method. But you're right, there isn't a way to disable public struct creation except with extern; we need an option for "create private/inner, but nothing public".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the PR to add separate settings for "create private/inner struct+method only" and "create private/inner struct+method, and a public struct for convenience".

Comment thread deepwell/relation-impl-derive/src/case.rs
use crate::types::{GenerateMethod, RelationObjectType};
use syn::Type;

#[test]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: would it be useful to add compile tests for the generated output, perhaps with trybuild?

@emmiegit emmiegit Jul 30, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's worth considering. I originally thoguht about doing tests (in the proc macro crate) for parsing and expansion, but then I thought it would be easier to just ensure deepwell itself built rather than setting up a duplicate system for testing. But maybe trybuild is closer to what I want here.

If I do implement this, it would be in a follow-up PR.

Comment thread deepwell/src/services/relation/site_member.rs
@emmiegit
emmiegit disabled auto-merge July 28, 2026 13:43
@emmiegit
emmiegit force-pushed the WJ-1168-site-ban-macro branch from 0f86650 to 4fbd1a8 Compare July 30, 2026 05:31
@emmiegit
emmiegit force-pushed the WJ-1168-site-ban-macro branch from 4fbd1a8 to c2be791 Compare July 30, 2026 06:23
Comment on lines +397 to +399
} else {
quote! {}
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does remove_fn => private_only currently leave the generated method without its input struct?
For PrivateWithoutStruct, generate_pub_struct is false, so this branch emits no Remove{Relation} struct. However, the generated remove_*_inner() method below still destructures Remove{Relation}. Should this mode generate a private removal struct, or should private_only not be supported for remove_fn?

}

// Designate how the remove method should be generated
// This key is optional, default is "true".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this say the default is public, with the example using remove_fn => public? The parser doesn't accept true anymore

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants