diff --git a/core/src/services/gcs/backend.rs b/core/src/services/gcs/backend.rs index 7cfa47cf7f3..a53a20ab3d6 100644 --- a/core/src/services/gcs/backend.rs +++ b/core/src/services/gcs/backend.rs @@ -383,6 +383,7 @@ impl Accessor for GcsBackend { read_can_next: true, write: true, + write_with_content_type: true, write_without_content_length: true, list: true, diff --git a/core/src/services/obs/backend.rs b/core/src/services/obs/backend.rs index 8a360f6199b..4eaf9c01011 100644 --- a/core/src/services/obs/backend.rs +++ b/core/src/services/obs/backend.rs @@ -314,6 +314,7 @@ impl Accessor for ObsBackend { read_can_next: true, write: true, + write_with_content_type: true, write_with_cache_control: true, list: true, diff --git a/core/src/services/oss/backend.rs b/core/src/services/oss/backend.rs index a4284dc3fad..1388c25a614 100644 --- a/core/src/services/oss/backend.rs +++ b/core/src/services/oss/backend.rs @@ -433,6 +433,7 @@ impl Accessor for OssBackend { write: true, write_with_cache_control: true, + write_with_content_type: true, write_without_content_length: true, list: true, diff --git a/core/src/services/s3/backend.rs b/core/src/services/s3/backend.rs index 44d8b5c6b12..c718460e086 100644 --- a/core/src/services/s3/backend.rs +++ b/core/src/services/s3/backend.rs @@ -925,6 +925,7 @@ impl Accessor for S3Backend { write: true, write_with_cache_control: true, + write_with_content_type: true, write_without_content_length: true, list: true, diff --git a/core/tests/behavior/write.rs b/core/tests/behavior/write.rs index 31b4cfcd258..c1212cfbb2f 100644 --- a/core/tests/behavior/write.rs +++ b/core/tests/behavior/write.rs @@ -21,7 +21,9 @@ use futures::AsyncSeekExt; use futures::StreamExt; use log::debug; use log::warn; -use opendal::ops::{OpRead, OpStat, OpWrite}; +use opendal::ops::OpRead; +use opendal::ops::OpStat; +use opendal::ops::OpWrite; use opendal::EntryMode; use opendal::ErrorKind; use opendal::Operator; @@ -76,6 +78,7 @@ macro_rules! behavior_write_tests { test_write_with_dir_path, test_write_with_special_chars, test_write_with_cache_control, + test_write_with_content_type, test_stat, test_stat_dir, test_stat_with_special_chars, @@ -180,7 +183,7 @@ pub async fn test_write_with_special_chars(op: Operator) -> Result<()> { Ok(()) } -// Write a single file with cache control should succeed. +/// Write a single file with cache control should succeed. pub async fn test_write_with_cache_control(op: Operator) -> Result<()> { if !op.info().capability().write_with_cache_control { return Ok(()); @@ -208,6 +211,35 @@ pub async fn test_write_with_cache_control(op: Operator) -> Result<()> { Ok(()) } +/// Write a single file with content type should succeed. +pub async fn test_write_with_content_type(op: Operator) -> Result<()> { + if !op.info().capability().write_with_content_type { + return Ok(()); + } + + let path = uuid::Uuid::new_v4().to_string(); + let (content, size) = gen_bytes(); + + let target_content_type = "application/json"; + + let mut op_write = OpWrite::default(); + op_write = op_write.with_content_type(target_content_type); + + op.write_with(&path, op_write, content).await?; + + let meta = op.stat(&path).await.expect("stat must succeed"); + assert_eq!(meta.mode(), EntryMode::FILE); + assert_eq!( + meta.content_type().expect("content type must exist"), + target_content_type + ); + assert_eq!(meta.content_length(), size as u64); + + op.delete(&path).await.expect("delete must succeed"); + + Ok(()) +} + /// Stat existing file should return metadata pub async fn test_stat(op: Operator) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); @@ -537,7 +569,7 @@ pub async fn test_read_not_exist(op: Operator) -> Result<()> { Ok(()) } -// Read with if_match should match, else get a ConditionNotMatch error. +/// Read with if_match should match, else get a ConditionNotMatch error. pub async fn test_read_with_if_match(op: Operator) -> Result<()> { if !op.info().capability().read_with_if_match { return Ok(()); @@ -722,7 +754,7 @@ pub async fn test_read_with_special_chars(op: Operator) -> Result<()> { Ok(()) } -// Read file with override_content_disposition should succeed. +/// Read file with override_content_disposition should succeed. pub async fn test_read_with_override_content_disposition(op: Operator) -> Result<()> { if !(op .info() @@ -775,7 +807,7 @@ pub async fn test_read_with_override_content_disposition(op: Operator) -> Result Ok(()) } -// Delete existing file should succeed. +/// Delete existing file should succeed. pub async fn test_writer_abort(op: Operator) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); let (content, _) = gen_bytes(); @@ -803,7 +835,7 @@ pub async fn test_writer_abort(op: Operator) -> Result<()> { Ok(()) } -// Delete existing file should succeed. +/// Delete existing file should succeed. pub async fn test_delete(op: Operator) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); let (content, _) = gen_bytes(); @@ -818,7 +850,7 @@ pub async fn test_delete(op: Operator) -> Result<()> { Ok(()) } -// Delete empty dir should succeed. +/// Delete empty dir should succeed. pub async fn test_delete_empty_dir(op: Operator) -> Result<()> { let path = format!("{}/", uuid::Uuid::new_v4()); @@ -829,7 +861,7 @@ pub async fn test_delete_empty_dir(op: Operator) -> Result<()> { Ok(()) } -// Delete file with special chars should succeed. +/// Delete file with special chars should succeed. pub async fn test_delete_with_special_chars(op: Operator) -> Result<()> { let path = format!("{} !@#$%^&()_+-=;',.txt", uuid::Uuid::new_v4()); debug!("Generate a random file: {}", &path); @@ -845,7 +877,7 @@ pub async fn test_delete_with_special_chars(op: Operator) -> Result<()> { Ok(()) } -// Delete not existing file should also succeed. +/// Delete not existing file should also succeed. pub async fn test_delete_not_existing(op: Operator) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); @@ -854,7 +886,7 @@ pub async fn test_delete_not_existing(op: Operator) -> Result<()> { Ok(()) } -// Delete via stream. +/// Delete via stream. pub async fn test_delete_stream(op: Operator) -> Result<()> { let dir = uuid::Uuid::new_v4().to_string(); op.create_dir(&format!("{dir}/")) @@ -881,7 +913,7 @@ pub async fn test_delete_stream(op: Operator) -> Result<()> { Ok(()) } -// Append data into writer +/// Append data into writer pub async fn test_writer_write(op: Operator) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); let size = 5 * 1024 * 1024; // write file with 5 MiB @@ -920,7 +952,7 @@ pub async fn test_writer_write(op: Operator) -> Result<()> { Ok(()) } -// copy data from reader to writer +/// Copy data from reader to writer pub async fn test_writer_futures_copy(op: Operator) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); let (content, size): (Vec, usize) =