diff --git a/.env.example b/.env.example index df2b81e3cc14..6123e61794cb 100644 --- a/.env.example +++ b/.env.example @@ -86,6 +86,9 @@ OPENDAL_WEBHDFS_ATOMIC_WRITE_DIR=.opendal_tmp/ OPENDAL_WEBHDFS_DISABLE_LIST_BATCH=false # vercel artifacts OPENDAL_VERCEL_ARTIFACTS_ACCESS_TOKEN= +# OPENDAL_VERCEL_ARTIFACTS_ENDPOINT=https://api.vercel.com +# OPENDAL_VERCEL_ARTIFACTS_TEAM_ID= +# OPENDAL_VERCEL_ARTIFACTS_TEAM_SLUG= # onedrive OPENDAL_ONEDRIVE_ACCESS_TOKEN= # foundationdb diff --git a/bindings/dotnet/OpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs b/bindings/dotnet/OpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs index 610c1f52c3b2..9d0d8eee3422 100644 --- a/bindings/dotnet/OpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs +++ b/bindings/dotnet/OpenDAL/ServiceConfig/VercelArtifactsServiceConfig.cs @@ -33,6 +33,24 @@ public sealed class VercelArtifactsServiceConfig : IServiceConfig /// public string? AccessToken { get; init; } + /// + /// The endpoint for the Vercel artifacts API. + /// Defaults to https://api.vercel.com. + /// + public string? Endpoint { get; init; } + + /// + /// The Vercel team ID. When set, the teamId query parameter + /// is appended to all API requests. + /// + public string? TeamId { get; init; } + + /// + /// The Vercel team slug. When set, the slug query parameter + /// is appended to all API requests. + /// + public string? TeamSlug { get; init; } + public string Scheme => "vercel_artifacts"; public IReadOnlyDictionary ToOptions() @@ -42,6 +60,18 @@ public IReadOnlyDictionary ToOptions() { map["access_token"] = Utilities.ToOptionString(AccessToken); } + if (Endpoint is not null) + { + map["endpoint"] = Utilities.ToOptionString(Endpoint); + } + if (TeamId is not null) + { + map["team_id"] = Utilities.ToOptionString(TeamId); + } + if (TeamSlug is not null) + { + map["team_slug"] = Utilities.ToOptionString(TeamSlug); + } return map; } } diff --git a/bindings/java/src/main/java/org/apache/opendal/ServiceConfig.java b/bindings/java/src/main/java/org/apache/opendal/ServiceConfig.java index 84694655d09d..51315855dc27 100644 --- a/bindings/java/src/main/java/org/apache/opendal/ServiceConfig.java +++ b/bindings/java/src/main/java/org/apache/opendal/ServiceConfig.java @@ -3323,6 +3323,21 @@ class VercelArtifacts implements ServiceConfig { *

The access token for Vercel.

