alien_bindings/traits.rs
1use crate::error::Result;
2use crate::presigned::PresignedRequest;
3use alien_core::{BuildConfig, BuildExecution};
4use async_trait::async_trait;
5use object_store::path::Path;
6use object_store::ObjectStore;
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9use std::sync::Arc;
10use std::time::Duration;
11use url::Url;
12
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16/// Marker trait for all binding types.
17pub trait Binding: Send + Sync + std::fmt::Debug {}
18
19/// A storage binding that provides object store capabilities.
20#[async_trait]
21pub trait Storage: Binding + ObjectStore {
22 /// Gets the base directory path configured for this storage binding.
23 fn get_base_dir(&self) -> Path;
24 /// Gets the underlying URL configured for this storage binding.
25 fn get_url(&self) -> Url;
26
27 /// Creates a presigned request for uploading data to the specified path.
28 /// The request can be serialized, stored, and executed later.
29 async fn presigned_put(&self, path: &Path, expires_in: Duration) -> Result<PresignedRequest>;
30
31 /// Creates a presigned request for downloading data from the specified path.
32 /// The request can be serialized, stored, and executed later.
33 async fn presigned_get(&self, path: &Path, expires_in: Duration) -> Result<PresignedRequest>;
34
35 /// Creates a presigned request for deleting the object at the specified path.
36 /// The request can be serialized, stored, and executed later.
37 async fn presigned_delete(&self, path: &Path, expires_in: Duration)
38 -> Result<PresignedRequest>;
39}
40
41/// A build binding that provides build execution capabilities.
42#[async_trait]
43pub trait Build: Binding {
44 /// Starts a new build with the given configuration.
45 /// Returns the build execution information.
46 async fn start_build(&self, config: BuildConfig) -> Result<BuildExecution>;
47
48 /// Gets the status of a specific build execution.
49 async fn get_build_status(&self, build_id: &str) -> Result<BuildExecution>;
50
51 /// Stops or cancels a running build.
52 async fn stop_build(&self, build_id: &str) -> Result<()>;
53}
54
55/// AWS IAM Role service account information
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58#[cfg_attr(feature = "openapi", derive(ToSchema))]
59pub struct AwsServiceAccountInfo {
60 /// The IAM role name
61 pub role_name: String,
62 /// The IAM role ARN (for AssumeRole)
63 pub role_arn: String,
64}
65
66/// GCP Service Account information
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69#[cfg_attr(feature = "openapi", derive(ToSchema))]
70pub struct GcpServiceAccountInfo {
71 /// The service account email (for impersonation)
72 pub email: String,
73 /// The service account unique ID
74 pub unique_id: String,
75}
76
77/// Azure User-Assigned Managed Identity information
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80#[cfg_attr(feature = "openapi", derive(ToSchema))]
81pub struct AzureServiceAccountInfo {
82 /// The managed identity client ID (for authentication)
83 pub client_id: String,
84 /// The managed identity resource ID (ARM ID)
85 pub resource_id: String,
86 /// The managed identity principal ID
87 pub principal_id: String,
88}
89
90/// Platform-specific service account information
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "platform", rename_all = "camelCase")]
93#[cfg_attr(feature = "openapi", derive(ToSchema))]
94pub enum ServiceAccountInfo {
95 /// AWS IAM Role
96 Aws(AwsServiceAccountInfo),
97 /// GCP Service Account
98 Gcp(GcpServiceAccountInfo),
99 /// Azure User-Assigned Managed Identity
100 Azure(AzureServiceAccountInfo),
101}
102
103/// Configuration for impersonation
104#[derive(Debug, Clone)]
105pub struct ImpersonationRequest {
106 /// Optional session name (AWS only)
107 pub session_name: Option<String>,
108 /// Optional session duration in seconds
109 pub duration_seconds: Option<i32>,
110 /// Optional scopes (GCP only)
111 pub scopes: Option<Vec<String>>,
112}
113
114impl Default for ImpersonationRequest {
115 fn default() -> Self {
116 Self {
117 session_name: None,
118 duration_seconds: Some(3600), // 1 hour default
119 scopes: None,
120 }
121 }
122}
123
124/// A service account binding that provides identity and impersonation capabilities.
125#[async_trait]
126pub trait ServiceAccount: Binding {
127 /// Gets information about the service account
128 async fn get_info(&self) -> Result<ServiceAccountInfo>;
129
130 /// Impersonates the service account and returns credentials as a ClientConfig.
131 ///
132 /// This performs the cloud-specific impersonation:
133 /// - AWS: STS AssumeRole to get temporary credentials
134 /// - GCP: IAM Credentials API generateAccessToken
135 /// - Azure: Uses the attached managed identity (no API call needed)
136 async fn impersonate(&self, request: ImpersonationRequest) -> Result<alien_core::ClientConfig>;
137
138 /// Helper for downcasting trait object
139 fn as_any(&self) -> &dyn std::any::Any;
140}
141
142/// Response from repository operations.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145#[cfg_attr(feature = "openapi", derive(ToSchema))]
146pub struct RepositoryResponse {
147 /// Repository name.
148 pub name: String,
149 /// Repository URI for pushing/pulling images. None if repository is not ready yet.
150 pub uri: Option<String>,
151 /// Optional creation timestamp in ISO8601 format.
152 pub created_at: Option<String>,
153}
154
155/// Permissions level for artifact registry access.
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(rename_all = "kebab-case")]
158#[cfg_attr(feature = "openapi", derive(ToSchema))]
159pub enum ArtifactRegistryPermissions {
160 /// Pull-only access (download artifacts).
161 Pull,
162 /// Push and pull access (upload and download artifacts).
163 PushPull,
164}
165
166/// Credentials for accessing a repository.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169#[cfg_attr(feature = "openapi", derive(ToSchema))]
170pub struct ArtifactRegistryCredentials {
171 /// Username for authentication.
172 pub username: String,
173 /// Password or token for authentication.
174 pub password: String,
175 /// Optional expiration time in ISO8601 format.
176 pub expires_at: Option<String>,
177}
178
179/// Types of compute services that can access artifact registries.
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181#[serde(rename_all = "kebab-case")]
182#[cfg_attr(feature = "openapi", derive(ToSchema))]
183pub enum ComputeServiceType {
184 /// Serverless functions
185 Function,
186 // In the future, we could add Container, VirtualMachine, Kubernetes, etc.
187}
188
189/// Cross-account access configuration for AWS artifact registries.
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192#[cfg_attr(feature = "openapi", derive(ToSchema))]
193pub struct AwsCrossAccountAccess {
194 /// AWS account IDs that should have cross-account access.
195 pub account_ids: Vec<String>,
196 /// AWS regions where the target Lambda functions run.
197 /// Used to construct `aws:sourceArn` patterns for the Lambda service principal condition.
198 pub regions: Vec<String>,
199 /// Types of compute services that should have access.
200 pub allowed_service_types: Vec<ComputeServiceType>,
201 /// Specific IAM role ARNs to grant access to.
202 /// These are typically deployment/management roles or service-specific roles.
203 pub role_arns: Vec<String>,
204}
205
206/// Cross-account access configuration for GCP artifact registries.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209#[cfg_attr(feature = "openapi", derive(ToSchema))]
210pub struct GcpCrossAccountAccess {
211 /// GCP project numbers that should have access.
212 pub project_numbers: Vec<String>,
213 /// Types of compute services that should have access.
214 pub allowed_service_types: Vec<ComputeServiceType>,
215 /// Additional service account emails to grant access to.
216 /// These are typically deployment/management service accounts.
217 pub service_account_emails: Vec<String>,
218}
219
220/// Platform-specific cross-account access configuration.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222#[serde(tag = "platform", rename_all = "lowercase")]
223#[cfg_attr(feature = "openapi", derive(ToSchema))]
224pub enum CrossAccountAccess {
225 /// AWS-specific cross-account access configuration.
226 Aws(AwsCrossAccountAccess),
227 /// GCP-specific cross-account access configuration.
228 Gcp(GcpCrossAccountAccess),
229}
230
231/// Current cross-account access permissions for a repository.
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234#[cfg_attr(feature = "openapi", derive(ToSchema))]
235pub struct CrossAccountPermissions {
236 /// Platform-specific access configuration currently applied.
237 pub access: CrossAccountAccess,
238 /// Timestamp when permissions were last updated.
239 pub last_updated: Option<String>,
240}
241
242/// A trait for artifact registry bindings that provide container image repository management.
243#[async_trait]
244pub trait ArtifactRegistry: Binding {
245 /// Returns the raw registry endpoint URL (e.g., "https://123456.dkr.ecr.us-east-1.amazonaws.com"
246 /// or "http://localhost:5000"). Used by the push proxy to forward requests transparently.
247 ///
248 /// Default returns empty string — cloud provider implementations should override.
249 fn registry_endpoint(&self) -> String {
250 String::new()
251 }
252
253 /// Returns the OCI repository path prefix used for upstream operations.
254 ///
255 /// When the proxy forwards push/pull requests to the upstream registry,
256 /// this prefix is prepended to the image name portion of the OCI path.
257 ///
258 /// Examples:
259 /// - ECR: `"alien-e2e"` (flat repo prefix)
260 /// - GAR: `"my-project/alien-e2e"` (project/repo structure)
261 /// - ACR: `""` (images pushed to root)
262 /// - Local: `"artifacts"` or similar
263 ///
264 /// Default returns empty string.
265 fn upstream_repository_prefix(&self) -> String {
266 String::new()
267 }
268
269 /// Creates a repository within the artifact registry.
270 /// Returns the repository details. URI will be None if repository is still being created.
271 async fn create_repository(&self, repo_name: &str) -> Result<RepositoryResponse>;
272
273 /// Gets repository details including name, URI, and creation time.
274 async fn get_repository(&self, repo_id: &str) -> Result<RepositoryResponse>;
275
276 /// Adds cross-account access permissions for a repository.
277 /// This adds the specified permissions to any existing cross-account permissions.
278 ///
279 /// For AWS: Grants access to specified account IDs with configurable principals and compute service types.
280 /// For GCP: Grants access to serverless robots and service accounts based on compute service types.
281 /// For Azure: Not supported - returns OperationNotSupported error.
282 async fn add_cross_account_access(
283 &self,
284 repo_id: &str,
285 access: CrossAccountAccess,
286 ) -> Result<()>;
287
288 /// Removes cross-account access permissions for a repository.
289 /// This removes the specified permissions from existing cross-account permissions.
290 ///
291 /// For AWS: Removes access for specified account IDs and compute service types.
292 /// For GCP: Removes access for specified project numbers and service accounts.
293 /// For Azure: Not supported - returns OperationNotSupported error.
294 async fn remove_cross_account_access(
295 &self,
296 repo_id: &str,
297 access: CrossAccountAccess,
298 ) -> Result<()>;
299
300 /// Gets the current cross-account access permissions for a repository.
301 /// For Azure: Not supported - returns OperationNotSupported error.
302 async fn get_cross_account_access(&self, repo_id: &str) -> Result<CrossAccountPermissions>;
303
304 /// Generates credentials for accessing a repository with specified permissions.
305 /// On AWS: assumes the relevant role and calls get_authorization_token.
306 /// On GCP: impersonates the relevant service account and gets an oauth token.
307 /// On Azure: uses the built-in token mechanism.
308 async fn generate_credentials(
309 &self,
310 repo_id: &str,
311 permissions: ArtifactRegistryPermissions,
312 ttl_seconds: Option<u32>,
313 ) -> Result<ArtifactRegistryCredentials>;
314
315 /// Cleans up resources created by `generate_credentials` (scope maps, tokens).
316 /// On Azure: deletes the scope map and token for the given repo ID.
317 /// Default: no-op (other providers use short-lived or IAM-based credentials).
318 async fn cleanup_credentials(&self, _repo_id: &str) -> Result<()> {
319 Ok(())
320 }
321
322 /// Deletes a repository and all contained images.
323 async fn delete_repository(&self, repo_id: &str) -> Result<()>;
324
325 /// Generates a pre-signed download URL for a blob (layer) in the registry.
326 ///
327 /// Used by the registry proxy to return 307 redirects instead of streaming
328 /// blob bytes through the manager. Returns `None` if the provider doesn't
329 /// support pre-signed URLs (the proxy will stream the blob directly instead).
330 ///
331 /// - ECR: Calls `GetDownloadUrlForLayer` (returns pre-signed S3 URL).
332 /// - GAR: Returns `None` (GAR uses OCI distribution API with bearer auth).
333 /// - ACR: Returns `None` (ACR uses OCI distribution API with bearer auth).
334 /// - Local: Returns `None` (local registry is co-located, streaming is fine).
335 async fn generate_blob_download_url(
336 &self,
337 _repo_name: &str,
338 _digest: &str,
339 _ttl_seconds: u32,
340 ) -> Result<Option<String>> {
341 Ok(None)
342 }
343
344 /// Fetches a manifest from the upstream registry by reference (tag or digest).
345 ///
346 /// Used by the registry proxy to serve manifest requests. Returns the
347 /// manifest bytes and the content type (e.g., `application/vnd.oci.image.manifest.v1+json`).
348 ///
349 /// Default implementation returns `OperationNotSupported`. Providers that
350 /// support the registry proxy should implement this.
351 async fn get_manifest(&self, _repo_name: &str, _reference: &str) -> Result<(Vec<u8>, String)> {
352 Err(alien_error::AlienError::new(
353 crate::error::ErrorData::OperationNotSupported {
354 operation: "get_manifest".to_string(),
355 reason: "This artifact registry provider does not support manifest fetching"
356 .to_string(),
357 },
358 ))
359 }
360
361 /// Checks if a manifest exists in the upstream registry.
362 ///
363 /// Returns the content type and digest if the manifest exists.
364 /// Default implementation returns `OperationNotSupported`.
365 async fn head_manifest(
366 &self,
367 _repo_name: &str,
368 _reference: &str,
369 ) -> Result<Option<(String, String)>> {
370 Err(alien_error::AlienError::new(
371 crate::error::ErrorData::OperationNotSupported {
372 operation: "head_manifest".to_string(),
373 reason: "This artifact registry provider does not support manifest checking"
374 .to_string(),
375 },
376 ))
377 }
378
379 /// Checks if a blob exists in the upstream registry.
380 ///
381 /// Returns the content length if the blob exists.
382 /// Default implementation returns `OperationNotSupported`.
383 async fn head_blob(&self, _repo_name: &str, _digest: &str) -> Result<Option<u64>> {
384 Err(alien_error::AlienError::new(
385 crate::error::ErrorData::OperationNotSupported {
386 operation: "head_blob".to_string(),
387 reason: "This artifact registry provider does not support blob checking"
388 .to_string(),
389 },
390 ))
391 }
392}
393
394/// A trait for vault bindings that provide secure secret management.
395#[async_trait]
396pub trait Vault: Binding {
397 /// Gets a secret value by name.
398 async fn get_secret(&self, secret_name: &str) -> Result<String>;
399
400 /// Sets a secret value, creating it if it doesn't exist or updating it if it does.
401 async fn set_secret(&self, secret_name: &str, value: &str) -> Result<()>;
402
403 /// Deletes a secret by name.
404 async fn delete_secret(&self, secret_name: &str) -> Result<()>;
405}
406
407/// Represents options for put operations in KV stores.
408#[derive(Debug, Clone, Default)]
409pub struct PutOptions {
410 /// Optional TTL for automatic expiration (soft hint - items MAY be deleted after expiry)
411 pub ttl: Option<Duration>,
412 /// Only put if the key does not exist
413 pub if_not_exists: bool,
414}
415
416/// Represents the result of a scan operation.
417#[derive(Debug)]
418pub struct ScanResult {
419 /// Key-value pairs found (may be ≤ limit, no guarantee to fill)
420 pub items: Vec<(String, Vec<u8>)>,
421 /// Opaque cursor for pagination. None if no more results.
422 /// **Warning**: Cursor may become invalid if data changes. No TTL guarantees.
423 pub next_cursor: Option<String>,
424}
425
426/// A trait for key-value store bindings that provide minimal, platform-agnostic KV operations.
427/// This API is designed to work consistently across DynamoDB, Firestore, Redis, and Azure Table Storage.
428#[async_trait]
429pub trait Kv: Binding {
430 /// Get a value by key. Returns None if key doesn't exist or has expired.
431 ///
432 /// **TTL Behavior**: TTL is a soft hint for automatic cleanup. If `now >= expires_at`,
433 /// implementations SHOULD behave as if the key is absent, even if the item still exists
434 /// physically in the backend. Physical deletion is eventual and not guaranteed.
435 ///
436 /// **Validation**: Keys are validated against MAX_KEY_BYTES and portable charset.
437 /// Invalid keys return `KvError::InvalidKey` immediately.
438 async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
439
440 /// Put a value with optional options. When options.if_not_exists is true, returns true if created,
441 /// false if already exists. When options.if_not_exists is false or options is None, always returns true.
442 ///
443 /// **Size Limits**:
444 /// - Keys: ≤ MAX_KEY_BYTES (512 bytes) with portable ASCII charset
445 /// - Values: ≤ MAX_VALUE_BYTES (24,576 bytes = 24 KiB)
446 ///
447 /// **Validation**: Size and charset constraints are enforced before backend calls.
448 /// Invalid inputs return `KvError::InvalidKey` or `KvError::InvalidValue` immediately.
449 ///
450 /// **TTL Behavior**: TTL is a soft hint for automatic cleanup. If TTL is specified,
451 /// item expires at `put_time + ttl`. Expired items SHOULD appear absent on subsequent
452 /// reads, but physical deletion is eventual and not guaranteed.
453 ///
454 /// **Conditional Logic**: The if_not_exists operation maps to backend primitives:
455 /// - Redis: SETNX
456 /// - DynamoDB: PutItem with condition_expression="attribute_not_exists(pk)"
457 /// - Firestore: create() with Precondition::DoesNotExist
458 /// - Azure Table Storage: InsertEntity (409 on conflict)
459 async fn put(&self, key: &str, value: Vec<u8>, options: Option<PutOptions>) -> Result<bool>;
460
461 /// Delete a key. No error if key doesn't exist.
462 ///
463 /// **Validation**: Keys are validated against MAX_KEY_BYTES and portable charset.
464 /// Invalid keys return `KvError::InvalidKey` immediately.
465 async fn delete(&self, key: &str) -> Result<()>;
466
467 /// Check if a key exists without retrieving the value.
468 ///
469 /// **TTL Behavior**: TTL is a soft hint for automatic cleanup. If `now >= expires_at`,
470 /// SHOULD return false even if physically present. Physical deletion is eventual and not guaranteed.
471 ///
472 /// **Validation**: Keys are validated against MAX_KEY_BYTES and portable charset.
473 /// Invalid keys return `KvError::InvalidKey` immediately.
474 async fn exists(&self, key: &str) -> Result<bool>;
475
476 /// Scan keys with a prefix, with pagination support.
477 ///
478 /// **Scan Contract**:
479 /// - Returns an **arbitrary, unordered subset** in backend-natural order
480 /// - **No ordering guarantees** across backends (Redis SCAN, Azure fan-out, etc.)
481 /// - **May return ≤ limit items** (not guaranteed to fill even if more data exists)
482 /// - **Clients MUST de-duplicate** keys across pages (backends may return duplicates)
483 /// - **No completeness guarantee** under concurrent writes (may miss or duplicate)
484 ///
485 /// **Cursor Behavior**:
486 /// - Opaque string, implementation-specific format
487 /// - **May become invalid** anytime after backend state changes
488 /// - **No TTL guarantees** - can expire without notice
489 /// - Passing invalid cursor should return error, not partial results
490 ///
491 /// **TTL Behavior**: TTL is a soft hint for automatic cleanup. Expired items SHOULD
492 /// be filtered out from results, but physical deletion is eventual and not guaranteed.
493 ///
494 /// **Validation**: Prefix follows same key validation rules.
495 /// Invalid prefix returns `KvError::InvalidKey` immediately.
496 async fn scan_prefix(
497 &self,
498 prefix: &str,
499 limit: Option<usize>,
500 cursor: Option<String>,
501 ) -> Result<ScanResult>;
502}
503
504/// JSON/Text message payload for Queue
505#[derive(Debug, Clone, Serialize, Deserialize)]
506#[serde(tag = "type", rename_all = "lowercase")]
507#[cfg_attr(feature = "openapi", derive(ToSchema))]
508pub enum MessagePayload {
509 /// JSON-serializable value
510 Json(serde_json::Value),
511 /// UTF-8 text payload
512 Text(String),
513}
514
515/// A queue message with payload and receipt handle for acknowledgment
516#[derive(Debug, Clone, Serialize, Deserialize)]
517#[serde(rename_all = "camelCase")]
518#[cfg_attr(feature = "openapi", derive(ToSchema))]
519pub struct QueueMessage {
520 /// JSON-first message payload
521 pub payload: MessagePayload,
522 /// Opaque receipt handle for acknowledgment (backend-specific, short-lived)
523 pub receipt_handle: String,
524}
525
526/// Maximum message size in bytes (64 KiB = 65,536 bytes)
527///
528/// This limit ensures compatibility across all queue backends:
529/// - **AWS SQS**: 256KB message limit (much higher, not constraining)
530/// - **Azure Service Bus**: 1MB message limit (much higher, not constraining)
531/// - **GCP Pub/Sub**: 10MB message limit (much higher, not constraining)
532///
533/// The 64KB limit provides:
534/// - Reasonable message sizes for most use cases
535/// - Fast network transfer and low latency
536/// - Consistent behavior across all cloud providers
537/// - Efficient memory usage during batch processing
538pub const MAX_MESSAGE_BYTES: usize = 65_536; // 64 KiB
539
540/// Maximum number of messages per receive call
541///
542/// This limit balances throughput with processing simplicity:
543/// - **AWS SQS**: Supports up to 10 messages per ReceiveMessage call
544/// - **Azure Service Bus**: Can receive multiple messages via prefetch/batching
545/// - **GCP Pub/Sub**: Supports configurable max_messages per Pull request
546///
547/// The 10-message limit ensures:
548/// - Portable batch sizes across all backends
549/// - Manageable memory usage
550/// - Reasonable processing latency per batch
551pub const MAX_BATCH_SIZE: usize = 10;
552
553/// Fixed lease duration in seconds
554///
555/// Messages are leased for exactly 30 seconds after delivery:
556/// - Long enough for most processing tasks
557/// - Short enough to enable fast retry on failures
558/// - Eliminates complexity of dynamic lease management
559/// - Consistent across all platforms
560pub const LEASE_SECONDS: u64 = 30;
561
562/// A trait for queue bindings providing minimal, portable queue operations.
563#[async_trait]
564pub trait Queue: Binding {
565 /// Send a message to the specified queue
566 async fn send(&self, queue: &str, message: MessagePayload) -> Result<()>;
567
568 /// Receive up to `max_messages` (1..=10) from the specified queue
569 async fn receive(&self, queue: &str, max_messages: usize) -> Result<Vec<QueueMessage>>;
570
571 /// Acknowledge a message using its receipt handle (idempotent)
572 async fn ack(&self, queue: &str, receipt_handle: &str) -> Result<()>;
573}
574
575/// Request for invoking a function directly
576#[derive(Debug, Clone, Serialize, Deserialize)]
577#[serde(rename_all = "camelCase")]
578#[cfg_attr(feature = "openapi", derive(ToSchema))]
579pub struct FunctionInvokeRequest {
580 /// Function identifier (name, ARN, URL, etc.)
581 pub target_function: String,
582 /// HTTP method
583 pub method: String,
584 /// Request path
585 pub path: String,
586 /// HTTP headers
587 pub headers: BTreeMap<String, String>,
588 /// Request body bytes
589 pub body: Vec<u8>,
590 /// Optional timeout for the invocation
591 pub timeout: Option<Duration>,
592}
593
594/// Response from function invocation
595#[derive(Debug, Clone, Serialize, Deserialize)]
596#[serde(rename_all = "camelCase")]
597#[cfg_attr(feature = "openapi", derive(ToSchema))]
598pub struct FunctionInvokeResponse {
599 /// HTTP status code
600 pub status: u16,
601 /// HTTP response headers
602 pub headers: BTreeMap<String, String>,
603 /// Response body bytes
604 pub body: Vec<u8>,
605}
606
607/// A trait for function bindings that enable direct function-to-function calls
608#[async_trait]
609pub trait Function: Binding {
610 /// Invoke a function with HTTP request data.
611 ///
612 /// This enables direct, low-latency function-to-function communication within
613 /// the same cloud environment, bypassing Commands for internal calls.
614 ///
615 /// Platform implementations:
616 /// - AWS: Uses InvokeFunction API directly
617 /// - GCP: Calls private service URL directly
618 /// - Azure: Calls private container app URL directly
619 /// - Kubernetes: HTTP call to internal service
620 async fn invoke(&self, request: FunctionInvokeRequest) -> Result<FunctionInvokeResponse>;
621
622 /// Get the public URL of the function, if available.
623 ///
624 /// Returns the function's public URL if it exists and is accessible.
625 /// This is useful for exposing public endpoints or getting URLs for
626 /// external integration.
627 ///
628 /// Platform implementations:
629 /// - AWS: Uses GetFunctionUrlConfig API or returns URL from binding
630 /// - GCP: Returns Cloud Run service URL or calls get_service API
631 /// - Azure: Returns Container App URL or calls get_container_app API
632 async fn get_function_url(&self) -> Result<Option<String>>;
633
634 /// Get a reference to this object as `Any` for dynamic casting
635 fn as_any(&self) -> &dyn std::any::Any;
636}
637
638/// A trait for container bindings that enable container-to-container communication
639#[async_trait]
640pub trait Container: Binding {
641 /// Get the internal URL for container-to-container communication.
642 ///
643 /// This returns the internal service discovery URL that other containers
644 /// in the same network can use to communicate with this container.
645 ///
646 /// Platform implementations:
647 /// - Horizon (AWS/GCP/Azure): Returns internal DNS URL (e.g., "http://api.svc:8080")
648 /// - Local (Docker): Returns Docker network DNS URL (e.g., "http://api.svc:3000")
649 fn get_internal_url(&self) -> &str;
650
651 /// Get the public URL of the container, if available.
652 ///
653 /// Returns the container's public URL if it exists and is accessible
654 /// from outside the cluster/network.
655 ///
656 /// Platform implementations:
657 /// - Horizon: Returns load balancer URL if exposed publicly
658 /// - Local: Returns localhost URL with mapped port (e.g., "http://localhost:62844")
659 fn get_public_url(&self) -> Option<&str>;
660
661 /// Get the container name/ID.
662 fn get_container_name(&self) -> &str;
663
664 /// Get a reference to this object as `Any` for dynamic casting
665 fn as_any(&self) -> &dyn std::any::Any;
666}
667
668/// A provider must implement methods to load the various types of bindings
669/// based on environment variables or other configuration sources.
670#[async_trait]
671pub trait BindingsProviderApi: Send + Sync + std::fmt::Debug {
672 /// Given a binding identifier, builds a Storage implementation.
673 async fn load_storage(&self, binding_name: &str) -> Result<Arc<dyn Storage>>;
674
675 /// Given a binding identifier, builds a Build implementation.
676 async fn load_build(&self, binding_name: &str) -> Result<Arc<dyn Build>>;
677
678 /// Given a binding identifier, builds an ArtifactRegistry implementation.
679 async fn load_artifact_registry(&self, binding_name: &str)
680 -> Result<Arc<dyn ArtifactRegistry>>;
681
682 /// Given a binding identifier, builds a Vault implementation.
683 async fn load_vault(&self, binding_name: &str) -> Result<Arc<dyn Vault>>;
684
685 /// Given a binding identifier, builds a KV implementation.
686 async fn load_kv(&self, binding_name: &str) -> Result<Arc<dyn Kv>>;
687
688 /// Given a binding identifier, builds a Queue implementation.
689 async fn load_queue(&self, binding_name: &str) -> Result<Arc<dyn Queue>>;
690
691 /// Given a binding identifier, builds a Function implementation.
692 async fn load_function(&self, binding_name: &str) -> Result<Arc<dyn Function>>;
693
694 /// Given a binding identifier, builds a Container implementation.
695 async fn load_container(&self, binding_name: &str) -> Result<Arc<dyn Container>>;
696
697 /// Given a binding identifier, builds a ServiceAccount implementation.
698 async fn load_service_account(&self, binding_name: &str) -> Result<Arc<dyn ServiceAccount>>;
699}