From a4364d32558b22183e2887aafb6f335e1984ba78 Mon Sep 17 00:00:00 2001 From: Adam Hellberg Date: Wed, 16 Aug 2023 04:12:37 +0200 Subject: [PATCH] Move blocking API to dedicated module --- crates/api/Cargo.toml | 5 +- crates/api/src/blocking.rs | 11 ++ crates/api/src/blocking/client.rs | 221 +++++++++++++++++++++++++++++ crates/api/src/blocking/error.rs | 32 +++++ crates/api/src/blocking/image.rs | 0 crates/api/src/blocking/reqwest.rs | 17 +++ crates/api/src/detail.rs | 30 ++-- crates/api/src/error.rs | 55 ++++--- crates/api/src/image.rs | 38 +---- crates/api/src/lib.rs | 217 ++-------------------------- crates/api/src/publish.rs | 31 +--- crates/api/src/reqwest.rs | 32 +++++ crates/api/src/upload.rs | 38 ----- crates/cli/Cargo.toml | 2 +- crates/cli/src/cli/portal.rs | 7 +- crates/cli/src/main.rs | 2 +- 16 files changed, 383 insertions(+), 355 deletions(-) create mode 100644 crates/api/src/blocking.rs create mode 100644 crates/api/src/blocking/client.rs create mode 100644 crates/api/src/blocking/error.rs create mode 100644 crates/api/src/blocking/image.rs create mode 100644 crates/api/src/blocking/reqwest.rs create mode 100644 crates/api/src/reqwest.rs diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index b529e8e..7ad5daa 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -10,13 +10,16 @@ license = "MPL-2.0" keywords = ["factorio"] categories = ["api-bindings"] +[features] +blocking = ["reqwest/blocking"] + [dependencies] chrono = { version = "0.4.26", default-features = false, features = [ "std", "serde" ] } facti-lib = { version = "0.1.0", path = "../lib" } -reqwest = { version = "0.11.18", features = ["blocking", "json", "multipart"] } +reqwest = { version = "0.11.18", features = ["json", "multipart"] } serde = { version = "1.0.183", features = ["derive"] } serde_json = "1.0.104" strum = { version = "0.25.0", features = ["derive"] } diff --git a/crates/api/src/blocking.rs b/crates/api/src/blocking.rs new file mode 100644 index 0000000..228458d --- /dev/null +++ b/crates/api/src/blocking.rs @@ -0,0 +1,11 @@ +//! Blocking client API. +//! +//! # Optional +//! +//! This requires the optional `blocking` feature to be enabled. + +mod client; +mod error; +mod reqwest; + +pub use client::{ApiClient, ApiClientBuilder}; diff --git a/crates/api/src/blocking/client.rs b/crates/api/src/blocking/client.rs new file mode 100644 index 0000000..e902ade --- /dev/null +++ b/crates/api/src/blocking/client.rs @@ -0,0 +1,221 @@ +use std::path::Path; + +use reqwest::{blocking::RequestBuilder, header}; +use serde::de::DeserializeOwned; +use url::Url; + +use crate::{ + detail::{ModDetailsRequest, ModDetailsResponse}, + error::{ApiError, ApiErrorKind}, + image::{ImageAddResponse, ImageEditRequest, ImageEditResponse, ImageUploadResponse}, + portal::{SearchQuery, SearchResponse, SearchResult}, + publish::{InitPublishResponse, PublishRequest, PublishResponse}, + reqwest::FormContainer, + upload::{InitUploadResponse, UploadResponse}, + DEFAULT_BASE_URL, +}; + +pub struct ApiClient { + client: reqwest::blocking::Client, + base_url: Url, + api_key: Option, +} + +type Result = core::result::Result; + +impl ApiClient { + pub fn new>(api_key: Option) -> Self { + Self::builder().api_key(api_key).build() + } + + pub fn builder() -> ApiClientBuilder { + ApiClientBuilder::new() + } + + pub fn search(&self, query: &SearchQuery) -> Result { + self.get("mods", false, |r| r.query(query)) + } + + pub fn info_short(&self, name: &str) -> Result { + self.get(&format!("mods/{}", name), false, |r| r) + } + + pub fn info_full(&self, name: &str) -> Result { + self.get(&format!("mods/{}/full", name), false, |r| r) + } + + pub fn init_upload>(&self, name: T) -> Result { + let form = reqwest::blocking::multipart::Form::new().text("mod", name.into()); + self.post("v2/mods/upload", true, |r| r.multipart(form)) + } + + pub fn upload(&self, url: Url, path: &Path) -> Result { + let form = reqwest::blocking::multipart::Form::new() + .file("file", path) + .map_err(|e| { + ApiError::new( + ApiErrorKind::ImageIo, + format!("Could not read mod file {:?}", e), + None, + ) + })?; + + self.send(self.client.post(url).multipart(form), false) + } + + pub fn edit_details(&self, data: ModDetailsRequest) -> Result { + let container: FormContainer = data.into(); + let form = container.into_inner(); + self.post("v2/mods/edit_details", true, |r| r.multipart(form)) + } + + pub fn add_image>(&self, name: T) -> Result { + self.post("v2/mods/images/add", true, |r| { + r.multipart(reqwest::blocking::multipart::Form::new().text("mod", name.into())) + }) + } + + pub fn upload_image(&self, url: Url, path: &Path) -> Result { + let form = reqwest::blocking::multipart::Form::new() + .file("image", path) + .map_err(|e| { + ApiError::new( + ApiErrorKind::ImageIo, + format!("Could not read image file: {:?}", e), + None, + ) + })?; + + self.send(self.client.post(url).multipart(form), false) + } + + pub fn edit_images(&self, data: ImageEditRequest) -> Result { + let container: FormContainer = data.into(); + let form = container.into_inner(); + self.post("v2/mods/images/edit", true, |r| r.multipart(form)) + } + + pub fn init_publish>(&self, name: T) -> Result { + let form = reqwest::blocking::multipart::Form::new().text("mod", name.into()); + self.post("v2/mods/init_publish", true, |r| r.multipart(form)) + } + + pub fn publish(&self, url: Url, data: PublishRequest, path: &Path) -> Result { + let container: FormContainer = data.into(); + let mut form = container.into_inner(); + form = form.file("file", path).map_err(|e| { + ApiError::new( + ApiErrorKind::ImageIo, + format!("Could not read mod file {:?}", e), + None, + ) + })?; + + self.send(self.client.post(url).multipart(form), false) + } + + fn url(&self, path: &str) -> Result { + self.base_url.join(path).map_err(|_| { + ApiError::new( + ApiErrorKind::UrlParseFailed, + format!("Failed to join base URL with path {}", path), + None, + ) + }) + } + + fn send(&self, request: reqwest::blocking::RequestBuilder, auth: bool) -> Result + where + T: DeserializeOwned, + { + let mut request = request.header(header::USER_AGENT, "facti"); + if auth { + if let Some(api_key) = &self.api_key { + request = request.bearer_auth(api_key) + } else { + return Err(ApiError::new( + ApiErrorKind::MissingApiKey, + "Missing API key", + None, + )); + } + } + + let response = request.send()?; + + if response.status().is_success() { + Ok(response.json::()?) + } else { + Err(response.into()) + } + } + + fn get(&self, path: &str, auth: bool, f: F) -> Result + where + T: serde::de::DeserializeOwned, + F: FnOnce(RequestBuilder) -> RequestBuilder, + { + let url = self.url(path)?; + let request = f(self.client.get(url)); + + self.send::(request, auth) + } + + fn post(&self, path: &str, auth: bool, f: F) -> Result + where + T: serde::de::DeserializeOwned, + F: FnOnce(RequestBuilder) -> RequestBuilder, + { + let url = self.url(path)?; + let request = f(self.client.post(url)); + + self.send::(request, auth) + } +} + +impl Default for ApiClient { + fn default() -> Self { + Self::new::(None) + } +} + +#[derive(Default)] +pub struct ApiClientBuilder { + client: Option, + base_url: Option, + api_key: Option, +} + +impl ApiClientBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn client(mut self, client: Option) -> Self { + self.client = client; + self + } + + pub fn base_url(mut self, base_url: Option) -> Self { + self.base_url = base_url; + self + } + + pub fn api_key>(mut self, api_key: Option) -> Self { + self.api_key = api_key.map(Into::into); + self + } + + pub fn build(self) -> ApiClient { + let client = self.client.unwrap_or_default(); + let base_url = self + .base_url + .unwrap_or(Url::parse(DEFAULT_BASE_URL).unwrap()); + + ApiClient { + client, + base_url, + api_key: self.api_key, + } + } +} diff --git a/crates/api/src/blocking/error.rs b/crates/api/src/blocking/error.rs new file mode 100644 index 0000000..1f49fbf --- /dev/null +++ b/crates/api/src/blocking/error.rs @@ -0,0 +1,32 @@ +use serde::Deserialize; + +use crate::error::{ApiError, ApiErrorKind}; + +impl From for ApiError { + fn from(response: reqwest::blocking::Response) -> Self { + #[derive(Debug, Deserialize)] + struct ApiErrorResponse { + error: String, + message: String, + } + + let source = match response.error_for_status_ref() { + Ok(_) => None, + Err(e) => Some(e), + }; + + if let Ok(error_response) = response.json::() { + Self::new( + ApiErrorKind::parse(error_response.error), + error_response.message, + source, + ) + } else { + Self::new( + ApiErrorKind::Unknown, + "Failed to parse error response", + source, + ) + } + } +} diff --git a/crates/api/src/blocking/image.rs b/crates/api/src/blocking/image.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/api/src/blocking/reqwest.rs b/crates/api/src/blocking/reqwest.rs new file mode 100644 index 0000000..e0315b4 --- /dev/null +++ b/crates/api/src/blocking/reqwest.rs @@ -0,0 +1,17 @@ +use std::borrow::Cow; + +use crate::reqwest::FormLike; + +impl FormLike for reqwest::blocking::multipart::Form { + fn new() -> Self { + Self::new() + } + + fn text(self, name: T, value: U) -> Self + where + T: Into>, + U: Into>, + { + self.text(name, value) + } +} diff --git a/crates/api/src/detail.rs b/crates/api/src/detail.rs index 6392d26..3463d98 100644 --- a/crates/api/src/detail.rs +++ b/crates/api/src/detail.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize, Serializer}; use strum::Display; use url::Url; +use crate::reqwest::{FormContainer, FormLike}; + /// Describes a request to modify details for a mod. /// /// The `name` field is required to identify the mod to change, @@ -85,53 +87,53 @@ impl ModDetailsRequest { } } -impl From for reqwest::blocking::multipart::Form { - fn from(data: ModDetailsRequest) -> Self { - let mut form = Self::new().text("mod", data.name); +impl From for FormContainer { + fn from(value: ModDetailsRequest) -> Self { + let mut form = T::new().text("mod", value.name); - if let Some(title) = data.title { + if let Some(title) = value.title { form = form.text("title", title); } - if let Some(summary) = data.summary { + if let Some(summary) = value.summary { form = form.text("summary", summary); } - if let Some(description) = data.description { + if let Some(description) = value.description { form = form.text("description", description); } - if let Some(category) = data.category { + if let Some(category) = value.category { form = form.text("category", category.to_string()); } - if let Some(tags) = data.tags { + if let Some(tags) = value.tags { for tag in tags { form = form.text("tags", tag.to_string()); } } - if let Some(license) = data.license { + if let Some(license) = value.license { form = form.text("license", license.to_string()); } - if let Some(homepage) = data.homepage { + if let Some(homepage) = value.homepage { form = form.text("homepage", homepage.to_string()); } - if let Some(deprecated) = data.deprecated { + if let Some(deprecated) = value.deprecated { form = form.text("deprecated", deprecated.to_string()); } - if let Some(source_url) = data.source_url { + if let Some(source_url) = value.source_url { form = form.text("source_url", source_url.to_string()); } - if let Some(faq) = data.faq { + if let Some(faq) = value.faq { form = form.text("faq", faq); } - form + FormContainer(form) } } diff --git a/crates/api/src/error.rs b/crates/api/src/error.rs index 2854aad..4f7d7ef 100644 --- a/crates/api/src/error.rs +++ b/crates/api/src/error.rs @@ -1,6 +1,5 @@ use std::str::FromStr; -use serde::Deserialize; use strum::Display; use thiserror::Error; @@ -95,34 +94,32 @@ impl ApiErrorKind { } } -impl From for ApiError { - fn from(response: reqwest::blocking::Response) -> Self { - #[derive(Debug, Deserialize)] - struct ApiErrorResponse { - error: String, - message: String, - } - - let source = match response.error_for_status_ref() { - Ok(_) => None, - Err(e) => Some(e), - }; - - if let Ok(error_response) = response.json::() { - Self::new( - ApiErrorKind::parse(error_response.error), - error_response.message, - source, - ) - } else { - Self::new( - ApiErrorKind::Unknown, - "Failed to parse error response", - source, - ) - } - } -} +// async fn from_response(response: reqwest::Response) -> ApiError { +// #[derive(Debug, Deserialize)] +// struct ApiErrorResponse { +// error: String, +// message: String, +// } + +// let source = match response.error_for_status_ref() { +// Ok(_) => None, +// Err(e) => Some(e), +// }; + +// if let Ok(error_response) = response.json::().await { +// ApiError::new( +// ApiErrorKind::parse(error_response.error), +// error_response.message, +// source, +// ) +// } else { +// ApiError::new( +// ApiErrorKind::Unknown, +// "Failed to parse error response", +// source, +// ) +// } +// } impl From for ApiError { fn from(error: reqwest::Error) -> Self { diff --git a/crates/api/src/image.rs b/crates/api/src/image.rs index d43d11c..2d70ccb 100644 --- a/crates/api/src/image.rs +++ b/crates/api/src/image.rs @@ -1,24 +1,7 @@ -use std::io; - use serde::Deserialize; use url::Url; -#[derive(Debug)] -pub struct ImageAddRequest { - pub name: String, -} - -impl ImageAddRequest { - pub fn new>(name: T) -> Self { - Self { name: name.into() } - } -} - -impl From for reqwest::blocking::multipart::Form { - fn from(request: ImageAddRequest) -> Self { - reqwest::blocking::multipart::Form::new().text("mod", request.name) - } -} +use crate::reqwest::{FormContainer, FormLike}; #[derive(Debug, Deserialize)] pub struct ImageAddResponse { @@ -31,19 +14,6 @@ impl ImageAddResponse { } } -#[derive(Debug)] -pub struct ImageUploadRequest { - pub path: String, -} - -impl TryFrom for reqwest::blocking::multipart::Form { - type Error = io::Error; - - fn try_from(request: ImageUploadRequest) -> Result { - reqwest::blocking::multipart::Form::new().file("image", request.path) - } -} - #[derive(Debug, Deserialize)] pub struct ImageUploadResponse { pub id: String, @@ -59,12 +29,10 @@ pub struct ImageEditRequest { pub images: Vec, } -impl From for reqwest::blocking::multipart::Form { +impl From for FormContainer { fn from(request: ImageEditRequest) -> Self { let images = request.images.join(","); - reqwest::blocking::multipart::Form::new() - .text("mod", request.name) - .text("images", images) + FormContainer(T::new().text("mod", request.name).text("images", images)) } } diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 8f00fb1..0a7d7c6 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -1,217 +1,18 @@ #![doc = include_str!("../README.md")] - -use reqwest::{blocking::RequestBuilder, header}; -use serde::de::DeserializeOwned; -use url::Url; - -use self::{ - detail::{ModDetailsRequest, ModDetailsResponse}, - error::{ApiError, ApiErrorKind}, - image::{ - ImageAddRequest, ImageAddResponse, ImageEditRequest, ImageEditResponse, ImageUploadRequest, - ImageUploadResponse, - }, - portal::{SearchQuery, SearchResponse, SearchResult}, - publish::{InitPublishRequest, InitPublishResponse, PublishRequest, PublishResponse}, - upload::{InitUploadRequest, InitUploadResponse, UploadRequest, UploadResponse}, -}; - +//! ## Features +//! +//! - **`blocking`:** Enables the [`blocking`] module, which provides a blocking client. +//! + +#[cfg(feature = "blocking")] +pub mod blocking; +pub mod client; pub mod detail; pub mod error; pub mod image; pub mod portal; pub mod publish; +mod reqwest; pub mod upload; pub const DEFAULT_BASE_URL: &str = "https://mods.factorio.com/api/"; - -pub struct ApiClient { - client: reqwest::blocking::Client, - base_url: Url, - api_key: Option, -} - -type Result = core::result::Result; - -impl ApiClient { - pub fn new>(api_key: Option) -> Self { - Self::builder().api_key(api_key).build() - } - - pub fn builder() -> ApiClientBuilder { - ApiClientBuilder::new() - } - - pub fn search(&self, query: &SearchQuery) -> Result { - self.get("mods", false, |r| r.query(query)) - } - - pub fn info_short(&self, name: &str) -> Result { - self.get(&format!("mods/{}", name), false, |r| r) - } - - pub fn info_full(&self, name: &str) -> Result { - self.get(&format!("mods/{}/full", name), false, |r| r) - } - - pub fn init_upload(&self, data: InitUploadRequest) -> Result { - self.post("v2/mods/upload", true, |r| r.multipart(data.into())) - } - - pub fn upload(&self, url: Url, data: UploadRequest) -> Result { - let form_data = data.try_into().map_err(|e| { - ApiError::new( - ApiErrorKind::ImageIo, - format!("Could not read mod file {:?}", e), - None, - ) - })?; - - self.send(self.client.post(url).multipart(form_data), false) - } - - pub fn edit_details(&self, data: ModDetailsRequest) -> Result { - self.post("v2/mods/edit_details", true, |r| r.multipart(data.into())) - } - - pub fn add_image(&self, data: ImageAddRequest) -> Result { - self.post("v2/mods/images/add", true, |r| r.multipart(data.into())) - } - - pub fn upload_image(&self, url: Url, data: ImageUploadRequest) -> Result { - let form_data = data.try_into().map_err(|e| { - ApiError::new( - ApiErrorKind::ImageIo, - format!("Could not read image file: {:?}", e), - None, - ) - })?; - - self.send(self.client.post(url).multipart(form_data), false) - } - - pub fn edit_images(&self, data: ImageEditRequest) -> Result { - self.post("v2/mods/images/edit", true, |r| r.multipart(data.into())) - } - - pub fn init_publish(&self, data: InitPublishRequest) -> Result { - self.post("v2/mods/init_publish", true, |r| r.multipart(data.into())) - } - - pub fn publish(&self, url: Url, data: PublishRequest) -> Result { - let form_data = data.try_into().map_err(|e| { - ApiError::new( - ApiErrorKind::ImageIo, - format!("Could not read mod file: {:?}", e), - None, - ) - })?; - - self.send(self.client.post(url).multipart(form_data), false) - } - - fn url(&self, path: &str) -> Result { - self.base_url.join(path).map_err(|_| { - ApiError::new( - ApiErrorKind::UrlParseFailed, - format!("Failed to join base URL with path {}", path), - None, - ) - }) - } - - fn send(&self, request: reqwest::blocking::RequestBuilder, auth: bool) -> Result - where - T: DeserializeOwned, - { - let mut request = request.header(header::USER_AGENT, "facti"); - if auth { - if let Some(api_key) = &self.api_key { - request = request.bearer_auth(api_key) - } else { - return Err(ApiError::new( - ApiErrorKind::MissingApiKey, - "Missing API key", - None, - )); - } - } - - let response = request.send()?; - - if response.status().is_success() { - Ok(response.json::()?) - } else { - Err(response.into()) - } - } - - fn get(&self, path: &str, auth: bool, f: F) -> Result - where - T: serde::de::DeserializeOwned, - F: FnOnce(RequestBuilder) -> RequestBuilder, - { - let url = self.url(path)?; - let request = f(self.client.get(url)); - - self.send::(request, auth) - } - - fn post(&self, path: &str, auth: bool, f: F) -> Result - where - T: serde::de::DeserializeOwned, - F: FnOnce(RequestBuilder) -> RequestBuilder, - { - let url = self.url(path)?; - let request = f(self.client.post(url)); - - self.send::(request, auth) - } -} - -impl Default for ApiClient { - fn default() -> Self { - Self::new::(None) - } -} - -#[derive(Default)] -pub struct ApiClientBuilder { - client: Option, - base_url: Option, - api_key: Option, -} - -impl ApiClientBuilder { - pub fn new() -> Self { - Default::default() - } - - pub fn client(mut self, client: Option) -> Self { - self.client = client; - self - } - - pub fn base_url(mut self, base_url: Option) -> Self { - self.base_url = base_url; - self - } - - pub fn api_key>(mut self, api_key: Option) -> Self { - self.api_key = api_key.map(Into::into); - self - } - - pub fn build(self) -> ApiClient { - let client = self.client.unwrap_or_default(); - let base_url = self - .base_url - .unwrap_or(Url::parse(DEFAULT_BASE_URL).unwrap()); - - ApiClient { - client, - base_url, - api_key: self.api_key, - } - } -} diff --git a/crates/api/src/publish.rs b/crates/api/src/publish.rs index d545189..492651c 100644 --- a/crates/api/src/publish.rs +++ b/crates/api/src/publish.rs @@ -1,26 +1,9 @@ -use std::io; - use serde::Deserialize; use url::Url; -use super::detail::{Category, License}; - -#[derive(Debug)] -pub struct InitPublishRequest { - pub name: String, -} - -impl InitPublishRequest { - pub fn new>(name: T) -> Self { - Self { name: name.into() } - } -} +use crate::reqwest::{FormContainer, FormLike}; -impl From for reqwest::blocking::multipart::Form { - fn from(req: InitPublishRequest) -> Self { - reqwest::blocking::multipart::Form::new().text("mod", req.name) - } -} +use super::detail::{Category, License}; #[derive(Debug, Deserialize)] pub struct InitPublishResponse { @@ -68,11 +51,9 @@ impl PublishRequest { } } -impl TryFrom for reqwest::blocking::multipart::Form { - type Error = io::Error; - - fn try_from(req: PublishRequest) -> Result { - let mut form = reqwest::blocking::multipart::Form::new().file("file", req.path)?; +impl From for FormContainer { + fn from(req: PublishRequest) -> Self { + let mut form = T::new(); if let Some(description) = req.description { form = form.text("description", description); @@ -90,7 +71,7 @@ impl TryFrom for reqwest::blocking::multipart::Form { form = form.text("source_url", source_url.to_string()); } - Ok(form) + FormContainer(form) } } diff --git a/crates/api/src/reqwest.rs b/crates/api/src/reqwest.rs new file mode 100644 index 0000000..0c2b233 --- /dev/null +++ b/crates/api/src/reqwest.rs @@ -0,0 +1,32 @@ +use std::borrow::Cow; + +pub(crate) trait FormLike { + fn new() -> Self; + + fn text(self, name: T, value: U) -> Self + where + T: Into>, + U: Into>; +} + +impl FormLike for reqwest::multipart::Form { + fn new() -> Self { + Self::new() + } + + fn text(self, name: T, value: U) -> Self + where + T: Into>, + U: Into>, + { + self.text(name, value) + } +} + +pub(crate) struct FormContainer(pub(crate) T); + +impl FormContainer { + pub fn into_inner(self) -> T { + self.0 + } +} diff --git a/crates/api/src/upload.rs b/crates/api/src/upload.rs index d7cfeea..0389bfd 100644 --- a/crates/api/src/upload.rs +++ b/crates/api/src/upload.rs @@ -1,49 +1,11 @@ -use std::io; - use serde::Deserialize; use url::Url; -#[derive(Clone, Debug)] -pub struct InitUploadRequest { - name: String, -} - -impl InitUploadRequest { - pub fn new>(name: T) -> Self { - Self { name: name.into() } - } -} - -impl From for reqwest::blocking::multipart::Form { - fn from(req: InitUploadRequest) -> Self { - reqwest::blocking::multipart::Form::new().text("mod", req.name) - } -} - #[derive(Debug, Deserialize)] pub struct InitUploadResponse { pub upload_url: Url, } -#[derive(Debug)] -pub struct UploadRequest { - pub path: String, -} - -impl UploadRequest { - pub fn new>(path: T) -> Self { - Self { path: path.into() } - } -} - -impl TryFrom for reqwest::blocking::multipart::Form { - type Error = io::Error; - - fn try_from(req: UploadRequest) -> Result { - reqwest::blocking::multipart::Form::new().file("file", req.path) - } -} - #[derive(Debug, Deserialize)] pub struct UploadResponse { pub success: bool, diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 17d71cb..228ef80 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,7 +15,7 @@ anyhow = "1.0.72" clap = { version = "4.3.21", features = ["derive"] } clap_complete = "4.3.2" directories = "5.0.1" -facti-api = { version = "0.1.0", path = "../api" } +facti-api = { version = "0.1.0", features = ["blocking"], path = "../api" } facti-lib = { version = "0.1.0", path = "../lib" } git2 = { version = "0.17.2", default-features = false } indoc = "2.0.3" diff --git a/crates/cli/src/cli/portal.rs b/crates/cli/src/cli/portal.rs index c543c11..5ee5658 100644 --- a/crates/cli/src/cli/portal.rs +++ b/crates/cli/src/cli/portal.rs @@ -1,4 +1,5 @@ use clap::{Args, Subcommand, ValueHint}; +use facti_api::blocking::ApiClient; use facti_lib::FactorioVersion; /// Interact with the Factorio mod portal. @@ -82,7 +83,7 @@ pub struct PortalShowArgs { } impl PortalArgs { - pub fn run(&self, client: &facti_api::ApiClient) -> anyhow::Result<()> { + pub fn run(&self, client: &ApiClient) -> anyhow::Result<()> { match &self.command { PortalCommands::Search(args) => args.run(client, self.json), PortalCommands::Show(args) => args.run(client, self.json), @@ -91,7 +92,7 @@ impl PortalArgs { } impl PortalSearchArgs { - pub fn run(&self, client: &facti_api::ApiClient, json: bool) -> anyhow::Result<()> { + pub fn run(&self, client: &ApiClient, json: bool) -> anyhow::Result<()> { let query = facti_api::portal::SearchQuery { hide_deprecated: !self.deprecated, page: self.page, @@ -120,7 +121,7 @@ impl PortalSearchArgs { } impl PortalShowArgs { - pub fn run(&self, client: &facti_api::ApiClient, json: bool) -> anyhow::Result<()> { + pub fn run(&self, client: &ApiClient, json: bool) -> anyhow::Result<()> { let response = if self.full { client.info_full(&self.name)? } else { diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 6af60be..a979d78 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -72,7 +72,7 @@ fn try_main() -> Result<()> { let api_key = resolve_api_key(&cli, &config).context("Failed to resolve API key")?; - let api_client = facti_api::ApiClient::builder() + let api_client = facti_api::blocking::ApiClient::builder() .base_url(base_url) .api_key(api_key) .build();