Merge pull request #885 from AleoHQ/feature-custom-api-route

[CLI] Allow setting custom API endpoint in ENV
This commit is contained in:
Collin Chin 2021-04-22 11:22:01 -07:00 committed by GitHub
commit 4024c1a9b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 37 additions and 31 deletions

View File

@ -22,25 +22,24 @@ use reqwest::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Trait describes API Routes and Request bodies, struct which implements /// API Routes and Request bodies.
/// Route MUST also support Serialize to be usable in Api::run_route(r: Route) /// Structs that implement Route MUST also support Serialize to be usable in Api::run_route(r: Route)
pub trait Route { pub trait Route {
/// Whether to use bearer auth or not. Some routes may have additional /// [`true`] if a route supports bearer authentication.
/// features for logged-in users, so authorization token should be sent /// For example, the login route.
/// if it is created of course
const AUTH: bool; const AUTH: bool;
/// HTTP method to use when requesting /// The HTTP method to use when requesting.
const METHOD: Method; const METHOD: Method;
/// URL path without first forward slash (e.g. v1/package/fetch) /// The URL path without the first forward slash (e.g. v1/package/fetch)
const PATH: &'static str; const PATH: &'static str;
/// Output type for this route. For login it is simple - String /// The output type for this route. For example, the login route output is [`String`].
/// But for other routes may be more complex. /// But for other routes may be more complex.
type Output; type Output;
/// Process reqwest Response and turn it into Output /// Process the reqwest Response and turn it into an Output.
fn process(&self, res: Response) -> Result<Self::Output>; fn process(&self, res: Response) -> Result<Self::Output>;
/// Transform specific status codes into correct errors for this route. /// Transform specific status codes into correct errors for this route.
@ -50,18 +49,18 @@ pub trait Route {
} }
} }
/// REST API handler with reqwest::blocking inside /// REST API handler with reqwest::blocking inside.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Api { pub struct Api {
host: String, host: String,
client: Client, client: Client,
/// Authorization token for API requests /// Authorization token for API requests.
auth_token: Option<String>, auth_token: Option<String>,
} }
impl Api { impl Api {
/// Create new instance of API, set host and Client is going to be /// Returns a new instance of API.
/// created and set automatically /// The set host and Client are created automatically.
pub fn new(host: String, auth_token: Option<String>) -> Api { pub fn new(host: String, auth_token: Option<String>) -> Api {
Api { Api {
client: Client::new(), client: Client::new(),
@ -70,18 +69,23 @@ impl Api {
} }
} }
/// Get token for bearer auth, should be passed into Api through Context pub fn host(&self) -> &str {
&*self.host
}
/// Returns the token for bearer auth, otherwise None.
/// The [`auth_token`] should be passed into the Api through Context.
pub fn auth_token(&self) -> Option<String> { pub fn auth_token(&self) -> Option<String> {
self.auth_token.clone() self.auth_token.clone()
} }
/// Set authorization token for future requests /// Set the authorization token for future requests.
pub fn set_auth_token(&mut self, token: String) { pub fn set_auth_token(&mut self, token: String) {
self.auth_token = Some(token); self.auth_token = Some(token);
} }
/// Run specific route struct. Turn struct into request body /// Run specific route struct. Turn struct into request body
/// and use type constants and Route implementation to get request params /// and use type constants and Route implementation to get request params.
pub fn run_route<T>(&self, route: T) -> Result<T::Output> pub fn run_route<T>(&self, route: T) -> Result<T::Output>
where where
T: Route, T: Route,
@ -100,7 +104,9 @@ impl Api {
}; };
// only one error is possible here // only one error is possible here
let res = res.send().map_err(|_| anyhow!("Unable to connect to Aleo PM"))?; let res = res.send().map_err(|_| {
anyhow!("Unable to connect to Aleo PM. If you specified custom API endpoint, then check the URL for errors")
})?;
// where magic begins // where magic begins
route.process(res) route.process(res)
@ -143,7 +149,7 @@ impl Route for Fetch {
// TODO: we should return 404 on not found author/package // TODO: we should return 404 on not found author/package
// and return BAD_REQUEST if data format is incorrect or some of the arguments // and return BAD_REQUEST if data format is incorrect or some of the arguments
// were not passed // were not passed
StatusCode::NOT_FOUND => anyhow!("Package is hidden"), StatusCode::NOT_FOUND => anyhow!("Package not found"),
_ => anyhow!("Unknown API error: {}", status), _ => anyhow!("Unknown API error: {}", status),
} }
} }
@ -183,7 +189,7 @@ impl Route for Login {
} }
/// Handler for 'my_profile' route. Meant to be used to get profile details but /// Handler for 'my_profile' route. Meant to be used to get profile details but
/// in current application is used to check if user is logged in. Any non-200 response /// in the current application it is used to check if the user is logged in. Any non-200 response
/// is treated as Unauthorized. /// is treated as Unauthorized.
#[derive(Serialize)] #[derive(Serialize)]
pub struct Profile {} pub struct Profile {}

