diff --git a/Cargo.toml b/Cargo.toml index c4f60196e8..80b5201f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ path = "src/rust/lib.rs" [dependencies] clap = "2" +http = "^0.2" hyper = "0.14" mime = "0.2" rustc-serialize = "*" @@ -30,4 +31,4 @@ strsim = "*" tokio = "^ 1.0" hyper-rustls = "^0.22" itertools = "^ 0.10" -tower-service = "^0.3" \ No newline at end of file +tower-service = "^0.3" diff --git a/changelog.md b/changelog.md index 5084d83410..dda9907adf 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ +## api/cli-vNEXT (YYYY-MM-DD) + +- New `GetToken` trait for custom ways of specifying a token. The latter can now be a String or be + `NoToken` as well. +- Upgrade `mkdocs` to a more recent version that doesn't break in more recent python interpreters. + ## api/cli-v3.0.0 (2022-3-8) - Support for yup-oauth 0.6 diff --git a/requirements.txt b/requirements.txt index 7ef6fdf2a5..89ad107608 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -mako==1.1.4 +mako==1.2.2 pyyaml<6 -mkdocs==0.11 +mkdocs==1.3.1 pytest pytest-cov codecov diff --git a/src/generator/templates/Cargo.toml.mako b/src/generator/templates/Cargo.toml.mako index 1d23f7138c..7885c43435 100644 --- a/src/generator/templates/Cargo.toml.mako +++ b/src/generator/templates/Cargo.toml.mako @@ -26,12 +26,16 @@ path = "src/main.rs" % endif [dependencies] +## TODO: temporary, remove when yup-oauth2 is optional +anyhow = "^ 1.0" hyper-rustls = "0.23.0" ## Must match the one hyper uses, otherwise there are duplicate similarly named `Mime` structs mime = "^ 0.2.0" serde = "^ 1.0" serde_json = "^ 1.0" serde_derive = "^ 1.0" +## TODO: Make yup-oauth2 optional +## yup-oauth2 = { version = "^ 7.0", optional = true } yup-oauth2 = "^ 7.0" itertools = "^ 0.10" % for dep in cargo.get('dependencies', list()): @@ -41,7 +45,7 @@ ${dep} <% api_name = util.library_name() crate_name_we_depend_on = None - + if make.depends_on_suffix is not None: crate_name_we_depend_on = library_to_crate_name(api_name, suffix=make.depends_on_suffix) %>\ @@ -53,3 +57,7 @@ ${dep} path = "../${api_name}" version = "${util.crate_version()}" % endif + +## TODO: Make yup-oauth2 optional +# [features] +# default = ["yup-oauth2"] diff --git a/src/generator/templates/api/api.rs.mako b/src/generator/templates/api/api.rs.mako index b5dfd40a1d..cc81bcf07d 100644 --- a/src/generator/templates/api/api.rs.mako +++ b/src/generator/templates/api/api.rs.mako @@ -30,7 +30,7 @@ use http::Uri; use hyper::client::connect; use tokio::io::{AsyncRead, AsyncWrite}; use tower_service; -use crate::client; +use crate::{client, client::GetToken}; // ############## // UTILITIES ### @@ -55,7 +55,7 @@ ${lib.hub_usage_example(c)}\ #[derive(Clone)] pub struct ${hub_type}${ht_params} { pub client: hyper::Client, - pub auth: oauth2::authenticator::Authenticator, + pub auth: Box, _user_agent: String, _base_url: String, _root_url: String, @@ -65,10 +65,10 @@ impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> client::Hub for ${hub_type}${ht_para impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> ${hub_type}${ht_params} { - pub fn new(client: hyper::Client, authenticator: oauth2::authenticator::Authenticator) -> ${hub_type}${ht_params} { + pub fn new(client: hyper::Client, auth: A) -> ${hub_type}${ht_params} { ${hub_type} { client, - auth: authenticator, + auth: Box::new(auth), _user_agent: "${default_user_agent}".to_string(), _base_url: "${baseUrl}".to_string(), _root_url: "${rootUrl}".to_string(), diff --git a/src/generator/templates/api/lib/mbuild.mako b/src/generator/templates/api/lib/mbuild.mako index 18007e04fa..7ba7c06a75 100644 --- a/src/generator/templates/api/lib/mbuild.mako +++ b/src/generator/templates/api/lib/mbuild.mako @@ -706,10 +706,21 @@ else { loop { % if default_scope: - let token = match ${auth_call}.token(&self.${api.properties.scopes}.keys().collect::>()[..]).await { - Ok(token) => token.clone(), + let token = match ${auth_call}.get_token(&self.${api.properties.scopes}.keys().map(String::as_str).collect::>()[..]).await { + // TODO: remove Ok / Err branches + Ok(Some(token)) => token.clone(), + Ok(None) => { + let err = oauth2::Error::OtherError(anyhow::Error::msg("unknown error occurred while generating oauth2 token")); + match dlg.token(&err) { + Some(token) => token, + None => { + ${delegate_finish}(false); + return Err(client::Error::MissingToken(err)) + } + } + } Err(err) => { - match dlg.token(&err) { + match dlg.token(&err) { Some(token) => token, None => { ${delegate_finish}(false); diff --git a/src/generator/templates/cli/mkdocs.yml.mako b/src/generator/templates/cli/mkdocs.yml.mako index 6566f5ad77..251bad2199 100644 --- a/src/generator/templates/cli/mkdocs.yml.mako +++ b/src/generator/templates/cli/mkdocs.yml.mako @@ -14,11 +14,12 @@ repo_url: ${util.github_source_root_url()} docs_dir: ${mkdocs.docs_dir} site_dir: ${mkdocs.site_dir} -pages: -- ['index.md', 'Home'] +nav: +- Home: 'index.md' % for resource in sorted(c.rta_map.keys()): +- '${pretty(resource)}': % for method in sorted(c.rta_map[resource]): -- ['${subcommand_md_filename(resource, method)}', '${pretty(resource)}', '${pretty(method)}'] + - '${pretty(method)}': '${subcommand_md_filename(resource, method)}' % endfor # each method % endfor # each resource diff --git a/src/rust/api/client.rs b/src/rust/api/client.rs index 1c5e04cdbc..25b2459830 100644 --- a/src/rust/api/client.rs +++ b/src/rust/api/client.rs @@ -1,7 +1,9 @@ use std::error; use std::error::Error as StdError; use std::fmt::{self, Display}; +use std::future::Future; use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; +use std::pin::Pin; use std::str::FromStr; use std::thread::sleep; use std::time::Duration; @@ -105,12 +107,13 @@ pub trait Delegate: Send { None } + // TODO: Remove oauth2::Error /// Called whenever the Authenticator didn't yield a token. The delegate /// may attempt to provide one, or just take it as a general information about the /// impending failure. /// The given Error provides information about why the token couldn't be acquired in the /// first place - fn token(&mut self, err: &oauth2::Error) -> Option { + fn token(&mut self, err: &oauth2::Error) -> Option { let _ = err; None } @@ -227,6 +230,7 @@ pub enum Error { /// Neither through the authenticator, nor through the Delegate. MissingAPIKey, + // TODO: Remove oauth2::Error /// We required a Token, but didn't get one from the Authenticator MissingToken(oauth2::Error), @@ -272,6 +276,7 @@ impl Display for Error { writeln!(f, "Bad Request: {}", message)?; Ok(()) } + // TODO: Remove oauth2::Error Error::MissingToken(ref err) => { writeln!(f, "Token retrieval failed with error: {}", err) } @@ -785,3 +790,75 @@ pub async fn get_body_as_string(res_body: &mut hyper::Body) -> String { let res_body_string = String::from_utf8_lossy(&res_body_buf); res_body_string.to_string() } + +// TODO: Simplify this to Option +type TokenResult = std::result::Result, oauth2::Error>; + +pub trait GetToken: GetTokenClone { + /// Called whenever there is the need for an oauth token after + /// the official authenticator implementation didn't provide one, for some reason. + /// If this method returns None as well, the underlying operation will fail + fn get_token<'a>(&'a self, _scopes: &'a [&str]) -> Pin + 'a>> { + Box::pin(async move { Ok(None) }) + } +} + +pub trait GetTokenClone { + fn clone_box(&self) -> Box; +} + +impl GetTokenClone for T +where + T: 'static + GetToken + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_box() + } +} + +impl GetToken for String { + fn get_token<'a>(&'a self, _scopes: &'a [&str]) -> Pin + 'a>> { + Box::pin(async move { Ok(Some(self.clone())) }) + } +} + +/// In the event that the API endpoint does not require an oauth2 token, `NoToken` should be provided to the hub to avoid specifying an +/// authenticator. +#[derive(Default, Clone)] +pub struct NoToken; + +impl GetToken for NoToken {} + +// TODO: Make this optional +// #[cfg(feature = "yup-oauth2")] +mod yup_oauth2_impl { + use core::future::Future; + use core::pin::Pin; + + use super::{GetToken, TokenResult}; + + use tower_service::Service; + use yup_oauth2::authenticator::Authenticator; + use tokio::io::{AsyncRead, AsyncWrite}; + use http::Uri; + use hyper::client::connect::Connection; + + + impl GetToken for Authenticator where + S: Service + Clone + Send + Sync + 'static, + S::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + S::Future: Send + Unpin + 'static, + S::Error: Into> { + fn get_token<'a>(&'a self, scopes: &'a [&str]) -> Pin + 'a>> { + Box::pin(async move { + self.token(scopes).await.map(|t| Some(t.as_str().to_owned())) + }) + } + } +}