fix(a2a): rename A2aErrorDetail.details to data per JSON-RPC 2.0 spec #3281

Merged
freemo merged 1 commits from fix/a2a-error-detail-data-field into master 2026-04-05 21:08:46 +00:00
3 changed files with 75 additions and 1 deletions
+21
View File
@@ -189,3 +189,24 @@ Feature: A2A JSON-RPC 2.0 wire format compliance
Given a wire-format facade with no services
When I dispatch wire-format method "_cleveragents/health/check" with id "test-id-123" and params {}
Then the wire-format response id should equal "test-id-123"
# -------------------------------------------------------------------
# A2aErrorDetail — JSON-RPC 2.0 field name compliance (issue #2745)
# -------------------------------------------------------------------
Scenario: A2aErrorDetail serialises with data field not details
Given an A2aErrorDetail with code "NOT_FOUND" and message "gone" and data {"id": "x"}
When I serialise the error detail to a dict
Then the error detail dict should contain key "data"
And the error detail dict should not contain key "details"
Scenario: A2aErrorDetail serialises with empty data by default
Given an A2aErrorDetail with code "ERR" and message "error" and no data
When I serialise the error detail to a dict
Then the error detail dict should contain key "data"
And the error detail dict should not contain key "details"
Scenario: A2aErrorDetail data field preserves payload values
Given an A2aErrorDetail with code "NOT_FOUND" and message "gone" and data {"plan_id": "abc123"}
When I serialise the error detail to a dict
Then the error detail data should contain key "plan_id"
1
@@ -350,5 +350,58 @@ def step_wire_response_id(context: Context, value: str) -> None:
)
# ---------------------------------------------------------------------------
# A2aErrorDetail — JSON-RPC 2.0 field name compliance (issue #2745)
# ---------------------------------------------------------------------------
@given(
r'an A2aErrorDetail with code "(?P<code>[^"]+)" and message "(?P<msg>[^"]+)" and data (?P<data_json>.+)'
)
def step_create_error_detail_with_data(
context: Context, code: str, msg: str, data_json: str
) -> None:
import json
data = json.loads(data_json)
context.error_detail = A2aErrorDetail(code=code, message=msg, data=data)
@given(
r'an A2aErrorDetail with code "(?P<code>[^"]+)" and message "(?P<msg>[^"]+)" and no data'
)
def step_create_error_detail_no_data(context: Context, code: str, msg: str) -> None:
context.error_detail = A2aErrorDetail(code=code, message=msg)
@when("I serialise the error detail to a dict")
def step_serialise_error_detail(context: Context) -> None:
context.error_detail_dict = context.error_detail.model_dump()
@then(r'the error detail dict should contain key "(?P<key>[^"]+)"')
def step_error_detail_dict_has_key(context: Context, key: str) -> None:
assert key in context.error_detail_dict, (
f"Expected key '{key}' in error detail dict, got keys: "
f"{list(context.error_detail_dict.keys())}"
)
@then(r'the error detail dict should not contain key "(?P<key>[^"]+)"')
def step_error_detail_dict_no_key(context: Context, key: str) -> None:
assert key not in context.error_detail_dict, (
f"Expected key '{key}' NOT in error detail dict, but it was present with value: "
f"{context.error_detail_dict.get(key)!r}"
)
@then(r'the error detail data should contain key "(?P<key>[^"]+)"')
def step_error_detail_data_has_key(context: Context, key: str) -> None:
data = context.error_detail_dict.get("data", {})
assert key in data, (
f"Expected key '{key}' in error detail data, got keys: {list(data.keys())}"
)
# Reset step matcher to parse (default) so subsequent step files are not affected
use_step_matcher("parse")
+1 -1
View File
@@ -68,7 +68,7 @@ class A2aErrorDetail(BaseModel):
code: str
message: str
details: dict[str, Any] = {}
data: dict[str, Any] = {}
@field_validator("code", "message")
@classmethod