View File

@ -15,10 +15,7 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use super::build::Build; use super::build::Build;
use crate::{ use crate::{commands::Command, context::Context};
commands::Command,
context::{Context, PACKAGE_MANAGER_URL},
};
use leo_package::{ use leo_package::{
outputs::OutputsDirectory, outputs::OutputsDirectory,
root::{ZipFile, AUTHOR_PLACEHOLDER}, root::{ZipFile, AUTHOR_PLACEHOLDER},
@ -118,7 +115,7 @@ impl Command for Publish {
// Make a request to publish a package // Make a request to publish a package
let response = client let response = client
.post(format!("{}{}", PACKAGE_MANAGER_URL, PUBLISH_URL).as_str()) .post(format!("{}{}", context.api.host(), PUBLISH_URL).as_str())
.headers(headers) .headers(headers)
.multipart(form_data) .multipart(form_data)
.send(); .send();

View File

@ -48,19 +48,19 @@ impl Context {
} }
/// Create a new context for the current directory. /// Create a new context for the current directory.
pub fn create_context(path: PathBuf) -> Result<Context> { pub fn create_context(path: PathBuf, api_url: Option<String>) -> Result<Context> {
let token = config::read_token().ok(); let token = config::read_token().ok();
let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token); let api = Api::new(api_url.unwrap_or_else(|| PACKAGE_MANAGER_URL.to_string()), token);
Ok(Context { api, path: Some(path) }) Ok(Context { api, path: Some(path) })
} }
/// Returns project context. /// Returns project context.
pub fn get_context() -> Result<Context> { pub fn get_context(api_url: Option<String>) -> Result<Context> {
let token = config::read_token().ok(); let token = config::read_token().ok();
let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token); let api = Api::new(api_url.unwrap_or_else(|| PACKAGE_MANAGER_URL.to_string()), token);
Ok(Context { api, path: None }) Ok(Context { api, path: None })
} }

View File

@ -55,6 +55,9 @@ struct Opt {
#[structopt(subcommand)] #[structopt(subcommand)]
command: CommandOpts, command: CommandOpts,
#[structopt(help = "Custom Aleo PM backend URL", env = "APM_URL")]
api: Option<String>,
#[structopt( #[structopt(
long, long,
global = true, global = true,
@ -192,8 +195,8 @@ fn main() {
// Get custom root folder and create context for it. // Get custom root folder and create context for it.
// If not specified, default context will be created in cwd. // If not specified, default context will be created in cwd.
let context = handle_error(match opt.path { let context = handle_error(match opt.path {
Some(path) => context::create_context(path), Some(path) => context::create_context(path, opt.api),
None => context::get_context(), None => context::get_context(opt.api),
}); });
handle_error(match opt.command { handle_error(match opt.command {

View File

@ -152,7 +152,7 @@ pub fn leo_update_and_update_automatic() -> Result<()> {
/// Create context for Pedersen Hash example /// Create context for Pedersen Hash example
fn context() -> Result<Context> { fn context() -> Result<Context> {
let path = PathBuf::from(&PEDERSEN_HASH_PATH); let path = PathBuf::from(&PEDERSEN_HASH_PATH);
let context = create_context(path)?; let context = create_context(path, None)?;
Ok(context) Ok(context)
} }