Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to Activity design #22

Open
vionya opened this issue Sep 23, 2022 · 8 comments
Open

Changes to Activity design #22

vionya opened this issue Sep 23, 2022 · 8 comments
Assignees
Labels
bikeshedding Discuss potential library changes

Comments

@vionya
Copy link
Owner

vionya commented Sep 23, 2022

Some recent issues have brought up some concerns with the current structure of the Activity struct and the way it works. I'm currently working on re-designing these interfaces to address these issues and hopefully be overall easier to use. These changes are heavily breaking and would/will accompany a future major version update.

Changes

Note
Activity and ActivityBuilder refer to all models provided by the library (i.e. Timestamps and TimestampsBuilder)

My current idea is to separate the current usage flow into distinct Activity and ActivityBuilder structs, implementing a consuming builder design pattern. My working design for this would look something like this in practice:

// A default `ActivityBuilder` supports chaining similar to that of the current `Activity` struct
let mut activity = activity::ActivityBuilder::default()
    .state("State")
    .details("Details")
    .assets(
        activity::AssetsBuilder::default()
            .large_image("large-image")
            .large_text("Large text")
            .build(),
    )
    .buttons(vec![activity::Button::new(
        "A button",
        "https://example.com",
    )])
    .timestamps(activity::TimestampsBuilder::default().start(1).build())
    .build();

// Individual fields on a built model can now be updated like so
activity.set_party(
    activity::PartyBuilder::default()
        .id("some-id")
        .size([1, 3])
        .build(),
);

This showcases several things:

  • The ActivityBuilder structs support chaining similar to that of the current design
    • The setters are consumptive, so all calls to them leading up to .build() must be chained (or a new reference has to be created for each setter called)
  • The Activity structs now have set_* methods which take an &mut self. This makes it much easier to update individual model values after the model has been built (Useless lifetimes #19)

Unrelated to the updates to chaining and construction, none of the library's models require lifetimes anymore, and accept anything that satisfies impl ToString where an &str was previously required - which was the reason the lifetimes were previously in place (also #19).

Discussion

I'm raising this issue to get feedback about the design as I've presented it, and if there are improvements to be had.

My main concern is the added boilerplate. I don't think it's a big issue for the large ActivityBuilder, since that's already a pretty long chain in practice, but as seen in the example code above, it definitely uglifies lines such as

    .timestamps(activity::TimestampsBuilder::default().start(1).build())

Is this beyond the acceptable amount of boilerplate, are there alternative solutions? One idea I scrapped was adding simple ::new functions for models like Timestamps instead of implementing a builder - the issue here is that this would then require arguments to be passed in an Option-friendly form, which still adds boilerplate.


This was a lot of words, so I thank any who read them all, and I'd appreciate feedback/discussion :)

@vionya vionya added the bikeshedding Discuss potential library changes label Sep 23, 2022
@vionya vionya self-assigned this Sep 23, 2022
@vionya vionya pinned this issue Sep 23, 2022
@Bowarc
Copy link
Contributor

Bowarc commented Jan 3, 2023

Sorry for the very late response 😅

About the builder patern, check derive_builder if you're not using it yet

About boilerplate, would a impl of From<(Option<i64>, Option<i64>)> on Timestamps (for example) be usefull? That would create lines like

 .timestamps(activity::Timestamps::from((Some(1), None))

which imo is not that bad

@vionya
Copy link
Owner Author

vionya commented Jan 6, 2023

Hey, appreciate any response at all 😄!

About the builder patern, check derive_builder if you're not using it yet

I've checked out that crate, and I really like what it does! My goal is to take design cues from the generated builders in the final product, absolutely. I'm slightly hesitant on including it as a dependency though, since it's not a difficult implementation, and I'd like to keep the crate relatively lean.

About boilerplate, would a impl of From<Option<i64>, Option<i64>) on Timestamps (for example) be usefull? That would create lines like

 .timestamps(activity::Timestamps::from((Some(1), None))

wich imo is not that bad

I really like this! It would make it a lot easier to construct these smaller models without tons of chained calls. Do you think this would be a good thing to implement alongside the verbose option (like how I constructed the Timestamps in the original issue)? That way, both options are available.

Thanks again for the response!

@Bowarc
Copy link
Contributor

Bowarc commented Mar 12, 2023

Late again 😅

Do you think this would be a good thing to implement alongside the verbose option (like how I constructed the Timestamps in the original issue)? That way, both options are available.

Yes! That would be preferable imo.

@vionya
Copy link
Owner Author

vionya commented Mar 12, 2023

Alright, sounds good!

@Radiicall
Copy link
Contributor

This is a good idea! I've been using this crate in my project for a while and I have been struggling with exactly this problem of ownership when trying to build an activity.

My current code for getting around this issue is very messy so having this fixed would be awesome!

@Eveeifyeve
Copy link

Eveeifyeve commented Sep 18, 2024

Hey, appreciate any response at all 😄!

About the builder patern, check derive_builder if you're not using it yet

I've checked out that crate, and I really like what it does! My goal is to take design cues from the generated builders in the final product, absolutely. I'm slightly hesitant on including it as a dependency though, since it's not a difficult implementation, and I'd like to keep the crate relatively lean.

About boilerplate, would a impl of From<Option<i64>, Option<i64>) on Timestamps (for example) be usefull? That would create lines like

 .timestamps(activity::Timestamps::from((Some(1), None))

wich imo is not that bad

I really like this! It would make it a lot easier to construct these smaller models without tons of chained calls. Do you think this would be a good thing to implement alongside the verbose option (like how I constructed the Timestamps in the original issue)? That way, both options are available.

Thanks again for the response!

Same with party it could be:

.timestamps(activity::Party::new( Some("some-id"), Some([1,3]) ));

@Bowarc
Copy link
Contributor

Bowarc commented Sep 18, 2024

Hey, appreciate any response at all 😄!

About the builder patern, check derive_builder if you're not using it yet

I've checked out that crate, and I really like what it does! My goal is to take design cues from the generated builders in the final product, absolutely. I'm slightly hesitant on including it as a dependency though, since it's not a difficult implementation, and I'd like to keep the crate relatively lean.

About boilerplate, would a impl of From<Option<i64>, Option<i64>) on Timestamps (for example) be usefull? That would create lines like

 .timestamps(activity::Timestamps::from((Some(1), None))

wich imo is not that bad

I really like this! It would make it a lot easier to construct these smaller models without tons of chained calls. Do you think this would be a good thing to implement alongside the verbose option (like how I constructed the Timestamps in the original issue)? That way, both options are available.
Thanks again for the response!

Same with party it could be:

.timestamps(activity::Party::new( Some("some-id"), Some([1,3]) ));

Implementing From allows us to use impl Into<activity::Timestamps> as method param type
which would make .timestamps((Some(1),None)) possible (or use any type that implements Into Timestamps)

But yea it would be cool to add this pattern for every method

@vionya
Copy link
Owner Author

vionya commented Sep 28, 2024

i'll plan to implement this pattern for every method :) (except for where it makes no sense to do so)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bikeshedding Discuss potential library changes
Projects
None yet
Development

No branches or pull requests

4 participants