boundless_market/storage/config.rs
1// Copyright 2026 Boundless Foundation, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Configuration types for storage uploaders.
16
17use std::path::PathBuf;
18
19use clap::{builder::ArgPredicate, Args, ValueEnum};
20use derive_builder::Builder;
21use url::Url;
22
23/// The type of storage uploader to use for uploads.
24#[derive(Default, Clone, Debug, ValueEnum, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum StorageUploaderType {
27 /// No storage uploader.
28 #[default]
29 None,
30 /// S3 storage uploader.
31 #[cfg(feature = "s3")]
32 S3,
33 /// Google Cloud Storage uploader.
34 #[cfg(feature = "gcs")]
35 Gcs,
36 /// Pinata storage uploader.
37 Pinata,
38 /// Temporary file storage uploader.
39 File,
40 /// In-memory mock storage uploader for testing.
41 #[cfg(feature = "test-utils")]
42 Mock,
43}
44
45/// Configuration for the storage module (upload providers).
46///
47/// This configuration is used to set up storage uploaders for uploading programs and inputs.
48///
49/// # Authentication
50///
51/// - **S3**: Uses the AWS SDK default credential chain (environment variables,
52/// `~/.aws/credentials`, IAM role, etc.). No explicit credentials needed in config.
53/// - **GCS**: Uses the Google Cloud SDK default credential chain (ADC) via
54/// `GOOGLE_APPLICATION_CREDENTIALS`, workload identity, or `gcloud auth application-default login`.
55/// Explicit credentials can be provided via `gcs_credentials_json`.
56/// - **Pinata**: Requires a JWT token.
57#[derive(Clone, Default, Debug, Args, Builder)]
58#[non_exhaustive]
59pub struct StorageUploaderConfig {
60 /// Storage uploader to use [possible values: s3, gcs, pinata, file]
61 ///
62 /// - For 's3', the following option is required:
63 /// --s3-bucket (optionally: --s3-url, --aws-region)
64 /// - For 'gcs', the following option is required:
65 /// --gcs-bucket (optionally: --gcs-url, --gcs-credentials-json)
66 /// - For 'pinata', the following option is required:
67 /// --pinata-jwt (optionally: --pinata-api-url, --ipfs-gateway-url)
68 /// - For 'file', no additional options are required (optionally: --file-path)
69 #[arg(long, env, value_enum, default_value = "none", default_value_ifs = [
70 ("s3_bucket", ArgPredicate::IsPresent, "s3"),
71 ("gcs_bucket", ArgPredicate::IsPresent, "gcs"),
72 ("pinata_jwt", ArgPredicate::IsPresent, "pinata"),
73 ("file_path", ArgPredicate::IsPresent, "file")
74 ])]
75 #[builder(default)]
76 pub storage_uploader: StorageUploaderType,
77
78 // **S3 Storage Uploader Options**
79 /// S3 bucket name
80 #[cfg(feature = "s3")]
81 #[arg(long, env, required_if_eq("storage_uploader", "s3"))]
82 #[builder(setter(strip_option, into), default)]
83 pub s3_bucket: Option<String>,
84 /// S3 endpoint URL (optional, for S3-compatible services like MinIO)
85 #[cfg(feature = "s3")]
86 #[arg(long, env)]
87 #[builder(setter(strip_option, into), default)]
88 pub s3_url: Option<String>,
89 /// AWS access key (optional, uses AWS default credential chain if not set)
90 #[cfg(feature = "s3")]
91 #[arg(long, env)]
92 #[builder(setter(strip_option, into), default)]
93 pub aws_access_key_id: Option<String>,
94 /// AWS secret key (required if aws_access_key_id is set)
95 #[cfg(feature = "s3")]
96 #[arg(long, env)]
97 #[builder(setter(strip_option, into), default)]
98 pub aws_secret_access_key: Option<String>,
99 /// AWS region (optional, can be inferred from environment)
100 #[cfg(feature = "s3")]
101 #[arg(long, env)]
102 #[builder(setter(strip_option, into), default)]
103 pub aws_region: Option<String>,
104 /// Use presigned URLs for S3 (default: true)
105 #[cfg(feature = "s3")]
106 #[arg(long, env)]
107 #[builder(setter(strip_option), default)]
108 pub s3_presigned: Option<bool>,
109 /// Return public HTTPS URLs instead of s3:// or presigned URLs (requires bucket to be public)
110 #[cfg(feature = "s3")]
111 #[arg(long, env)]
112 #[builder(setter(strip_option), default)]
113 pub s3_public_url: Option<bool>,
114
115 // **GCS Storage Uploader Options**
116 /// GCS bucket name
117 #[cfg(feature = "gcs")]
118 #[arg(long, env, required_if_eq("storage_uploader", "gcs"))]
119 #[builder(setter(strip_option, into), default)]
120 pub gcs_bucket: Option<String>,
121 /// GCS endpoint URL (optional, for emulators like fake-gcs-server)
122 #[cfg(feature = "gcs")]
123 #[arg(long, env)]
124 #[builder(setter(strip_option), default)]
125 pub gcs_url: Option<String>,
126 /// GCS service account credentials JSON (optional, uses ADC if not set)
127 #[cfg(feature = "gcs")]
128 #[arg(long, env)]
129 #[builder(setter(strip_option, into), default)]
130 pub gcs_credentials_json: Option<String>,
131 /// Return public HTTPS URLs instead of gs:// URLs (requires bucket to be publicly readable)
132 #[cfg(feature = "gcs")]
133 #[arg(long, env)]
134 #[builder(setter(strip_option), default)]
135 pub gcs_public_url: Option<bool>,
136
137 // **Pinata Storage Uploader Options**
138 /// Pinata JWT
139 #[arg(long, env, required_if_eq("storage_uploader", "pinata"))]
140 #[builder(setter(strip_option, into), default)]
141 pub pinata_jwt: Option<String>,
142 /// Pinata API URL
143 #[arg(long, env)]
144 #[builder(setter(strip_option), default)]
145 pub pinata_api_url: Option<Url>,
146 /// Pinata gateway URL
147 #[arg(long, env)]
148 #[builder(setter(strip_option), default)]
149 pub ipfs_gateway_url: Option<Url>,
150
151 // **File Storage Uploader Options**
152 /// Path for file storage uploader
153 #[arg(long)]
154 #[builder(setter(strip_option, into), default)]
155 pub file_path: Option<PathBuf>,
156}
157
158impl StorageUploaderConfig {
159 /// Create a new builder to construct a config.
160 pub fn builder() -> StorageUploaderConfigBuilder {
161 Default::default()
162 }
163
164 /// Create a new configuration for a [StorageUploaderType::File].
165 pub fn dev_mode() -> Self {
166 Self { storage_uploader: StorageUploaderType::File, ..Default::default() }
167 }
168}
169
170/// Default IPFS gateway URL for fallback downloads.
171pub const DEFAULT_IPFS_GATEWAY_URL: &str = "https://gateway.beboundless.cloud";
172
173/// Configuration for download operations (construction-time settings).
174#[derive(Clone, Debug, PartialEq, Eq)]
175pub struct StorageDownloaderConfig {
176 /// Maximum size in bytes for downloaded content.
177 pub max_size: usize,
178 /// Maximum number of retry attempts for failed downloads.
179 ///
180 /// If not set, nothing is retried.
181 pub max_retries: Option<u8>,
182 /// Optional cache directory for storing downloaded images and inputs.
183 ///
184 /// If not set, files will be re-downloaded every time.
185 pub cache_dir: Option<PathBuf>,
186 /// Optional IPFS gateway URL for fallback when downloading IPFS content.
187 ///
188 /// When set, if an HTTP download fails for a URL containing `/ipfs/`,
189 /// the downloader will retry with this gateway.
190 pub ipfs_gateway: Option<Url>,
191}
192
193impl Default for StorageDownloaderConfig {
194 fn default() -> Self {
195 Self {
196 max_size: usize::MAX,
197 max_retries: None,
198 cache_dir: None,
199 // Safe to unwrap: DEFAULT_IPFS_GATEWAY_URL is a valid URL constant
200 ipfs_gateway: Some(Url::parse(DEFAULT_IPFS_GATEWAY_URL).unwrap()),
201 }
202 }
203}