tonymushah / mangadex-api

A Mangadex API Wrapper for Rust
Apache License 2.0
14 stars 5 forks source link

gondolyr request list #2

Open tonymushah opened 1 year ago

tonymushah commented 1 year ago

i need to know what feature you wanted to add in the crate.

I have a bunch of stuff :sweat_smile: ... There are probably more I can list but this is already a lot.


Notes about some of the custom deserialization I added:

Originally posted by @gondolyr in https://github.com/tonymushah/mangadex-api/issues/1#issuecomment-1462293341

LuMiSxh commented 11 months ago

The API could be made more ergonomic. The builder pattern felt clunky whenever I tried to use my own library.

Could you please describe what feels clunky about it? I'd like to help improve the pattern but since I've not used this wrapper yet (planning to use it in my next project) I can't really make out what feels clunky

gondolyr commented 11 months ago

The API could be made more ergonomic. The builder pattern felt clunky whenever I tried to use my own library.

Could you please describe what feels clunky about it? I'd like to help improve the pattern but since I've not used this wrapper yet (planning to use it in my next project) I can't really make out what feels clunky

In the following example,

let manga = client
    .manga()
    .aggregate()
    .manga_id(&manga_id)
    .translated_language(languages)
    .build()
    .unwrap()
    .send()
    .await;

The build().unwrap() part felt excessive and would be more intuitive to just call send() after building the parameters like the manga ID; this is because of the use of the derive_builder crate (docs). I believe version 3.0.0 refactors this to make it simpler to make requests.

LuMiSxh commented 11 months ago

The API could be made more ergonomic. The builder pattern felt clunky whenever I tried to use my own library.

Could you please describe what feels clunky about it? I'd like to help improve the pattern but since I've not used this wrapper yet (planning to use it in my next project) I can't really make out what feels clunky

In the following example,


let manga = client

    .manga()

    .aggregate()

    .manga_id(&manga_id)

    .translated_language(languages)

    .build()

    .unwrap()

    .send()

    .await;

The build().unwrap() part felt excessive and would be more intuitive to just call send() after building the parameters like the manga ID; this is because of the use of the derive_builder crate (docs). I believe version 3.0.0 refactors this to make it simpler to make requests.

Instead of validating during build(), you could do that during send() and have the unwrap after that and return a Result<>.

But to my knowledge an unwrap is needed somewhere in there, how would you otherwise let the user know something is wrong?

gondolyr commented 5 months ago

The link to burntsushi's comment does not work anymore so here's an alternative: https://red.artemislena.eu/r/rust/comments/gj8inf/rust_structuring_and_handling_errors_in_2020/fqlmknt/?context=3 The article they are referring to is this one here: https://nick.groenen.me/posts/rust-error-handling/

For convenience, I have copied the content here in case the new link goes down as well.

u/burntsushi
> Great article! I agree with a lot of it. > > I do think it would be wise to be a bit more circumspect with recommendations for `thiserror` (and similar error handling helper libraries). In particular, they can have a big impact on compilation times. If you're already using proc macros somewhere, or if your dependency tree is already necessarily large, then `thiserror` seems like a great choice. But otherwise, writing out the impls by hand is very easy to do. I tried it myself and it only took me a couple minutes: > > ```rust > /// WordCountError enumerates all possible errors returned by this library. > #[derive(Debug)] > enum WordCountError { > /// Represents an empty source. For example, an empty text file being given > /// as input to `count_words()`. > EmptySource, > > /// Represents a failure to read from input. > ReadError { source: std::io::Error }, > > /// Represents all other cases of `std::io::Error`. > IOError(std::io::Error), > } > > impl std::error::Error for WordCountError { > fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { > match *self { > WordCountError::EmptySource => None, > WordCountError::ReadError { ref source } => Some(source), > WordCountError::IOError(_) => None, > } > } > } > > impl std::fmt::Display for WordCountError { > fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { > match *self { > WordCountError::EmptySource => { > write!(f, "Source contains no data") > } > WordCountError::ReadError { .. } => { > write!(f, "Read error") > } > WordCountError::IOError(ref err) => { > err.fmt(f) > } > } > } > } > > impl From for WordCountError { > fn from(err: std::io::Error) -> WordCountError { > WordCountError::IOError(err) > } > } > ``` > > In terms of compile times, the `thiserror` version took 5 seconds and 7.5 seconds in debug and release mode, respectively. Without `thiserror` took 0.37 and 0.39 seconds in debug and release mode, respectively. That's a fairly sizable improvement. > I think using `thiserror` makes a lot of sense in many circumstances. For example, if I were starting a new project in Rust at work, then using `thiserror` as a foundation to make quickly building error types in all my internal libraries would be a really nice win. Because writing boiler plate would otherwise (I think) discourage writing out more structured errors, where as `thiserror` makes it deliciously easy and painless. But I think if I were writing an open source library for others to use, then I think avoiding passing on this compile time hit to everyone else in exchange for a couple minutes of writing some very easy code is well worth it.