11use {
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" ) ) ;
1719const INVALID_TOKEN_ERROR : & str = "invalid auth token" ;
1820
1921pub 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 ) ]
6467pub struct RegistryHttpClient {
65- base_url : Url ,
68+ base_explorer_url : Url ,
69+ base_internal_api_url : Url ,
6670 http_client : reqwest:: Client ,
6771}
6872
6973impl 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.
154203fn 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