BREAKING CHANGE(core): rebrand from smarts3 to smartstorage
Default (tags) / security (push) Successful in 43s
Default (tags) / test (push) Failing after 26s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped

- Package renamed from @push.rocks/smarts3 to @push.rocks/smartstorage
- Class: Smarts3 → SmartStorage, Interface: ISmarts3Config → ISmartStorageConfig
- Method: getS3Descriptor → getStorageDescriptor
- Rust binary: rusts3 → ruststorage
- Rust types: S3Error→StorageError, S3Action→StorageAction, S3Config→SmartStorageConfig, S3Server→StorageServer
- On-disk file extension: ._S3_object → ._storage_object
- Default credentials: S3RVER → STORAGE
- All internal S3 branding removed; AWS S3 protocol compatibility fully maintained
This commit is contained in:
2026-03-14 15:20:30 +00:00
parent d437ffc226
commit bba0855218
26 changed files with 347 additions and 332 deletions
+33 -33
View File
@@ -18,22 +18,22 @@ use tokio::sync::watch;
use tokio_util::io::ReaderStream;
use uuid::Uuid;
use crate::action::{self, RequestContext, S3Action};
use crate::action::{self, RequestContext, StorageAction};
use crate::auth::{self, AuthenticatedIdentity};
use crate::config::S3Config;
use crate::config::SmartStorageConfig;
use crate::policy::{self, PolicyDecision, PolicyStore};
use crate::s3_error::S3Error;
use crate::error::StorageError;
use crate::storage::FileStore;
use crate::xml_response;
pub struct S3Server {
pub struct StorageServer {
store: Arc<FileStore>,
shutdown_tx: watch::Sender<bool>,
server_handle: tokio::task::JoinHandle<()>,
}
impl S3Server {
pub async fn start(config: S3Config) -> Result<Self> {
impl StorageServer {
pub async fn start(config: SmartStorageConfig) -> Result<Self> {
let store = Arc::new(FileStore::new(config.storage.directory.clone().into()));
// Initialize or reset storage
@@ -104,7 +104,7 @@ impl S3Server {
});
if !config.server.silent {
tracing::info!("S3 server listening on {}", addr);
tracing::info!("Storage server listening on {}", addr);
}
Ok(Self {
@@ -124,7 +124,7 @@ impl S3Server {
}
}
impl S3Config {
impl SmartStorageConfig {
fn address(&self) -> &str {
&self.server.address
}
@@ -192,7 +192,7 @@ fn empty_response(status: StatusCode, request_id: &str) -> Response<BoxBody> {
.unwrap()
}
fn s3_error_response(err: &S3Error, request_id: &str) -> Response<BoxBody> {
fn storage_error_response(err: &StorageError, request_id: &str) -> Response<BoxBody> {
let xml = err.to_xml();
Response::builder()
.status(err.status)
@@ -205,7 +205,7 @@ fn s3_error_response(err: &S3Error, request_id: &str) -> Response<BoxBody> {
async fn handle_request(
req: Request<Incoming>,
store: Arc<FileStore>,
config: S3Config,
config: SmartStorageConfig,
policy_store: Arc<PolicyStore>,
) -> Result<Response<BoxBody>, std::convert::Infallible> {
let request_id = Uuid::new_v4().to_string();
@@ -219,7 +219,7 @@ async fn handle_request(
return Ok(resp);
}
// Step 1: Resolve S3 action from request
// Step 1: Resolve storage action from request
let request_ctx = action::resolve_action(&req);
// Step 2: Auth + policy pipeline
@@ -238,7 +238,7 @@ async fn handle_request(
Ok(id) => Some(id),
Err(e) => {
tracing::warn!("Auth failed: {}", e.message);
return Ok(s3_error_response(&e, &request_id));
return Ok(storage_error_response(&e, &request_id));
}
}
} else {
@@ -248,7 +248,7 @@ async fn handle_request(
// Step 3: Authorization (policy evaluation)
if let Err(e) = authorize_request(&request_ctx, identity.as_ref(), &policy_store).await {
return Ok(s3_error_response(&e, &request_id));
return Ok(storage_error_response(&e, &request_id));
}
}
@@ -256,12 +256,12 @@ async fn handle_request(
let mut response = match route_request(req, store, &config, &request_id, &policy_store).await {
Ok(resp) => resp,
Err(err) => {
if let Some(s3err) = err.downcast_ref::<S3Error>() {
s3_error_response(s3err, &request_id)
if let Some(s3err) = err.downcast_ref::<StorageError>() {
storage_error_response(s3err, &request_id)
} else {
tracing::error!("Internal error: {}", err);
let s3err = S3Error::internal_error(&err.to_string());
s3_error_response(&s3err, &request_id)
let s3err = StorageError::internal_error(&err.to_string());
storage_error_response(&s3err, &request_id)
}
}
};
@@ -288,11 +288,11 @@ async fn authorize_request(
ctx: &RequestContext,
identity: Option<&AuthenticatedIdentity>,
policy_store: &PolicyStore,
) -> Result<(), S3Error> {
) -> Result<(), StorageError> {
// ListAllMyBuckets requires authentication (no bucket to apply policy to)
if ctx.action == S3Action::ListAllMyBuckets {
if ctx.action == StorageAction::ListAllMyBuckets {
if identity.is_none() {
return Err(S3Error::access_denied());
return Err(StorageError::access_denied());
}
return Ok(());
}
@@ -302,7 +302,7 @@ async fn authorize_request(
if let Some(bucket_policy) = policy_store.get_policy(bucket).await {
let decision = policy::evaluate_policy(&bucket_policy, ctx, identity);
match decision {
PolicyDecision::Deny => return Err(S3Error::access_denied()),
PolicyDecision::Deny => return Err(StorageError::access_denied()),
PolicyDecision::Allow => return Ok(()),
PolicyDecision::NoOpinion => {
// Fall through to default behavior
@@ -313,7 +313,7 @@ async fn authorize_request(
// Default: authenticated users get full access, anonymous denied
if identity.is_none() {
return Err(S3Error::access_denied());
return Err(StorageError::access_denied());
}
Ok(())
@@ -326,7 +326,7 @@ async fn authorize_request(
async fn route_request(
req: Request<Incoming>,
store: Arc<FileStore>,
_config: &S3Config,
_config: &SmartStorageConfig,
request_id: &str,
policy_store: &Arc<PolicyStore>,
) -> Result<Response<BoxBody>> {
@@ -414,8 +414,8 @@ async fn route_request(
let upload_id = query.get("uploadId").unwrap().clone();
handle_complete_multipart(req, store, &bucket, &key, &upload_id, request_id).await
} else {
let err = S3Error::invalid_request("Invalid POST request");
Ok(s3_error_response(&err, request_id))
let err = StorageError::invalid_request("Invalid POST request");
Ok(storage_error_response(&err, request_id))
}
}
_ => Ok(empty_response(StatusCode::METHOD_NOT_ALLOWED, request_id)),
@@ -467,7 +467,7 @@ async fn handle_head_bucket(
if store.bucket_exists(bucket).await {
Ok(empty_response(StatusCode::OK, request_id))
} else {
Err(S3Error::no_such_bucket().into())
Err(StorageError::no_such_bucket().into())
}
}
@@ -682,7 +682,7 @@ async fn handle_get_bucket_policy(
.unwrap();
Ok(resp)
}
None => Err(S3Error::no_such_bucket_policy().into()),
None => Err(StorageError::no_such_bucket_policy().into()),
}
}
@@ -695,7 +695,7 @@ async fn handle_put_bucket_policy(
) -> Result<Response<BoxBody>> {
// Verify bucket exists
if !store.bucket_exists(bucket).await {
return Err(S3Error::no_such_bucket().into());
return Err(StorageError::no_such_bucket().into());
}
// Read body
@@ -709,7 +709,7 @@ async fn handle_put_bucket_policy(
policy_store
.put_policy(bucket, validated_policy)
.await
.map_err(|e| S3Error::internal_error(&e.to_string()))?;
.map_err(|e| StorageError::internal_error(&e.to_string()))?;
Ok(empty_response(StatusCode::NO_CONTENT, request_id))
}
@@ -722,7 +722,7 @@ async fn handle_delete_bucket_policy(
policy_store
.delete_policy(bucket)
.await
.map_err(|e| S3Error::internal_error(&e.to_string()))?;
.map_err(|e| StorageError::internal_error(&e.to_string()))?;
Ok(empty_response(StatusCode::NO_CONTENT, request_id))
}
@@ -756,7 +756,7 @@ async fn handle_upload_part(
.unwrap_or(0);
if part_number < 1 || part_number > 10000 {
return Err(S3Error::invalid_part_number().into());
return Err(StorageError::invalid_part_number().into());
}
let body = req.into_body();
@@ -925,7 +925,7 @@ fn extract_xml_value<'a>(xml: &'a str, tag: &str) -> Option<String> {
// CORS
// ============================
fn build_cors_preflight(config: &S3Config, request_id: &str) -> Response<BoxBody> {
fn build_cors_preflight(config: &SmartStorageConfig, request_id: &str) -> Response<BoxBody> {
let mut builder = Response::builder()
.status(StatusCode::NO_CONTENT)
.header("x-amz-request-id", request_id);
@@ -949,7 +949,7 @@ fn build_cors_preflight(config: &S3Config, request_id: &str) -> Response<BoxBody
builder.body(empty_body()).unwrap()
}
fn add_cors_headers(headers: &mut hyper::HeaderMap, config: &S3Config) {
fn add_cors_headers(headers: &mut hyper::HeaderMap, config: &SmartStorageConfig) {
if let Some(ref origins) = config.cors.allowed_origins {
headers.insert(
"access-control-allow-origin",