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

Make the public API more modular #49

Merged
merged 30 commits into from
Feb 1, 2022
Merged

Make the public API more modular #49

merged 30 commits into from
Feb 1, 2022

Conversation

djc
Copy link
Owner

@djc djc commented Jan 24, 2022

This does way with all top-level functions except init(), while making AuthenticationManager::new() (effectively the same as init()) and the CustomServiceAccount type public. The latter can be constructed directly with from_env(), from_file() and from_json() callers, and can easily be converted to an AuthenticationManager. It also gives access to some of its inner credentials which I think will be useful.

I've also switch the logging provider from log to tracing and gave CustomServiceAccount callers access to its signer.

@flub any thoughts?

Copy link
Contributor

@flub flub left a comment

Choose a reason for hiding this comment

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

The new API to let you create structs directly feels natural, though it is weird to single out the CustomServiceAccount as the only public one. Maybe all should be made public? That way someone could also say use only the DefaultServiceAccount or something like that.

@@ -21,10 +25,66 @@ pub struct AuthenticationManager {
}

impl AuthenticationManager {
pub(crate) fn new(client: HyperClient, service_account: Box<dyn ServiceAccount>) -> Self {
/// Create an `AuthenticationManager` directly from a custom service account
pub fn from_custom_service_account(service_account: CustomServiceAccount) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

is it customary to provide this as well as a From impl? Not that I have any problem with this, just wondering if there is a Rust-wide consensus on this.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Yeah, I was maybe overdoing it on the ergonomics. I've found that often From implementations aren't very easily discoverable, and in my mind gcp_auth is about making this complicated stuff really easy; but maybe we should double down on There's Only One Way To Do It and we should ditch this method in favor of the From impl.

Copy link
Contributor

Choose a reason for hiding this comment

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

The discoverability is fair, but easily solvable by showing it in the struct docs, maybe even with an example.

Copy link
Owner Author

Choose a reason for hiding this comment

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

That's fair. I'm interested what @hrvolapeter thinks.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think both arguments valid. I was trying to find if some other library is having from trait and function as well but wasn't able to find anything. Adding public function is easier then removing it so I would probably just start with From trait and if people are confused we can consider improving docs / making function explicit

src/lib.rs Outdated
/// Initialize GCP authentication
///
/// Returns `AuthenticationManager` which can be used to obtain tokens
/// This is a convenience wrapper around [`AuthenticationManager::new()`].
Copy link
Contributor

Choose a reason for hiding this comment

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

If you don't mind breaking the API why not completely remove this init and rely only on AuthenticationManager::new?

Copy link
Owner Author

Choose a reason for hiding this comment

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

I personally like the shortcut of being able to do gcp_auth::init() without doing any imports, especially in helper CLIs. Seems like a cheap way to get a slicely nicer UX.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have no strong opinions and recognise this is entirely bikeshedding, so if you like to keep both that's perfectly fine. Though in rust I did get very used to the SomeStruct::constructor() way over an init shortcut and when I have the option (I think tracing_subscriber::fmt() is another example) I will still choose the constructor with the struct name in it as it is more descriptive to me to see the struct name being created.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Interesting data point! I think I rather like the import-less initialization aspect to me but I will recognize it's less idiomatic then calling a type constructor.

Again, since @hrvolapeter came up with this API initially I'll let them weigh in before I make any further changes.

Copy link
Collaborator

@hrvolapeter hrvolapeter Jan 31, 2022

Choose a reason for hiding this comment

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

the whole init think is probably just a habit from university, we've been taught that constructor shouldn't have side effects due to weird error handling in C++ constructors which is not really a valid point in rust anymore. init for me is basically constructor with side effects, I think similar approach is quite common in OOP although it would be constructed in 2 steps. I agree that it's probably not the most rust native way.

}

/// The project ID as found in the credentials
pub fn project_id(&self) -> Option<&str> {
Copy link
Contributor

Choose a reason for hiding this comment

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

it seems odd to me to provide two public functions with the same name on the same struct. I realise this is a slightly different signature that the ServiceAccount trait, but this seems hardly worth it?

Copy link
Owner Author

Choose a reason for hiding this comment

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

The ServiceAccount trait is pub(crate), so that implementation is actually not public.

tokens: RwLock::new(HashMap::new()),
})
}

/// The RSA PKCS1 SHA256 [`Signer`] used to sign JWT tokens
pub fn signer(&self) -> &Signer {
Copy link
Contributor

Choose a reason for hiding this comment

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

why make the signer public?

Copy link
Owner Author

Choose a reason for hiding this comment

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

So there's a use case here: ThouCheese/cloud-storage-rs#92. This is for the https://cloud.google.com/storage/docs/access-control/signed-urls feature, which uses the same encryption mechanism as far as I can tell.

More broadly, this is basically code that we already have in gcp_auth, so we might as well expose it to users as an easy API -- they might also want to produce JWTs.

@djc
Copy link
Owner Author

djc commented Jan 25, 2022

The new API to let you create structs directly feels natural, though it is weird to single out the CustomServiceAccount as the only public one. Maybe all should be made public? That way someone could also say use only the DefaultServiceAccount or something like that.

We could definitely do the others. Personally I find the CustomServiceAccount implementation more interesting because it can provide access to the Signer.

Copy link
Collaborator

@hrvolapeter hrvolapeter left a comment

Choose a reason for hiding this comment

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

The proposed changes look good. All open discussions seems reasonable in both ways

@djc
Copy link
Owner Author

djc commented Feb 1, 2022

I removed init() and AuthenticationManager::from_custom_service_account() for now, we could always add them back later. @hrvolapeter shall I publish 0.7 to crates.io?

@djc djc merged commit a3edad5 into master Feb 1, 2022
@djc djc deleted the decouple branch February 1, 2022 09:39
@hrvolapeter
Copy link
Collaborator

Yes please do

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.

3 participants