Skip to content

Commit 1a9722c

Browse files
committed
feat: implementing project_plan_limits method to get limits from a new internal api
1 parent 9f6d122 commit 1a9722c

3 files changed

Lines changed: 74 additions & 19 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
- name: "Check Formatting"
4545
cmd: fmt
4646
args: -- --check
47-
toolchain: nightly
47+
toolchain: stable
4848
profile: default
4949
- name: "Unit Tests"
5050
cmd: test

src/project/types/project_data.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ pub struct Quota {
5050
pub is_valid: bool,
5151
}
5252

53+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)]
54+
#[serde(rename_all = "camelCase")]
55+
pub struct PlanLimits {
56+
pub tier: String,
57+
pub is_above_rpc_limit: bool,
58+
pub is_above_mau_limit: bool,
59+
}
60+
5361
impl ProjectData {
5462
pub fn validate_access(
5563
&self,

src/registry/client.rs

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
use {
22
crate::{
3-
project::{ProjectData, ProjectDataWithQuota},
3+
project::{PlanLimits, ProjectData, ProjectDataWithQuota},
44
registry::error::RegistryError,
55
},
66
async_trait::async_trait,
77
reqwest::{
88
header::{self, HeaderValue},
9-
IntoUrl,
10-
StatusCode,
11-
Url,
9+
IntoUrl, StatusCode, Url,
1210
},
1311
serde::de::DeserializeOwned,
1412
std::{fmt::Debug, time::Duration},
1513
};
1614

15+
use once_cell::sync::Lazy;
16+
17+
static INTERNAL_API_BASE_URI: Lazy<Url> =
18+
Lazy::new(|| Url::parse("https://api.reown.com").expect("Invalid internal API base URI"));
1719
const INVALID_TOKEN_ERROR: &str = "invalid auth token";
1820

1921
pub type RegistryResult<T> = Result<T, RegistryError>;
@@ -25,6 +27,7 @@ pub trait RegistryClient: 'static + Send + Sync + Debug {
2527
&self,
2628
id: &str,
2729
) -> RegistryResult<Option<ProjectDataWithQuota>>;
30+
async fn project_plan_limits(&self, id: &str) -> RegistryResult<Option<PlanLimits>>;
2831
}
2932

