Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions server-common/src/main/java/io/a2a/server/util/ArtifactUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.a2a.server.util;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import io.a2a.spec.Artifact;
import io.a2a.spec.DataPart;
import io.a2a.spec.Part;
import io.a2a.spec.TextPart;

/**
* Utility functions for creating A2A Artifact objects.
*/
public final class ArtifactUtils {

private ArtifactUtils() {
// Utility class - prevent instantiation
}

/**
* Creates a new Artifact object.
*
* @param name The human-readable name of the artifact.
* @param parts The list of {@code Part} objects forming the artifact's content.
* @param description An optional description of the artifact.
* @return A new {@code Artifact} object with a generated artifact_id.
*/
public static Artifact newArtifact(String name, List<Part<?>> parts, String description) {
return new Artifact(
UUID.randomUUID().toString(),
name,
description,
parts,
null
);
}

/**
* Creates a new Artifact object with empty description.
*
* @param name The human-readable name of the artifact.
* @param parts The list of {@code Part} objects forming the artifact's content.
* @return A new {@code Artifact} object with a generated artifact_id.
*/
public static Artifact newArtifact(String name, List<Part<?>> parts) {
return newArtifact(name, parts, null);
}

/**
* Creates a new Artifact object containing only a single TextPart.
*
* @param name The human-readable name of the artifact.
* @param text The text content of the artifact.
* @param description An optional description of the artifact.
* @return A new {@code Artifact} object with a generated artifact_id.
*/
public static Artifact newTextArtifact(String name, String text, String description) {
return newArtifact(
name,
List.of(new TextPart(text)),
description
);
}

/**
* Creates a new Artifact object containing only a single TextPart with empty description.
*
* @param name The human-readable name of the artifact.
* @param text The text content of the artifact.
* @return A new {@code Artifact} object with a generated artifact_id.
*/
public static Artifact newTextArtifact(String name, String text) {
return newTextArtifact(name, text, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation passes null as the description when it's not provided. It might be better to pass an empty string ("") instead to avoid potential null pointer exceptions when the description is accessed elsewhere. This provides a default value and ensures consistency.

Consider updating the newTextArtifact method to handle null descriptions by defaulting to an empty string.

    public static Artifact newTextArtifact(String name, String text) {
        return newTextArtifact(name, text, ""); // Provide an empty string as default description
    }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null avoid serializing the description in json

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I aligned with your suggestion to keep description as null by default so that it’s omitted from JSON (@JsonInclude(NON_NULL)). Tests now assert null accordingly.

}

/**
* Creates a new Artifact object containing only a single DataPart.
*
* @param name The human-readable name of the artifact.
* @param data The structured data content of the artifact.
* @param description An optional description of the artifact.
* @return A new {@code Artifact} object with a generated artifact_id.
*/
public static Artifact newDataArtifact(String name, Map<String, Object> data, String description) {
return newArtifact(
name,
List.of(new DataPart(data)),
description
);
}

/**
* Creates a new Artifact object containing only a single DataPart with empty description.
*
* @param name The human-readable name of the artifact.
* @param data The structured data content of the artifact.
* @return A new {@code Artifact} object with a generated artifact_id.
*/
public static Artifact newDataArtifact(String name, Map<String, Object> data) {
return newDataArtifact(name, data, null);
}
}
154 changes: 154 additions & 0 deletions server-common/src/test/java/io/a2a/server/util/ArtifactUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package io.a2a.server.util;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.junit.jupiter.api.Test;

import io.a2a.spec.Artifact;
import io.a2a.spec.DataPart;
import io.a2a.spec.Part;
import io.a2a.spec.TextPart;
class ArtifactUtilsTest {

@Test
void testNewArtifactAssignsPartsNameDescription() {
// Given
List<Part<?>> parts = List.of(new TextPart("Sample text"));
String name = "My Artifact";
String description = "This is a test artifact.";

// When
Artifact artifact = ArtifactUtils.newArtifact(name, parts, description);

// Then
assertEquals(parts, artifact.parts());
assertEquals(name, artifact.name());
assertEquals(description, artifact.description());

// Then
assertNotNull(artifact.artifactId());
assertFalse(artifact.artifactId().isBlank());
assertDoesNotThrow(() -> UUID.fromString(artifact.artifactId()));
}

@Test
void testNewArtifactEmptyDescriptionIfNotProvided() {
// Given
List<Part<?>> parts = List.of(new TextPart("Another sample"));
String name = "Artifact_No_Desc";

// When
Artifact artifact = ArtifactUtils.newArtifact(name, parts);

// Then
assertEquals(null, artifact.description()); }

@Test
void testNewTextArtifactCreatesSingleTextPart() {
// Given
String text = "This is a text artifact.";
String name = "Text_Artifact";

// When
Artifact artifact = ArtifactUtils.newTextArtifact(name, text);

// Then
assertEquals(1, artifact.parts().size());
assertInstanceOf(TextPart.class, artifact.parts().get(0));
}

@Test
void testNewTextArtifactPartContainsProvidedText() {
// Given
String text = "Hello, world!";
String name = "Greeting_Artifact";

// When
Artifact artifact = ArtifactUtils.newTextArtifact(name, text);

// Then
TextPart textPart = (TextPart) artifact.parts().get(0);
assertEquals(text, textPart.getText());
}

@Test
void testNewTextArtifactAssignsNameDescription() {
// Given
String text = "Some content.";
String name = "Named_Text_Artifact";
String description = "Description for text artifact.";

// When
Artifact artifact = ArtifactUtils.newTextArtifact(name, text, description);

// Then
assertEquals(name, artifact.name());
assertEquals(description, artifact.description());
}

@Test
void testNewDataArtifactCreatesSingleDataPart() {
// Given
Map<String, Object> sampleData = Map.of("key", "value", "number", 123);
String name = "Data_Artifact";

// When
Artifact artifact = ArtifactUtils.newDataArtifact(name, sampleData);

// Then
assertEquals(1, artifact.parts().size());
assertInstanceOf(DataPart.class, artifact.parts().get(0));
}

@Test
void testNewDataArtifactPartContainsProvidedData() {
// Given
Map<String, Object> sampleData = Map.of("content", "test_data", "is_valid", true);
String name = "Structured_Data_Artifact";

// When
Artifact artifact = ArtifactUtils.newDataArtifact(name, sampleData);

// Then
DataPart dataPart = (DataPart) artifact.parts().get(0);
assertEquals(sampleData, dataPart.getData());
}

@Test
void testNewDataArtifactAssignsNameDescription() {
// Given
Map<String, Object> sampleData = Map.of("info", "some details");
String name = "Named_Data_Artifact";
String description = "Description for data artifact.";

// When
Artifact artifact = ArtifactUtils.newDataArtifact(name, sampleData, description);

// Then
assertEquals(name, artifact.name());
assertEquals(description, artifact.description());
}

@Test
void testArtifactIdIsNotNull() {
// Given
List<Part<?>> parts = List.of(new TextPart("Test"));
String name = "Test_Artifact";

// When
Artifact artifact = ArtifactUtils.newArtifact(name, parts);

// Then
assertNotNull(artifact.artifactId());
assertTrue(artifact.artifactId().length() > 0);
}
}