*/ public final String accessToken; + /** + *

The endpoint for the Vercel artifacts API.

+ *

Defaults to https://api.vercel.com.

+ */ + public final String endpoint; + /** + *

The Vercel team ID. When set, the teamId query parameter + * is appended to all API requests.

+ */ + public final String teamId; + /** + *

The Vercel team slug. When set, the slug query parameter + * is appended to all API requests.

+ */ + public final String teamSlug; @Override public String scheme() { @@ -3335,6 +3350,15 @@ public Map configMap() { if (accessToken != null) { map.put("access_token", accessToken); } + if (endpoint != null) { + map.put("endpoint", endpoint); + } + if (teamId != null) { + map.put("team_id", teamId); + } + if (teamSlug != null) { + map.put("team_slug", teamSlug); + } return map; } } diff --git a/bindings/python/python/opendal/operator.pyi b/bindings/python/python/opendal/operator.pyi index 4ad7a47b5e9f..7136720b0bac 100644 --- a/bindings/python/python/opendal/operator.pyi +++ b/bindings/python/python/opendal/operator.pyi @@ -2331,6 +2331,9 @@ class AsyncOperator: /, *, access_token: builtins.str = ..., + endpoint: builtins.str = ..., + team_id: builtins.str = ..., + team_slug: builtins.str = ..., ) -> typing_extensions.Self: r""" Create a new `AsyncOperator` for `vercel-artifacts` service. @@ -2339,6 +2342,12 @@ class AsyncOperator: ---------- access_token : builtins.str, optional The access token for Vercel. + endpoint : builtins.str, optional + The endpoint for the Vercel artifacts API. Defaults to ``https://api.vercel.com``. + team_id : builtins.str, optional + The Vercel team ID. + team_slug : builtins.str, optional + The Vercel team slug. Returns ------- @@ -4571,6 +4580,9 @@ class Operator: /, *, access_token: builtins.str = ..., + endpoint: builtins.str = ..., + team_id: builtins.str = ..., + team_slug: builtins.str = ..., ) -> typing_extensions.Self: r""" Create a new `Operator` for `vercel-artifacts` service. @@ -4579,6 +4591,12 @@ class Operator: ---------- access_token : builtins.str, optional The access token for Vercel. + endpoint : builtins.str, optional + The endpoint for the Vercel artifacts API. Defaults to ``https://api.vercel.com``. + team_id : builtins.str, optional + The Vercel team ID. + team_slug : builtins.str, optional + The Vercel team slug. Returns ------- diff --git a/bindings/python/src/services.rs b/bindings/python/src/services.rs index 4e0f5c6d7173..09dd71fd6f42 100644 --- a/bindings/python/src/services.rs +++ b/bindings/python/src/services.rs @@ -2343,6 +2343,9 @@ submit! { /, *, access_token: builtins.str = ..., + endpoint: builtins.str = ..., + team_id: builtins.str = ..., + team_slug: builtins.str = ..., ) -> typing_extensions.Self: r""" Create a new `Operator` for `vercel-artifacts` service. @@ -2351,6 +2354,12 @@ submit! { ---------- access_token : builtins.str, optional The access token for Vercel. + endpoint : builtins.str, optional + The endpoint for the Vercel artifacts API. Defaults to ``https://api.vercel.com``. + team_id : builtins.str, optional + The Vercel team ID. + team_slug : builtins.str, optional + The Vercel team slug. Returns ------- Operator @@ -4677,6 +4686,9 @@ submit! { /, *, access_token: builtins.str = ..., + endpoint: builtins.str = ..., + team_id: builtins.str = ..., + team_slug: builtins.str = ..., ) -> typing_extensions.Self: r""" Create a new `AsyncOperator` for `vercel-artifacts` service. @@ -4685,6 +4697,12 @@ submit! { ---------- access_token : builtins.str, optional The access token for Vercel. + endpoint : builtins.str, optional + The endpoint for the Vercel artifacts API. Defaults to ``https://api.vercel.com``. + team_id : builtins.str, optional + The Vercel team ID. + team_slug : builtins.str, optional + The Vercel team slug. Returns ------- AsyncOperator diff --git a/core/services/vercel-artifacts/src/builder.rs b/core/services/vercel-artifacts/src/builder.rs index 738f35f283f4..205d9e96e46f 100644 --- a/core/services/vercel-artifacts/src/builder.rs +++ b/core/services/vercel-artifacts/src/builder.rs @@ -48,6 +48,36 @@ impl VercelArtifactsBuilder { self.config.access_token = Some(access_token.to_string()); self } + + /// Set the endpoint for the Vercel artifacts API. + /// + /// Default: `https://api.vercel.com` + pub fn endpoint(mut self, endpoint: &str) -> Self { + if !endpoint.is_empty() { + self.config.endpoint = Some(endpoint.trim_end_matches('/').to_string()); + } + self + } + + /// Set the Vercel team ID. + /// + /// When set, the `teamId` query parameter is appended to all requests. + pub fn team_id(mut self, team_id: &str) -> Self { + if !team_id.is_empty() { + self.config.team_id = Some(team_id.to_string()); + } + self + } + + /// Set the Vercel team slug. + /// + /// When set, the `slug` query parameter is appended to all requests. + pub fn team_slug(mut self, team_slug: &str) -> Self { + if !team_slug.is_empty() { + self.config.team_slug = Some(team_slug.to_string()); + } + self + } } impl Builder for VercelArtifactsBuilder { @@ -68,14 +98,36 @@ impl Builder for VercelArtifactsBuilder { ..Default::default() }); - match self.config.access_token.clone() { - Some(access_token) => Ok(VercelArtifactsBackend { - core: Arc::new(VercelArtifactsCore { - info: Arc::new(info), - access_token, - }), - }), - None => Err(Error::new(ErrorKind::ConfigInvalid, "access_token not set")), + let access_token = self + .config + .access_token + .ok_or_else(|| Error::new(ErrorKind::ConfigInvalid, "access_token not set"))?; + + let endpoint = self + .config + .endpoint + .unwrap_or_else(|| "https://api.vercel.com".to_string()); + + let mut query_params = Vec::new(); + if let Some(team_id) = &self.config.team_id { + query_params.push(format!("teamId={team_id}")); + } + if let Some(slug) = &self.config.team_slug { + query_params.push(format!("slug={slug}")); } + let query_string = if query_params.is_empty() { + String::new() + } else { + format!("?{}", query_params.join("&")) + }; + + Ok(VercelArtifactsBackend { + core: Arc::new(VercelArtifactsCore { + info: Arc::new(info), + access_token, + endpoint, + query_string, + }), + }) } } diff --git a/core/services/vercel-artifacts/src/config.rs b/core/services/vercel-artifacts/src/config.rs index 19a7627693a8..d579272fd0c6 100644 --- a/core/services/vercel-artifacts/src/config.rs +++ b/core/services/vercel-artifacts/src/config.rs @@ -29,6 +29,16 @@ use super::builder::VercelArtifactsBuilder; pub struct VercelArtifactsConfig { /// The access token for Vercel. pub access_token: Option, + /// The endpoint for the Vercel artifacts API. + /// + /// Defaults to `https://api.vercel.com`. + pub endpoint: Option, + /// The Vercel team ID. When set, the `teamId` query parameter + /// is appended to all API requests. + pub team_id: Option, + /// The Vercel team slug. When set, the `slug` query parameter + /// is appended to all API requests. + pub team_slug: Option, } impl Debug for VercelArtifactsConfig { @@ -67,4 +77,39 @@ mod tests { let cfg = VercelArtifactsConfig::from_uri(&uri).unwrap(); assert_eq!(cfg.access_token.as_deref(), Some("token123")); } + + #[test] + fn from_uri_loads_all_options() { + let uri = OperatorUri::new( + "vercel-artifacts://cache", + vec![ + ("access_token".to_string(), "token123".to_string()), + ( + "endpoint".to_string(), + "https://custom.api.example.com".to_string(), + ), + ("team_id".to_string(), "team_abc".to_string()), + ("team_slug".to_string(), "my-team".to_string()), + ], + ) + .unwrap(); + + let cfg = VercelArtifactsConfig::from_uri(&uri).unwrap(); + assert_eq!(cfg.access_token.as_deref(), Some("token123")); + assert_eq!( + cfg.endpoint.as_deref(), + Some("https://custom.api.example.com") + ); + assert_eq!(cfg.team_id.as_deref(), Some("team_abc")); + assert_eq!(cfg.team_slug.as_deref(), Some("my-team")); + } + + #[test] + fn defaults_are_none() { + let cfg = VercelArtifactsConfig::default(); + assert!(cfg.access_token.is_none()); + assert!(cfg.endpoint.is_none()); + assert!(cfg.team_id.is_none()); + assert!(cfg.team_slug.is_none()); + } } diff --git a/core/services/vercel-artifacts/src/core.rs b/core/services/vercel-artifacts/src/core.rs index d4df5ae5b71d..7ff6b22eb838 100644 --- a/core/services/vercel-artifacts/src/core.rs +++ b/core/services/vercel-artifacts/src/core.rs @@ -28,6 +28,8 @@ use opendal_core::*; pub struct VercelArtifactsCore { pub info: Arc, pub(crate) access_token: String, + pub(crate) endpoint: String, + pub(crate) query_string: String, } impl Debug for VercelArtifactsCore { @@ -45,8 +47,10 @@ impl VercelArtifactsCore { _: &OpRead, ) -> Result> { let url: String = format!( - "https://api.vercel.com/v8/artifacts/{}", - percent_encode_path(hash) + "{}/v8/artifacts/{}{}", + self.endpoint, + percent_encode_path(hash), + self.query_string ); let mut req = Request::get(&url); @@ -72,8 +76,10 @@ impl VercelArtifactsCore { body: Buffer, ) -> Result> { let url = format!( - "https://api.vercel.com/v8/artifacts/{}", - percent_encode_path(hash) + "{}/v8/artifacts/{}{}", + self.endpoint, + percent_encode_path(hash), + self.query_string ); let mut req = Request::put(&url); @@ -92,8 +98,10 @@ impl VercelArtifactsCore { pub(crate) async fn vercel_artifacts_stat(&self, hash: &str) -> Result> { let url = format!( - "https://api.vercel.com/v8/artifacts/{}", - percent_encode_path(hash) + "{}/v8/artifacts/{}{}", + self.endpoint, + percent_encode_path(hash), + self.query_string ); let mut req = Request::head(&url); diff --git a/core/services/vercel-artifacts/src/docs.md b/core/services/vercel-artifacts/src/docs.md index 5ed8c34fb30d..1360583a4763 100644 --- a/core/services/vercel-artifacts/src/docs.md +++ b/core/services/vercel-artifacts/src/docs.md @@ -15,6 +15,9 @@ This service can be used to: ## Configuration - `access_token`: set the access_token for Rest API +- `endpoint`: set the API endpoint (default: `https://api.vercel.com`) +- `team_id`: optional Vercel team ID, appended as `teamId` query parameter +- `team_slug`: optional Vercel team slug, appended as `slug` query parameter You can refer to [`VercelArtifactsBuilder`]'s docs for more information @@ -31,7 +34,9 @@ use opendal_core::Operator; async fn main() -> Result<()> { // create backend builder let mut builder = VercelArtifacts::default() - .access_token("xxx"); + .access_token("xxx") + .endpoint("https://my-vercel-api.example.com") + .team_id("team_xxx"); let op: Operator = Operator::new(builder)?.finish(); Ok(())