Files
v-hono/storage_errors.v
T
qinqx e7760ea80d feat: integrate multi-cloud storage (S3, OSS, COS, Local)
Add built-in storage support from v-hono-storage module:
- StorageProvider interface with 4 backends
- FileService with HTTP handlers
- Multipart upload support
- 86 property-based tests
- Retry mechanism with exponential backoff

Breaking: None (backward compatible)
2026-01-25 14:07:34 +08:00

501 lines
15 KiB
V

module hono
import time
// 存储错误类型枚举
pub enum StorageErrorKind {
// 可重试错误 - 网络和临时性问题
network_timeout
service_unavailable
rate_limited
connection_reset
temporary_failure
// 不可重试错误 - 配置和权限问题
invalid_credentials
access_denied
bucket_not_found
object_not_found
invalid_object_key
quota_exceeded
invalid_config
bucket_not_empty
bucket_already_exists
object_already_exists
invalid_range
checksum_mismatch
// 其他错误
unknown
}
// 存储错误结构
pub struct StorageError {
pub:
kind StorageErrorKind
message string
provider string
operation string
http_status int
retry_count int
details map[string]string
pub mut:
retry_history []RetryAttempt
}
// 重试尝试记录
pub struct RetryAttempt {
pub:
attempt_number int
timestamp i64
error_message string
delay_ms int
}
// 判断错误是否可重试
pub fn (e StorageError) is_retryable() bool {
return e.kind in [
.network_timeout,
.service_unavailable,
.rate_limited,
.connection_reset,
.temporary_failure,
]
}
// 获取建议的重试延迟(毫秒)
pub fn (e StorageError) suggested_retry_delay() int {
base_delay := match e.kind {
.rate_limited { 5000 } // 限流错误需要更长等待
.service_unavailable { 2000 }
.network_timeout { 1000 }
.connection_reset { 500 }
.temporary_failure { 1000 }
else { 0 }
}
return base_delay
}
// 获取错误类别描述
pub fn (e StorageError) category() string {
if e.is_retryable() {
return 'retryable'
}
return 'non_retryable'
}
// 实现 IError 接口
pub fn (e StorageError) msg() string {
return '${e.provider}/${e.operation}: ${e.message} (kind: ${e.kind}, http_status: ${e.http_status})'
}
pub fn (e StorageError) code() int {
return e.http_status
}
// 添加重试记录
pub fn (mut e StorageError) add_retry_attempt(attempt_number int, error_message string, delay_ms int) {
e.retry_history << RetryAttempt{
attempt_number: attempt_number
timestamp: time.now().unix()
error_message: error_message
delay_ms: delay_ms
}
}
// 获取重试历史摘要
pub fn (e StorageError) retry_summary() string {
if e.retry_history.len == 0 {
return 'No retry attempts'
}
mut summary := 'Retry attempts: ${e.retry_history.len}\n'
for attempt in e.retry_history {
summary += ' Attempt ${attempt.attempt_number}: ${attempt.error_message} (delay: ${attempt.delay_ms}ms)\n'
}
return summary
}
// 创建存储错误的辅助函数
pub fn new_storage_error(kind StorageErrorKind, message string, provider string, operation string) StorageError {
return StorageError{
kind: kind
message: message
provider: provider
operation: operation
http_status: 0
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建带 HTTP 状态码的存储错误
pub fn new_storage_error_with_status(kind StorageErrorKind, message string, provider string, operation string, http_status int) StorageError {
return StorageError{
kind: kind
message: message
provider: provider
operation: operation
http_status: http_status
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建带详情的存储错误
pub fn new_storage_error_with_details(kind StorageErrorKind, message string, provider string, operation string, http_status int, details map[string]string) StorageError {
return StorageError{
kind: kind
message: message
provider: provider
operation: operation
http_status: http_status
retry_count: 0
details: details
retry_history: []RetryAttempt{}
}
}
// 创建配置错误
pub fn new_config_error(message string, field string) StorageError {
mut details := map[string]string{}
details['field'] = field
return StorageError{
kind: .invalid_config
message: message
provider: 'config'
operation: 'validate'
http_status: 0
retry_count: 0
details: details
retry_history: []RetryAttempt{}
}
}
// 创建对象未找到错误
pub fn new_not_found_error(provider string, bucket string, key string) StorageError {
mut details := map[string]string{}
details['bucket'] = bucket
details['key'] = key
return StorageError{
kind: .object_not_found
message: 'Object not found: ${bucket}/${key}'
provider: provider
operation: 'get'
http_status: 404
retry_count: 0
details: details
retry_history: []RetryAttempt{}
}
}
// 创建存储桶未找到错误
pub fn new_bucket_not_found_error(provider string, bucket string) StorageError {
mut details := map[string]string{}
details['bucket'] = bucket
return StorageError{
kind: .bucket_not_found
message: 'Bucket not found: ${bucket}'
provider: provider
operation: 'bucket'
http_status: 404
retry_count: 0
details: details
retry_history: []RetryAttempt{}
}
}
// 创建访问拒绝错误
pub fn new_access_denied_error(provider string, operation string, resource string) StorageError {
mut details := map[string]string{}
details['resource'] = resource
return StorageError{
kind: .access_denied
message: 'Access denied to resource: ${resource}'
provider: provider
operation: operation
http_status: 403
retry_count: 0
details: details
retry_history: []RetryAttempt{}
}
}
// 创建网络超时错误
pub fn new_timeout_error(provider string, operation string) StorageError {
return StorageError{
kind: .network_timeout
message: 'Network timeout during ${operation}'
provider: provider
operation: operation
http_status: 0
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建服务不可用错误
pub fn new_service_unavailable_error(provider string, operation string, message string) StorageError {
return StorageError{
kind: .service_unavailable
message: message
provider: provider
operation: operation
http_status: 503
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建限流错误
pub fn new_rate_limited_error(provider string, operation string) StorageError {
return StorageError{
kind: .rate_limited
message: 'Rate limited during ${operation}'
provider: provider
operation: operation
http_status: 429
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建连接重置错误
pub fn new_connection_reset_error(provider string, operation string) StorageError {
return StorageError{
kind: .connection_reset
message: 'Connection reset during ${operation}'
provider: provider
operation: operation
http_status: 0
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建凭据无效错误
pub fn new_invalid_credentials_error(provider string, operation string) StorageError {
return StorageError{
kind: .invalid_credentials
message: 'Invalid credentials for ${provider}'
provider: provider
operation: operation
http_status: 401
retry_count: 0
details: map[string]string{}
retry_history: []RetryAttempt{}
}
}
// 创建配额超限错误
pub fn new_quota_exceeded_error(provider string, operation string, resource string) StorageError {
mut details := map[string]string{}
details['resource'] = resource
return StorageError{
kind: .quota_exceeded
message: 'Quota exceeded for ${resource}'
provider: provider
operation: operation
http_status: 413
retry_count: 0
details: details
retry_history: []RetryAttempt{}
}
}
// ============================================================================
// HTTP 状态码到错误类型的映射
// ============================================================================
// 从 HTTP 状态码映射到错误类型
pub fn error_kind_from_http_status(status int) StorageErrorKind {
return match status {
400 { StorageErrorKind.invalid_object_key }
401 { StorageErrorKind.invalid_credentials }
403 { StorageErrorKind.access_denied }
404 { StorageErrorKind.object_not_found }
409 { StorageErrorKind.bucket_already_exists }
413 { StorageErrorKind.quota_exceeded }
416 { StorageErrorKind.invalid_range }
429 { StorageErrorKind.rate_limited }
500 { StorageErrorKind.service_unavailable }
502 { StorageErrorKind.service_unavailable }
503 { StorageErrorKind.service_unavailable }
504 { StorageErrorKind.network_timeout }
else { StorageErrorKind.unknown }
}
}
// ============================================================================
// 提供者特定的错误映射
// ============================================================================
// S3 错误代码映射
pub fn map_s3_error_code(error_code string, http_status int) StorageErrorKind {
return match error_code {
'AccessDenied' { StorageErrorKind.access_denied }
'AccountProblem' { StorageErrorKind.access_denied }
'AllAccessDisabled' { StorageErrorKind.access_denied }
'BucketAlreadyExists' { StorageErrorKind.bucket_already_exists }
'BucketAlreadyOwnedByYou' { StorageErrorKind.bucket_already_exists }
'BucketNotEmpty' { StorageErrorKind.bucket_not_empty }
'CredentialsNotSupported' { StorageErrorKind.invalid_credentials }
'ExpiredToken' { StorageErrorKind.invalid_credentials }
'InvalidAccessKeyId' { StorageErrorKind.invalid_credentials }
'InvalidBucketName' { StorageErrorKind.invalid_object_key }
'InvalidObjectName' { StorageErrorKind.invalid_object_key }
'InvalidRange' { StorageErrorKind.invalid_range }
'InvalidSecurity' { StorageErrorKind.invalid_credentials }
'InvalidToken' { StorageErrorKind.invalid_credentials }
'NoSuchBucket' { StorageErrorKind.bucket_not_found }
'NoSuchKey' { StorageErrorKind.object_not_found }
'NoSuchUpload' { StorageErrorKind.object_not_found }
'RequestTimeout' { StorageErrorKind.network_timeout }
'RequestTimeTooSkewed' { StorageErrorKind.invalid_credentials }
'ServiceUnavailable' { StorageErrorKind.service_unavailable }
'SignatureDoesNotMatch' { StorageErrorKind.invalid_credentials }
'SlowDown' { StorageErrorKind.rate_limited }
'TemporaryRedirect' { StorageErrorKind.temporary_failure }
'TokenRefreshRequired' { StorageErrorKind.invalid_credentials }
else { error_kind_from_http_status(http_status) }
}
}
// 阿里云 OSS 错误代码映射
pub fn map_aliyun_oss_error_code(error_code string, http_status int) StorageErrorKind {
return match error_code {
'AccessDenied' { StorageErrorKind.access_denied }
'BucketAlreadyExists' { StorageErrorKind.bucket_already_exists }
'BucketNotEmpty' { StorageErrorKind.bucket_not_empty }
'EntityTooLarge' { StorageErrorKind.quota_exceeded }
'EntityTooSmall' { StorageErrorKind.invalid_object_key }
'FileGroupTooLarge' { StorageErrorKind.quota_exceeded }
'InvalidAccessKeyId' { StorageErrorKind.invalid_credentials }
'InvalidArgument' { StorageErrorKind.invalid_object_key }
'InvalidBucketName' { StorageErrorKind.invalid_object_key }
'InvalidDigest' { StorageErrorKind.checksum_mismatch }
'InvalidObjectName' { StorageErrorKind.invalid_object_key }
'InvalidRange' { StorageErrorKind.invalid_range }
'NoSuchBucket' { StorageErrorKind.bucket_not_found }
'NoSuchKey' { StorageErrorKind.object_not_found }
'NoSuchUpload' { StorageErrorKind.object_not_found }
'RequestTimeout' { StorageErrorKind.network_timeout }
'RequestTimeTooSkewed' { StorageErrorKind.invalid_credentials }
'ServiceUnavailable' { StorageErrorKind.service_unavailable }
'SignatureDoesNotMatch' { StorageErrorKind.invalid_credentials }
'TooManyBuckets' { StorageErrorKind.quota_exceeded }
else { error_kind_from_http_status(http_status) }
}
}
// 腾讯云 COS 错误代码映射
pub fn map_tencent_cos_error_code(error_code string, http_status int) StorageErrorKind {
return match error_code {
'AccessDenied' { StorageErrorKind.access_denied }
'BucketAlreadyExists' { StorageErrorKind.bucket_already_exists }
'BucketAlreadyOwnedByYou' { StorageErrorKind.bucket_already_exists }
'BucketNotEmpty' { StorageErrorKind.bucket_not_empty }
'EntityTooLarge' { StorageErrorKind.quota_exceeded }
'EntityTooSmall' { StorageErrorKind.invalid_object_key }
'InvalidAccessKeyId' { StorageErrorKind.invalid_credentials }
'InvalidArgument' { StorageErrorKind.invalid_object_key }
'InvalidBucketName' { StorageErrorKind.invalid_object_key }
'InvalidDigest' { StorageErrorKind.checksum_mismatch }
'InvalidObjectName' { StorageErrorKind.invalid_object_key }
'InvalidRange' { StorageErrorKind.invalid_range }
'NoSuchBucket' { StorageErrorKind.bucket_not_found }
'NoSuchKey' { StorageErrorKind.object_not_found }
'NoSuchUpload' { StorageErrorKind.object_not_found }
'RequestTimeout' { StorageErrorKind.network_timeout }
'RequestTimeTooSkewed' { StorageErrorKind.invalid_credentials }
'ServiceUnavailable' { StorageErrorKind.service_unavailable }
'SignatureDoesNotMatch' { StorageErrorKind.invalid_credentials }
'SlowDown' { StorageErrorKind.rate_limited }
'TooManyBuckets' { StorageErrorKind.quota_exceeded }
else { error_kind_from_http_status(http_status) }
}
}
// ============================================================================
// 从错误消息解析错误代码
// ============================================================================
// 从 XML 错误响应中提取错误代码
pub fn extract_error_code_from_xml(body string) string {
if code_start := body.index('<Code>') {
if code_end := body.index('</Code>') {
return body[code_start + 6..code_end]
}
}
return ''
}
// 从 XML 错误响应中提取错误消息
pub fn extract_error_message_from_xml(body string) string {
if message_start := body.index('<Message>') {
if message_end := body.index('</Message>') {
return body[message_start + 9..message_end]
}
}
return ''
}
// ============================================================================
// 创建提供者特定的错误
// ============================================================================
// 从 S3 响应创建错误
pub fn new_s3_error_from_response(body string, http_status int, operation string) StorageError {
error_code := extract_error_code_from_xml(body)
error_message := extract_error_message_from_xml(body)
kind := map_s3_error_code(error_code, http_status)
message := if error_message != '' { error_message } else { 'HTTP ${http_status}' }
mut details := map[string]string{}
if error_code != '' {
details['error_code'] = error_code
}
return new_storage_error_with_details(kind, message, 's3', operation, http_status, details)
}
// 从阿里云 OSS 响应创建错误
pub fn new_aliyun_oss_error_from_response(body string, http_status int, operation string) StorageError {
error_code := extract_error_code_from_xml(body)
error_message := extract_error_message_from_xml(body)
kind := map_aliyun_oss_error_code(error_code, http_status)
message := if error_message != '' { error_message } else { 'HTTP ${http_status}' }
mut details := map[string]string{}
if error_code != '' {
details['error_code'] = error_code
}
return new_storage_error_with_details(kind, message, 'aliyun_oss', operation, http_status, details)
}
// 从腾讯云 COS 响应创建错误
pub fn new_tencent_cos_error_from_response(body string, http_status int, operation string) StorageError {
error_code := extract_error_code_from_xml(body)
error_message := extract_error_message_from_xml(body)
kind := map_tencent_cos_error_code(error_code, http_status)
message := if error_message != '' { error_message } else { 'HTTP ${http_status}' }
mut details := map[string]string{}
if error_code != '' {
details['error_code'] = error_code
}
return new_storage_error_with_details(kind, message, 'tencent_cos', operation, http_status, details)
}