rust-payjoin icon indicating copy to clipboard operation
rust-payjoin copied to clipboard

Remove `url::Url` types from public API

Open DanGould opened this issue 10 months ago • 4 comments

Our dependency is twofold, in function arguments and in errors. We can more easily maintain stability of our public API if we internalize our url dependencies in both function arguments and in error variants.

Using url::Url in the public API may be a form of tech debt, in that we took it on to get the API out without full consideration at the time the dep was first introduced by Kixunil.

Don't' depend on url::Url in function arguments

Instead, take Url arguments as IntoUrl trait implementations in core typestate machine instead of url::Url.

We have to re-export url::Url, bind to it, and have downstream implementers manually parse URLs before payjoin will accept them as arguments to many functions that produce requests e.g. Sender::extract_v2.

Instead, an IntoUrl trait like reqwest over &'a str, &'a String, String that handles Url validation inside the typestate machine would abstract this burden from an end user and make one less error for them to handle since it'd be encapsulated in our variants resulting from those extract or setup functions.

related: #459

Don't depend on url in public error variants.

We have no explicit url::ParseError variants in our Error types because they're only internal variants, but if we were to make those variants public we would. We also make downstream users construct their own Urls to pass to our functions which makes them handle the ParseError on their own rather than encapsulating that logic and error handling in our extract_ functions or similar.

related: #403

DanGould avatar Jan 27 '25 16:01 DanGould

reqwest's IntoUrl is sealed, but what you're thinking of wouldn't be, right?

nothingmuch avatar Jan 28 '25 00:01 nothingmuch

It would be sealed and we'd implement it for &'a str, &'a String and String just like reqwest.

DanGould avatar Jan 28 '25 02:01 DanGould

I realize now that reqwest also re-exports url::Url, which is nice for convenience. There are circumstances where you want to parse URLs early, as in payjoin-cli configuration, and having the preferred type available alongside IntoUrl, with IntoUrl trait implementations on &Url and Url even more ergonomic.

DanGould avatar Jan 30 '25 19:01 DanGould

This is somewhat covered by #520, we're not going to remove url::Url (immediately), but we are using IntoUrl to make the signatures more ergonomic. Whether we want to depend on the url::ParseUrl variants in the public API is a question I haven't answered yet, but am leaning to NOT. Better if we encapsulate it in our own so that downstream won't get a break from the dependency being upgraded.

DanGould avatar Mar 20 '25 18:03 DanGould

From @DanGould "If we get rid of this [impl IntoUrl for [url::]Url], then we can use string types everywhere" https://docs.rs/payjoin/latest/payjoin/trait.IntoUrl.html#implementors

arminsabouri avatar Sep 08 '25 17:09 arminsabouri

While #1057 removed the use of Url in function arguments, it's still in the public API. Reopening

A typesafe future proof solution would be to introduce

struct Url(url::Url);

impl Url {

    fn as_str(&self) -> &str;

    fn to_string(&self) -> String;
}

impl IntoUrl for Url;
impl IntoUrl for &Url;

and then return this Url type from the remaining functions.

e.g.

PayjoinExtras::endpoint() -> Url PjParams::endpoint() -> Url

DanGould avatar Sep 12 '25 16:09 DanGould

After work on #1077 we realized that perhaps we want to move towards our own implementation of payjoin::Url instead of trying to marry the url crate with our halfway approach.

However we still need to decide if that is the best approach, there is still the option of using the url crate with no default features and enabling only the serde feature which removes some of the more problematic features like idna

Ideally we can use our on payjoin::Url with string manipulation methods similar to those on the url crate that we use. I need to explore exactly what is still needed as Dan pointed out we can probably remove parse() all together.

Url methods we wwould need to recreate

  • path_segments(): needed for PjParam parse but can possibly be done with some string manipulation
  • fragment(): again used on PjParam parse but seems like could be done with out own string manipulation
  • parse(): this can probably be removed
  • query(): This is only used in v1 receiver so perhaps there is a hacky way to avoid this?

Action Items right now are to remove url::Url from payjoin-test-utils and the still publicly available Request::url before moving on to start getting rid of the url crate entirely

benalleng avatar Sep 24 '25 21:09 benalleng

From @DanGould Remove public accessers from struct req fields. Use getters for read only field access. Get a string method to get the contents of Request::url field (or otherwise document exactly why we need to reteurn a proper Url from a getter (either Request::url() or Request::url_as_str()` or similar)

arminsabouri avatar Sep 29 '25 17:09 arminsabouri

#1121 should close this issue but does not remove the url crate as a dep. I think we should make a new issue for that as it seems outside of the scope of this issue

benalleng avatar Sep 29 '25 20:09 benalleng

Noted un-completed by #1141

DanGould avatar Oct 03 '25 15:10 DanGould

closed by #1141

DanGould avatar Oct 03 '25 20:10 DanGould