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

add user-agent header and set application name #2

Merged
merged 2 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 103 additions & 17 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,71 @@ use reqwest::header;
use std::fmt::Debug;
use strum_macros::EnumString;

/// All methods contain an `Option<String>` to provide an alternate api key to use if it differs from the default
pub struct OpenShockAPI {
client: reqwest::Client,
base_url: String,
default_key: String,
/// Builder for [`OpenShockAPI`]
#[derive(Default)]
pub struct OpenShockAPIBuilder {
base_url: Option<String>,
default_key: Option<String>,
app_name: Option<String>,
app_version: Option<String>,
}

/// Which list of shockers to return
#[derive(EnumString, Debug)]
pub enum ListShockerSource {
Own,
Shared,
}
impl OpenShockAPIBuilder {
/// Create a new builder
pub fn new() -> Self {
Self::default()
}

/// set the base URL to use
///
/// this is optional and can be provided to use a self-hosted instance of the OpenShock API. if
/// left unset, the default (`https://api.openshock.app`) will be used.
pub fn with_base_url(mut self, base_url: String) -> Self {
self.base_url = Some(base_url);
self
}

/// set the API token to use
///
/// this must be provided
pub fn with_default_api_token(mut self, default_api_token: String) -> Self {
self.default_key = Some(default_api_token);
self
}

/// set the name and optionally version of the app using this crate
///
/// this is optional. if provided, the information will be added to the user agent string for
/// all OpenShock API requests and also sent in [`OpenShockAPI::post_control`] so the app name
/// shows up in the OpenShock log.
pub fn with_app(mut self, app_name: String, app_version: Option<String>) -> Self {
self.app_name = Some(app_name);
self.app_version = app_version;
self
}

/// check parameters and build an instance of [`OpenShockAPI`]
pub fn build(self) -> Result<OpenShockAPI, Error> {
let base_url = self
.base_url
.unwrap_or("https://api.openshock.app".to_string());
let Some(default_key) = self.default_key else {
return Err(Error::MissingApiToken);
};

let mut user_agent = format!("rzap/{}", env!("CARGO_PKG_VERSION"));
// maybe add platform information as well?
let app_name = if let Some(app_name) = self.app_name {
if let Some(app_version) = self.app_version {
user_agent += &format!(" ({} {})", app_name, app_version);
} else {
user_agent += &format!(" ({})", app_name);
}
app_name
} else {
"rzap".to_string()
};

impl OpenShockAPI {
/// Create a new instance of the api interface with a default key and the base_url, because OpenShock can be self hosted `base_url` can be any url without the leading `/` if `None` is provided the default of <https://api.shocklink.net> is used.
pub fn new(base_url: Option<String>, default_key: String) -> Self {
let mut headers = header::HeaderMap::new();
headers.insert(
"Content-type",
Expand All @@ -29,16 +77,54 @@ impl OpenShockAPI {
"accept",
header::HeaderValue::from_static("application/json"),
);
headers.insert(
header::USER_AGENT,
header::HeaderValue::from_str(&user_agent).map_err(|e| Error::InvalidHeaderValue(e))?,
);
let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.unwrap();
let base_url = base_url.unwrap_or("https://api.openshock.app".to_string());
OpenShockAPI {

Ok(OpenShockAPI {
client,
base_url,
default_key,
app_name,
})
}
}

/// All methods contain an `Option<String>` to provide an alternate api key to use if it differs from the default
pub struct OpenShockAPI {
client: reqwest::Client,
base_url: String,
default_key: String,
app_name: String,
}

/// Which list of shockers to return
#[derive(EnumString, Debug)]
pub enum ListShockerSource {
Own,
Shared,
}

impl OpenShockAPI {
/// Return a builder for the api interface
///
/// this is the same as [`OpenShockAPIBuilder::new`]
pub fn builder() -> OpenShockAPIBuilder {
OpenShockAPIBuilder::new()
}

/// Create a new instance of the api interface with a default key and the base_url, because OpenShock can be self hosted `base_url` can be any url without the leading `/` if `None` is provided the default of <https://api.shocklink.net> is used.
pub fn new(base_url: Option<String>, default_key: String) -> Self {
let mut builder = Self::builder().with_default_api_token(default_key);
if let Some(base_url) = base_url {
builder = builder.with_base_url(base_url);
}
builder.build().unwrap()
}

/// Gets user info from the provided API key, the default key from the instance is used if `None` is provided
Expand Down Expand Up @@ -108,7 +194,7 @@ impl OpenShockAPI {
duration: duration,
exclusive: true,
}],
custom_name: "rusty".to_string(),
custom_name: self.app_name.clone(),
})?;

let resp = self
Expand Down
10 changes: 10 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/// Error type for functions in this crate
#[derive(Debug)]
pub enum Error {
/// error propagated from reqwest
Reqwest(reqwest::Error),
/// error propagated from serde
Serde(serde_json::Error),
/// no API token was provided to the API interface
MissingApiToken,
/// invalid header value when building the API interface
InvalidHeaderValue(reqwest::header::InvalidHeaderValue),
}

impl From<reqwest::Error> for Error {
Expand All @@ -22,6 +28,8 @@ impl std::fmt::Display for Error {
match self {
Self::Reqwest(e) => e.fmt(f),
Self::Serde(e) => e.fmt(f),
Self::MissingApiToken => write!(f, "no API token was provided"),
Self::InvalidHeaderValue(e) => write!(f, "invalid header value for user agent: {}", e),
}
}
}
Expand All @@ -31,6 +39,8 @@ impl std::error::Error for Error {
match self {
Self::Reqwest(e) => e.source(),
Self::Serde(e) => e.source(),
Self::MissingApiToken => None,
Self::InvalidHeaderValue(e) => e.source(),
}
}
}
Loading