3033
/// HTTP client configuration.
@@ -62,22 +65,27 @@ impl Default for HttpClientConfig {
6265

6366
#[derive(Debug, Clone)]
6467
pub struct RegistryHttpClient {
65-
base_url: Url,
68+
base_explorer_url: Url,
69+
base_internal_api_url: Url,
6670
http_client: reqwest::Client,
6771
}
6872

6973
impl RegistryHttpClient {
70-
pub fn new(base_url: impl IntoUrl, auth_token: &str, origin: &str) -> RegistryResult<Self> {
71-
Self::with_config(base_url, auth_token, origin, Default::default())
74+
pub fn new(
75+
base_explorer_url: impl IntoUrl,
76+
auth_token: &str,
77+
origin: &str,
78+
) -> RegistryResult<Self> {
79+
Self::with_config(base_explorer_url, auth_token, origin, Default::default())
7280
}
7381

7482
pub fn with_config(
75-
base_url: impl IntoUrl,
83+
base_explorer_url: impl IntoUrl,
7684
auth_token: &str,
7785
origin: &str,
7886
config: HttpClientConfig,
7987
) -> RegistryResult<Self> {
80-
let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", auth_token))
88+
let mut auth_value = HeaderValue::from_str(&format!("Bearer {auth_token}"))
8189
.map_err(|_| RegistryError::Config(INVALID_TOKEN_ERROR))?;
8290

8391
// Make sure we're not leaking auth token in debug output.
@@ -90,6 +98,8 @@ impl RegistryHttpClient {
9098
HeaderValue::from_str(origin).map_err(RegistryError::OriginParse)?,
9199
);
92100

101+
// We can use the same client for both explorer and internal API
102+
// because the internal API is protected by the same auth token.
93103
let mut http_client = reqwest::Client::builder()
94104
.default_headers(headers)
95105
.pool_idle_timeout(config.pool_idle_timeout)
@@ -100,7 +110,10 @@ impl RegistryHttpClient {
100110
}
101111

102112
Ok(Self {
103-
base_url: base_url.into_url().map_err(RegistryError::BaseUrlIntoUrl)?,
113+
base_explorer_url: base_explorer_url
114+
.into_url()
115+
.map_err(RegistryError::BaseUrlIntoUrl)?,
116+
base_internal_api_url: INTERNAL_API_BASE_URI.clone(),
104117
http_client: http_client.build().map_err(RegistryError::BuildClient)?,
105118
})
106119
}
@@ -114,7 +127,29 @@ impl RegistryHttpClient {
114127
return Ok(None);
115128
}
116129

117-
let url = build_url(&self.base_url, project_id, quota).map_err(RegistryError::UrlBuild)?;
130+
let url = build_explorer_url(&self.base_explorer_url, project_id, quota)
131+
.map_err(RegistryError::UrlBuild)?;
132+
133+
let resp = self
134+
.http_client
135+
.get(url)
136+
.send()
137+
.await
138+
.map_err(RegistryError::Transport)?;
139+
140+
parse_http_response(resp).await
141+
}
142+
143+
async fn project_plan_limits_impl<T: DeserializeOwned>(
144+
&self,
145+
project_id: &str,
146+
) -> RegistryResult<Option<T>> {
147+
if !is_valid_project_id(project_id) {
148+
return Ok(None);
149+
}
150+
151+
let url = build_internal_api_url(&self.base_internal_api_url, project_id)
152+
.map_err(RegistryError::UrlBuild)?;
118153

119154
let resp = self
120155
.http_client
@@ -139,16 +174,30 @@ impl RegistryClient for RegistryHttpClient {
139174
) -> RegistryResult<Option<ProjectDataWithQuota>> {
140175
self.project_data_impl(project_id, true).await
141176
}
177+
178+
async fn project_plan_limits(&self, project_id: &str) -> RegistryResult<Option<PlanLimits>> {
179+
self.project_plan_limits_impl(project_id).await
180+
}
142181
}
143182

144-
fn build_url(base_url: &Url, project_id: &str, quota: bool) -> Result<Url, url::ParseError> {
183+
fn build_explorer_url(
184+
base_url: &Url,
185+
project_id: &str,
186+
quota: bool,
187+
) -> Result<Url, url::ParseError> {
145188
let mut url = base_url.join(&format!("/internal/project/key/{project_id}"))?;
146189
if quota {
147190
url.query_pairs_mut().append_pair("quotas", "true");
148191
}
149192
Ok(url)
150193
}
151194

195+
fn build_internal_api_url(base_url: &Url, project_id: &str) -> Result<Url, url::ParseError> {
196+
let mut url = base_url.join("/internal/v1/project-limits")?;
197+
url.query_pairs_mut().append_pair("projectId", project_id);
198+
Ok(url)
199+
}
200+
152201
/// Checks if the project ID is formatted properly. It must be 32 hex
153202
/// characters.
154203
fn is_valid_project_id(project_id: &str) -> bool {
@@ -186,9 +235,7 @@ mod test {
186235
wiremock::{
187236
http::Method,
188237
matchers::{method, path, query_param},
189-
Mock,
190-
MockServer,
191-
ResponseTemplate,
238+
Mock, MockServer, ResponseTemplate,
192239
},
193240
};
194241

@@ -350,11 +397,11 @@ mod test {
350397
}
351398

352399
#[test]
353-
fn test_build_url() {
400+
fn test_build_explorer_url() {
354401
let base_url = Url::parse("http://example.com").unwrap();
355402
let project_id = "a".repeat(32);
356403

357-
let url = build_url(&base_url, &project_id, false).unwrap();
404+
let url = build_explorer_url(&base_url, &project_id, false).unwrap();
358405
assert_eq!(
359406
url.as_str(),
360407
"http://example.com/internal/project/key/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
@@ -366,7 +413,7 @@ mod test {
366413
let base_url = Url::parse("http://example.com").unwrap();
367414
let project_id = "a".repeat(32);
368415

369-
let url = build_url(&base_url, &project_id, true).unwrap();
416+
let url = build_explorer_url(&base_url, &project_id, true).unwrap();
370417
assert_eq!(
371418
url.as_str(),
372419
"http://example.com/internal/project/key/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?quotas=true"

0 commit comments

Comments
 (0)