This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit a8e10d099af36d0eeb7af6589662d3ab0fbe05e4 Author: Quan Tran <[email protected]> AuthorDate: Wed May 6 15:06:58 2026 +0700 JAMES-4182 Add tests documenting the metadata name format --- .../org/apache/james/blob/api/BlobStoreDAO.java | 14 +++++++ .../apache/james/blob/api/BlobMetadataTest.java | 44 ++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreDAO.java b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreDAO.java index ed0679bd51..a527dad825 100644 --- a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreDAO.java +++ b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStoreDAO.java @@ -38,6 +38,19 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteSource; import com.google.common.io.FileBackedOutputStream; +/** + * James virtual blob store abstraction. + * + * <p>A {@link BucketName} is a James-specific logical bucket. Each storage connector decides how this logical + * bucket is represented in its backend. It should not be conflated with an S3 bucket name and does not have to map one-to-one + * to a physical bucket.</p> + * + * <p>{@link BlobMetadata} is part of the contract so wrapper DAOs and storage implementations can keep side information + * needed to interpret a payload, such as compression markers. Metadata actively used by James should expose typed + * helpers, while the underlying metadata map remains an extension point for James library users and custom implementations.</p> + * + * <p>See {@code docs/modules/servers/partials/architecture/blobstore.adoc} for more details.</p> + */ public interface BlobStoreDAO { record BlobMetadataName(String name) { private static final CharMatcher CHAR_MATCHER = CharMatcher.inRange('a', 'z') @@ -46,6 +59,7 @@ public interface BlobStoreDAO { .or(CharMatcher.is('-')); public BlobMetadataName { + Preconditions.checkArgument(!name.isEmpty(), "Metadata name cannot be empty"); Preconditions.checkArgument(CHAR_MATCHER.matchesAllOf(name), "Invalid char in metadata name. Must be a-z,A-Z,0-9 or - got " + name); Preconditions.checkArgument(name.length() < 128, "Metadata name is too long. Size exceed 128 chars"); name = name.toLowerCase(Locale.US); diff --git a/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobMetadataTest.java b/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobMetadataTest.java index 3cabf6991a..4f36eea2a5 100644 --- a/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobMetadataTest.java +++ b/server/blob/blob-api/src/test/java/org/apache/james/blob/api/BlobMetadataTest.java @@ -20,10 +20,17 @@ package org.apache.james.blob.api; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class BlobMetadataTest { + private static final String ONE_HUNDRED_TWENTY_SEVEN_CHARS_METADATA_NAME = "a".repeat(127); + private static final String ONE_HUNDRED_TWENTY_EIGHT_CHARS_METADATA_NAME = "a".repeat(128); + @Test void blobMetadataNameShouldBeCaseInsensitive() { assertThat(new BlobStoreDAO.BlobMetadataName("X-Test").name()) @@ -31,4 +38,41 @@ class BlobMetadataTest { assertThat(new BlobStoreDAO.BlobMetadataName("X-Test")) .isEqualTo(new BlobStoreDAO.BlobMetadataName("x-test")); } + + @ParameterizedTest + @CsvSource({ + "metadata, metadata", + "CONTENT-ENCODING, content-encoding", + "x-test-123, x-test-123", + "A1-B2-C3, a1-b2-c3" + }) + void blobMetadataNameShouldAcceptLettersDigitsAndDash(String rawName, String expectedName) { + assertThat(new BlobStoreDAO.BlobMetadataName(rawName).name()) + .isEqualTo(expectedName); + } + + @ParameterizedTest + @ValueSource(strings = {"metadata_name", "metadata.name", "metadata name", "metadata/name", "metadata:name", "metadata#name"}) + void blobMetadataNameShouldRejectUnsupportedCharacters(String rawName) { + assertThatThrownBy(() -> new BlobStoreDAO.BlobMetadataName(rawName)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void blobMetadataNameShouldRejectEmptyName() { + assertThatThrownBy(() -> new BlobStoreDAO.BlobMetadataName("")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void blobMetadataNameShouldAcceptNameBelowOneHundredTwentyEightCharacters() { + assertThat(new BlobStoreDAO.BlobMetadataName(ONE_HUNDRED_TWENTY_SEVEN_CHARS_METADATA_NAME).name()) + .isEqualTo(ONE_HUNDRED_TWENTY_SEVEN_CHARS_METADATA_NAME); + } + + @Test + void blobMetadataNameShouldRejectNameOfOneHundredTwentyEightCharacters() { + assertThatThrownBy(() -> new BlobStoreDAO.BlobMetadataName(ONE_HUNDRED_TWENTY_EIGHT_CHARS_METADATA_NAME)) + .isInstanceOf(IllegalArgumentException.class); + } } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
