-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api-client-framework): add blockscout services related helper de…
…finitions
- Loading branch information
1 parent
c55b2bb
commit b21cedc
Showing
5 changed files
with
221 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
use super::config; | ||
use crate::{Endpoint, Error, HttpApiClient, HttpApiClientConfig}; | ||
use reqwest::header::HeaderValue; | ||
|
||
pub struct Client { | ||
http_client: HttpApiClient, | ||
api_key: Option<HeaderValue>, | ||
} | ||
|
||
impl Client { | ||
pub async fn new(config: config::Config) -> Self { | ||
let http_client_config = HttpApiClientConfig { | ||
http_timeout: config.http_timeout, | ||
default_headers: Default::default(), | ||
middlewares: config.middlewares, | ||
}; | ||
|
||
let http_client = HttpApiClient::new(config.url, http_client_config) | ||
.unwrap_or_else(|err| panic!("cannot build an http client: {err:#?}")); | ||
|
||
let client = Self { | ||
http_client, | ||
api_key: config.api_key, | ||
}; | ||
|
||
if config.probe_url { | ||
let endpoint = health::HealthCheck::new(Default::default()); | ||
if let Err(err) = client.request(&endpoint).await { | ||
panic!("Cannot establish a connection with contracts-info client: {err}") | ||
} | ||
} | ||
|
||
client | ||
} | ||
|
||
pub async fn request<EndpointType: Endpoint>( | ||
&self, | ||
endpoint: &EndpointType, | ||
) -> Result<<EndpointType as Endpoint>::Response, Error> { | ||
self.http_client.request(endpoint).await | ||
} | ||
|
||
pub fn api_key(&self) -> Option<&HeaderValue> { | ||
self.api_key.as_ref() | ||
} | ||
} | ||
|
||
/// As we don't have protobuf generated structures here (they are only available inside a service), | ||
/// we have to imitate the service health endpoint. | ||
mod health { | ||
use crate::{serialize_query, Endpoint}; | ||
use reqwest::Method; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Debug, Default, Serialize)] | ||
pub struct HealthCheckRequest { | ||
pub service: String, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct HealthCheckResponse { | ||
#[serde(rename = "status")] | ||
pub _status: i32, | ||
} | ||
|
||
pub struct HealthCheck { | ||
request: HealthCheckRequest, | ||
} | ||
|
||
impl HealthCheck { | ||
pub fn new(request: HealthCheckRequest) -> Self { | ||
Self { request } | ||
} | ||
} | ||
|
||
impl Endpoint for HealthCheck { | ||
type Response = HealthCheckResponse; | ||
|
||
fn method(&self) -> Method { | ||
Method::GET | ||
} | ||
|
||
fn path(&self) -> String { | ||
"/health".to_string() | ||
} | ||
|
||
fn query(&self) -> Option<String> { | ||
serialize_query(&self.request) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use reqwest::header::HeaderValue; | ||
use reqwest_middleware::Middleware; | ||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; | ||
use serde::{Deserialize, Deserializer}; | ||
use std::{fmt, fmt::Formatter, sync::Arc, time::Duration}; | ||
|
||
#[derive(Clone, Deserialize)] | ||
pub struct Config { | ||
pub url: url::Url, | ||
#[serde(default, deserialize_with = "deserialize_api_key")] | ||
pub api_key: Option<HeaderValue>, | ||
/// The maximum time limit for an API request. If a request takes longer than this, it will be | ||
/// cancelled. Defaults to 30 seconds. | ||
#[serde(default = "defaults::http_timeout")] | ||
pub http_timeout: Duration, | ||
#[serde(default)] | ||
pub probe_url: bool, | ||
#[serde(skip_deserializing)] | ||
pub middlewares: Vec<Arc<dyn Middleware>>, | ||
} | ||
|
||
fn deserialize_api_key<'de, D>(deserializer: D) -> Result<Option<HeaderValue>, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let string = Option::<String>::deserialize(deserializer)?; | ||
string | ||
.map(|value| HeaderValue::from_str(&value)) | ||
.transpose() | ||
.map_err(<D::Error as serde::de::Error>::custom) | ||
} | ||
|
||
// We have to derive `Debug` manually as we need to skip middlewares field which does not implement it. | ||
impl fmt::Debug for Config { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
#[derive(Debug)] | ||
#[allow(dead_code)] | ||
struct ConfigDebug<'a> { | ||
url: &'a url::Url, | ||
api_key: &'a Option<HeaderValue>, | ||
http_timeout: &'a Duration, | ||
probe_url: &'a bool, | ||
} | ||
let Config { | ||
url, | ||
api_key, | ||
http_timeout, | ||
probe_url, | ||
middlewares: _, | ||
} = self; | ||
fmt::Debug::fmt( | ||
&ConfigDebug { | ||
url, | ||
api_key, | ||
http_timeout, | ||
probe_url, | ||
}, | ||
f, | ||
) | ||
} | ||
} | ||
|
||
impl Config { | ||
pub fn new(url: url::Url) -> Self { | ||
Self { | ||
url, | ||
api_key: None, | ||
http_timeout: defaults::http_timeout(), | ||
middlewares: vec![], | ||
probe_url: false, | ||
} | ||
} | ||
|
||
pub fn with_retry_middleware(self, max_retries: u32) -> Self { | ||
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(max_retries); | ||
let middleware = RetryTransientMiddleware::new_with_policy(retry_policy); | ||
self.with_middleware(middleware) | ||
} | ||
|
||
pub fn with_middleware<M: Middleware>(self, middleware: M) -> Self { | ||
self.with_arc_middleware(Arc::new(middleware)) | ||
} | ||
|
||
pub fn with_arc_middleware<M: Middleware>(mut self, middleware: Arc<M>) -> Self { | ||
self.middlewares.push(middleware); | ||
self | ||
} | ||
|
||
pub fn probe_url(mut self, value: bool) -> Self { | ||
self.probe_url = value; | ||
self | ||
} | ||
|
||
pub fn api_key(mut self, api_key: Option<HeaderValue>) -> Self { | ||
self.api_key = api_key; | ||
self | ||
} | ||
|
||
pub fn http_timeout(mut self, timeout: Duration) -> Self { | ||
self.http_timeout = timeout; | ||
self | ||
} | ||
} | ||
|
||
mod defaults { | ||
use std::time::Duration; | ||
|
||
pub fn http_timeout() -> Duration { | ||
Duration::from_secs(30) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
mod client; | ||
mod config; | ||
|
||
pub use client::Client; | ||
pub use config::Config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters