feat: type stateless draft results
This commit is contained in:
@@ -74,10 +74,10 @@
|
||||
- Typed-result slices now give `workflow.health`, all artifact, deployment,
|
||||
and run operations, every persisted draft-workspace operation, and both the
|
||||
capability and source-discovery surfaces named transport-neutral result
|
||||
schemas: 53 of 70 methods. The remaining 17 success results still collapse
|
||||
to generic objects across stateless draft patch/validate and the
|
||||
source-registry/admin operations. Continue introducing operation result DTOs
|
||||
before adopting generated TypeScript contracts.
|
||||
schemas: 55 of 70 methods. The remaining 15 success results still collapse
|
||||
to generic objects across the source-registry/admin operations. Continue
|
||||
introducing operation result DTOs before adopting generated TypeScript
|
||||
contracts.
|
||||
- The stock `@open-rpc/generator` TypeScript client is not suitable here. It
|
||||
exhausted a 4 GB Node heap on the full contract and emitted invalid dotted
|
||||
class members plus `any` results for a minimal `workflow.health` contract.
|
||||
|
||||
+33
-7
@@ -60,6 +60,11 @@ from .models import (
|
||||
InvalidDraftResult,
|
||||
JsonProjector,
|
||||
ListDraftWorkspacesResult,
|
||||
PatchDraftResult,
|
||||
PatchedDraftInvalidResult,
|
||||
PatchedDraftValidResult,
|
||||
ValidateDraftResult,
|
||||
ValidDraftResult,
|
||||
)
|
||||
from .operation_context import WorkflowOperationContext
|
||||
from .schema_projection import project_property_to_schema_path, schema_path_exists
|
||||
@@ -69,6 +74,23 @@ _PROJECT_DRAFT_WORKSPACE_LIST = JsonProjector(ListDraftWorkspacesResult)
|
||||
_PROJECT_DRAFT_WORKSPACE_DELETE = JsonProjector(DeleteDraftWorkspaceResult)
|
||||
_PROJECT_DRAFT_COMPILE = JsonProjector(CompileDraftWorkspaceSuccess)
|
||||
_PROJECT_INVALID_DRAFT = JsonProjector(InvalidDraftResult)
|
||||
_PROJECT_VALID_DRAFT = JsonProjector(ValidDraftResult)
|
||||
_PROJECT_PATCHED_DRAFT_VALID = JsonProjector(PatchedDraftValidResult)
|
||||
_PROJECT_PATCHED_DRAFT_INVALID = JsonProjector(PatchedDraftInvalidResult)
|
||||
|
||||
|
||||
def _project_validate_draft(payload: dict[str, Any]) -> ValidateDraftResult:
|
||||
"""Project one validation result through the matching status variant."""
|
||||
if payload.get("status") == "valid":
|
||||
return _PROJECT_VALID_DRAFT(payload)
|
||||
return _PROJECT_INVALID_DRAFT(payload)
|
||||
|
||||
|
||||
def _project_patch_draft(payload: dict[str, Any]) -> PatchDraftResult:
|
||||
"""Project one patch result while preserving an optional invalid draft."""
|
||||
if payload.get("status") == "valid":
|
||||
return _PROJECT_PATCHED_DRAFT_VALID(payload)
|
||||
return _PROJECT_PATCHED_DRAFT_INVALID(payload)
|
||||
|
||||
|
||||
def _empty_object_schema() -> dict[str, Any]:
|
||||
@@ -174,12 +196,14 @@ class WorkflowDraftApi:
|
||||
node_defs.append(spec.to_node_def())
|
||||
return node_defs
|
||||
|
||||
async def validate_draft(self, *, draft: dict[str, Any]) -> dict[str, Any]:
|
||||
return validate_workflow_draft(
|
||||
async def validate_draft(self, *, draft: dict[str, Any]) -> ValidateDraftResult:
|
||||
return _project_validate_draft(
|
||||
validate_workflow_draft(
|
||||
draft,
|
||||
outcome_lookup=self._outcomes_for_capability,
|
||||
node_defs=self._node_defs_for_draft(draft),
|
||||
)
|
||||
)
|
||||
|
||||
async def compile_draft(
|
||||
self, *, draft: dict[str, Any]
|
||||
@@ -203,12 +227,14 @@ class WorkflowDraftApi:
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
return patch_workflow_draft(
|
||||
) -> PatchDraftResult:
|
||||
return _project_patch_draft(
|
||||
patch_workflow_draft(
|
||||
draft,
|
||||
patch,
|
||||
node_defs_for_draft=self._node_defs_for_draft,
|
||||
)
|
||||
)
|
||||
|
||||
async def list_draft_workspaces(self) -> ListDraftWorkspacesResult:
|
||||
"""Return compact summaries for stored draft workspaces."""
|
||||
@@ -887,14 +913,14 @@ def _path_text(value: Any, *, expected_root: str | None = None) -> str:
|
||||
|
||||
|
||||
def _with_workspace_repair_hints(
|
||||
payload: dict[str, Any],
|
||||
payload: Mapping[str, Any],
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
) -> dict[str, Any]:
|
||||
diagnostics = payload.get("diagnostics")
|
||||
if not isinstance(diagnostics, list):
|
||||
return payload
|
||||
return dict(payload)
|
||||
enriched = []
|
||||
changed = False
|
||||
for diagnostic in diagnostics:
|
||||
@@ -912,7 +938,7 @@ def _with_workspace_repair_hints(
|
||||
changed = True
|
||||
enriched.append(repaired)
|
||||
if not changed:
|
||||
return payload
|
||||
return dict(payload)
|
||||
return {**payload, "diagnostics": enriched}
|
||||
|
||||
|
||||
|
||||
@@ -56,8 +56,13 @@ from .drafts import (
|
||||
DraftWorkspaceSummary,
|
||||
InvalidDraftResult,
|
||||
ListDraftWorkspacesResult,
|
||||
PatchDraftResult,
|
||||
PatchedDraftInvalidResult,
|
||||
PatchedDraftValidResult,
|
||||
SavedDraftArtifactResult,
|
||||
UnsavedDraftArtifactResult,
|
||||
ValidateDraftResult,
|
||||
ValidDraftResult,
|
||||
WrapperAuthoringHintsPayload,
|
||||
WrapperMissingDecisionPayload,
|
||||
WrapperOutcomeCandidatePayload,
|
||||
@@ -136,6 +141,9 @@ __all__ = [
|
||||
"NodeSpecCapabilitySummary",
|
||||
"NodeSpecInventoryPayload",
|
||||
"PageMetadataPayload",
|
||||
"PatchDraftResult",
|
||||
"PatchedDraftInvalidResult",
|
||||
"PatchedDraftValidResult",
|
||||
"RawWorkflowPlan",
|
||||
"ReducerInventoryPayload",
|
||||
"ResumeReadiness",
|
||||
@@ -163,7 +171,9 @@ __all__ = [
|
||||
"TraceRange",
|
||||
"TraceEntryPayload",
|
||||
"UnsavedDraftArtifactResult",
|
||||
"ValidateDraftResult",
|
||||
"ValidateDeploymentResult",
|
||||
"ValidDraftResult",
|
||||
"WorkflowDeploymentPayload",
|
||||
"WorkflowArtifactPayload",
|
||||
"WorkflowRefPayload",
|
||||
|
||||
@@ -61,6 +61,34 @@ class InvalidDraftResult(TypedDict):
|
||||
diagnostics: list[DraftDiagnosticPayload]
|
||||
|
||||
|
||||
class ValidDraftResult(TypedDict):
|
||||
"""Successful validation of one stateless draft document."""
|
||||
|
||||
status: Literal["valid"]
|
||||
diagnostics: list[DraftDiagnosticPayload]
|
||||
compiled_plan: JsonObject
|
||||
|
||||
|
||||
type ValidateDraftResult = ValidDraftResult | InvalidDraftResult
|
||||
|
||||
|
||||
class PatchedDraftValidResult(ValidDraftResult):
|
||||
"""Valid draft document produced after applying a JSON Patch."""
|
||||
|
||||
draft: JsonObject
|
||||
|
||||
|
||||
class PatchedDraftInvalidResult(InvalidDraftResult):
|
||||
"""Invalid patch result, optionally retaining the applied draft document."""
|
||||
|
||||
# Malformed JSON Patch cannot produce a document. Applied patches retain
|
||||
# their invalid document so a later edit can repair it.
|
||||
draft: NotRequired[JsonObject]
|
||||
|
||||
|
||||
type PatchDraftResult = PatchedDraftValidResult | PatchedDraftInvalidResult
|
||||
|
||||
|
||||
class CompileDraftWorkspaceSuccess(TypedDict):
|
||||
"""Compiled raw plan and dependencies for one valid draft workspace."""
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ from .models import (
|
||||
ListDeploymentsResult,
|
||||
ListDraftWorkspacesResult,
|
||||
ListRunsResult,
|
||||
PatchDraftResult,
|
||||
RawWorkflowPlan,
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
@@ -36,6 +37,7 @@ from .models import (
|
||||
SavedDraftArtifactResult,
|
||||
SaveDeploymentResult,
|
||||
ValidateDeploymentResult,
|
||||
ValidateDraftResult,
|
||||
WorkflowArtifactPayload,
|
||||
WorkflowDeploymentPayload,
|
||||
)
|
||||
@@ -254,7 +256,7 @@ class WorkflowApi:
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> ValidateDraftResult:
|
||||
return await self.drafts.validate_draft(draft=draft)
|
||||
|
||||
async def compile_draft(
|
||||
@@ -269,7 +271,7 @@ class WorkflowApi:
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
) -> PatchDraftResult:
|
||||
return await self.drafts.patch_draft(draft=draft, patch=patch)
|
||||
|
||||
# -- draft workspaces --
|
||||
|
||||
@@ -26,12 +26,14 @@ from .models import (
|
||||
ListDraftWorkspacesResult,
|
||||
ListRunsResult,
|
||||
ListSourcesResult,
|
||||
PatchDraftResult,
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
SaveArtifactResult,
|
||||
SaveDeploymentResult,
|
||||
SourceDiagnosisResult,
|
||||
ValidateDeploymentResult,
|
||||
ValidateDraftResult,
|
||||
WorkflowArtifactPayload,
|
||||
WorkflowDeploymentPayload,
|
||||
)
|
||||
@@ -72,6 +74,19 @@ class WorkflowDraftSurface(Protocol):
|
||||
not every same-process authoring helper on ``WorkflowApi``.
|
||||
"""
|
||||
|
||||
async def validate_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> ValidateDraftResult: ...
|
||||
|
||||
async def patch_draft(
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> PatchDraftResult: ...
|
||||
|
||||
async def list_draft_workspaces(self) -> ListDraftWorkspacesResult: ...
|
||||
|
||||
async def get_draft_workspace(
|
||||
|
||||
@@ -11,6 +11,8 @@ from wf_api.models import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListDraftWorkspacesResult,
|
||||
PatchDraftResult,
|
||||
ValidateDraftResult,
|
||||
)
|
||||
from wf_api.surface import RouteSource
|
||||
from wf_artifacts.drafts.models import DraftStep
|
||||
@@ -31,6 +33,30 @@ async def _call_draft_workspace(
|
||||
class RpcDraftClientMixin:
|
||||
"""JSON-RPC implementation of workflow draft workspace surface methods."""
|
||||
|
||||
async def validate_draft(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> ValidateDraftResult:
|
||||
return cast(
|
||||
ValidateDraftResult,
|
||||
await self._call("workflow.drafts.validate", {"draft": draft}),
|
||||
)
|
||||
|
||||
async def patch_draft(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
patch: list[dict[str, Any]],
|
||||
) -> PatchDraftResult:
|
||||
return cast(
|
||||
PatchDraftResult,
|
||||
await self._call(
|
||||
"workflow.drafts.patch",
|
||||
{"draft": draft, "patch": patch},
|
||||
),
|
||||
)
|
||||
|
||||
async def list_draft_workspaces(self: RpcCaller) -> ListDraftWorkspacesResult:
|
||||
return cast(
|
||||
ListDraftWorkspacesResult,
|
||||
|
||||
@@ -4,8 +4,6 @@ Return annotations stay eagerly evaluated because fastapi-jsonrpc captures them
|
||||
while registering nested handlers for response validation and OpenRPC output.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import fastapi_jsonrpc as jsonrpc
|
||||
|
||||
from wf_api.models import (
|
||||
@@ -15,6 +13,8 @@ from wf_api.models import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListDraftWorkspacesResult,
|
||||
PatchDraftResult,
|
||||
ValidateDraftResult,
|
||||
)
|
||||
from wf_api.surface import RouteSource
|
||||
from wf_server import WorkflowServer
|
||||
@@ -77,7 +77,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.drafts.patch", errors=[WorkflowRpcError])
|
||||
async def workflow_drafts_patch(
|
||||
params: PatchDraftParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> PatchDraftResult:
|
||||
try:
|
||||
return await server.api.patch_draft(draft=params.draft, patch=params.patch)
|
||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||
@@ -86,7 +86,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.drafts.validate", errors=[WorkflowRpcError])
|
||||
async def workflow_drafts_validate(
|
||||
params: ValidateDraftParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> ValidateDraftResult:
|
||||
try:
|
||||
return await server.api.validate_draft(draft=params.draft)
|
||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||
|
||||
@@ -740,6 +740,36 @@ async def test_patch_draft_applies_json_patch(tmp_path: Path) -> None:
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_patch_draft_projects_valid_applied_document(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "drafts_patch_valid")
|
||||
api, _service, _authoring = _draft_api(artifact_store, register_echo=True)
|
||||
|
||||
result = await api.patch_draft(
|
||||
draft=_echo_draft(),
|
||||
patch=[{"op": "replace", "path": "/name", "value": "renamed_echo"}],
|
||||
)
|
||||
|
||||
assert result["status"] == "valid"
|
||||
assert result["draft"]["name"] == "renamed_echo"
|
||||
assert result["compiled_plan"]["name"] == "renamed_echo"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_patch_draft_malformed_patch_has_no_draft(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "drafts_patch_malformed")
|
||||
api, _service, _authoring = _draft_api(artifact_store, register_echo=True)
|
||||
|
||||
result = await api.patch_draft(
|
||||
draft=_echo_draft(),
|
||||
patch=[{"op": "remove", "path": "/missing"}],
|
||||
)
|
||||
|
||||
assert result["status"] == "invalid"
|
||||
assert result["diagnostics"][0]["code"] == "patch_invalid"
|
||||
assert "draft" not in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_draft_workspace_creates_workspace(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "drafts_create_workspace")
|
||||
|
||||
@@ -31,6 +31,48 @@ async def _rpc(
|
||||
return response.json()
|
||||
|
||||
|
||||
def _rpc_constant_draft() -> dict[str, Any]:
|
||||
"""Return the canonical keyed draft shared by stateless RPC tests."""
|
||||
return {
|
||||
"name": "rpc_constant",
|
||||
"input_schema": {"type": "object", "properties": {}},
|
||||
"state_schema": {
|
||||
"type": "object",
|
||||
"properties": {"result": {"type": "string", "reducer": "wf.std.replace"}},
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {"result": {"type": "string"}},
|
||||
"required": ["result"],
|
||||
},
|
||||
"start": "constant",
|
||||
"steps": {
|
||||
"constant": {
|
||||
"use": "wf.std.constant",
|
||||
"input": [
|
||||
{
|
||||
"value": "hello over rpc",
|
||||
"target": {"root": "local", "parts": ["value"]},
|
||||
}
|
||||
],
|
||||
"output": [
|
||||
{
|
||||
"source": {"root": "local", "parts": ["value"]},
|
||||
"target": {"root": "state", "parts": ["result"]},
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"routes": {"constant": {"ok": "__end__"}},
|
||||
"output": [
|
||||
{
|
||||
"path": {"root": "state", "parts": ["result"]},
|
||||
"target": {"root": "local", "parts": ["result"]},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_update_capability_step_params_preserve_nested_field_presence() -> None:
|
||||
params = UpdateCapabilityStepParams.model_validate(
|
||||
{
|
||||
@@ -193,6 +235,67 @@ async def test_rpc_source_discovery_preserves_inventory_contracts(tmp_path) -> N
|
||||
assert diagnosed["result"]["status"] == "unknown"
|
||||
|
||||
|
||||
async def test_rpc_stateless_draft_methods_preserve_result_variants(tmp_path) -> None:
|
||||
app = create_rpc_app(build_local_static_workflow_server(tmp_path / "store"))
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
validated = await _rpc(
|
||||
client,
|
||||
"workflow.drafts.validate",
|
||||
{"draft": {}},
|
||||
)
|
||||
patched_valid = await _rpc(
|
||||
client,
|
||||
"workflow.drafts.patch",
|
||||
{
|
||||
"draft": _rpc_constant_draft(),
|
||||
"patch": [
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/name",
|
||||
"value": "renamed_rpc_constant",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
patched_invalid = await _rpc(
|
||||
client,
|
||||
"workflow.drafts.patch",
|
||||
{
|
||||
"draft": _rpc_constant_draft(),
|
||||
"patch": [
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/routes/constant/ok",
|
||||
"value": "missing_step",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
patched_malformed = await _rpc(
|
||||
client,
|
||||
"workflow.drafts.patch",
|
||||
{
|
||||
"draft": {},
|
||||
"patch": [{"op": "remove", "path": "/missing"}],
|
||||
},
|
||||
)
|
||||
|
||||
assert validated["result"]["status"] == "invalid"
|
||||
assert validated["result"]["diagnostics"]
|
||||
assert patched_valid["result"]["status"] == "valid"
|
||||
assert patched_valid["result"]["draft"]["name"] == "renamed_rpc_constant"
|
||||
assert patched_valid["result"]["compiled_plan"]["name"] == "renamed_rpc_constant"
|
||||
assert patched_invalid["result"]["status"] == "invalid"
|
||||
assert patched_invalid["result"]["diagnostics"]
|
||||
assert patched_invalid["result"]["draft"]["routes"]["constant"]["ok"] == (
|
||||
"missing_step"
|
||||
)
|
||||
assert patched_malformed["result"]["status"] == "invalid"
|
||||
assert patched_malformed["result"]["diagnostics"][0]["code"] == "patch_invalid"
|
||||
assert "draft" not in patched_malformed["result"]
|
||||
|
||||
|
||||
async def test_rpc_capability_methods_preserve_saved_wrapper_fields(tmp_path) -> None:
|
||||
server = build_local_static_workflow_server(tmp_path / "store")
|
||||
await server.api.create_artifact_from_plan(
|
||||
@@ -285,46 +388,7 @@ async def test_rpc_draft_artifact_deployment_lifecycle(tmp_path) -> None:
|
||||
},
|
||||
)
|
||||
|
||||
draft = {
|
||||
"name": "rpc_constant",
|
||||
"input_schema": {"type": "object", "properties": {}},
|
||||
"state_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "string", "reducer": "wf.std.replace"}
|
||||
},
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {"result": {"type": "string"}},
|
||||
"required": ["result"],
|
||||
},
|
||||
"start": "constant",
|
||||
"steps": {
|
||||
"constant": {
|
||||
"use": "wf.std.constant",
|
||||
"input": [
|
||||
{
|
||||
"value": "hello over rpc",
|
||||
"target": {"root": "local", "parts": ["value"]},
|
||||
}
|
||||
],
|
||||
"output": [
|
||||
{
|
||||
"source": {"root": "local", "parts": ["value"]},
|
||||
"target": {"root": "state", "parts": ["result"]},
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"routes": {"constant": {"ok": "__end__"}},
|
||||
"output": [
|
||||
{
|
||||
"path": {"root": "state", "parts": ["result"]},
|
||||
"target": {"root": "local", "parts": ["result"]},
|
||||
}
|
||||
],
|
||||
}
|
||||
draft = _rpc_constant_draft()
|
||||
|
||||
validate_draft = await _rpc(
|
||||
client,
|
||||
|
||||
@@ -399,6 +399,33 @@ async def test_rpc_client_sends_exact_draft_lifecycle_payloads() -> None:
|
||||
]
|
||||
|
||||
|
||||
async def test_rpc_client_sends_exact_stateless_draft_payloads() -> None:
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
class Client(RpcDraftClientMixin):
|
||||
async def _call(self, method: str, params: dict[str, object]):
|
||||
calls.append({"method": method, "params": params})
|
||||
return {"status": "invalid", "diagnostics": []}
|
||||
|
||||
client = Client()
|
||||
draft = {"name": "report"}
|
||||
patch = [{"op": "replace", "path": "/name", "value": "renamed"}]
|
||||
|
||||
await client.validate_draft(draft=draft)
|
||||
await client.patch_draft(draft=draft, patch=patch)
|
||||
|
||||
assert calls == [
|
||||
{
|
||||
"method": "workflow.drafts.validate",
|
||||
"params": {"draft": draft},
|
||||
},
|
||||
{
|
||||
"method": "workflow.drafts.patch",
|
||||
"params": {"draft": draft, "patch": patch},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
async def test_rpc_client_sends_exact_replace_document_payload() -> None:
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
|
||||
@@ -350,6 +350,53 @@ def test_openrpc_exposes_typed_compile_draft_workspace_result(
|
||||
assert schemas["InvalidDraftResult"]["properties"]["status"]["const"] == "invalid"
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_stateless_draft_validation_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
method = _method_by_name(openrpc_document, "workflow.drafts.validate")
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
|
||||
assert method["result"]["schema"] == {
|
||||
"$ref": "#/components/schemas/ValidateDraftResult"
|
||||
}
|
||||
assert schemas["ValidateDraftResult"] == {
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/ValidDraftResult"},
|
||||
{"$ref": "#/components/schemas/InvalidDraftResult"},
|
||||
]
|
||||
}
|
||||
assert schemas["ValidDraftResult"]["required"] == [
|
||||
"status",
|
||||
"diagnostics",
|
||||
"compiled_plan",
|
||||
]
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_stateless_draft_patch_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
method = _method_by_name(openrpc_document, "workflow.drafts.patch")
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
|
||||
assert method["result"]["schema"] == {
|
||||
"$ref": "#/components/schemas/PatchDraftResult"
|
||||
}
|
||||
assert schemas["PatchDraftResult"] == {
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/PatchedDraftValidResult"},
|
||||
{"$ref": "#/components/schemas/PatchedDraftInvalidResult"},
|
||||
]
|
||||
}
|
||||
assert "draft" in schemas["PatchedDraftValidResult"]["required"]
|
||||
assert "draft" not in schemas["PatchedDraftInvalidResult"]["required"]
|
||||
assert schemas["PatchedDraftValidResult"]["properties"]["draft"] == {
|
||||
"$ref": "#/components/schemas/JsonObject"
|
||||
}
|
||||
assert schemas["PatchedDraftInvalidResult"]["properties"]["draft"] == {
|
||||
"$ref": "#/components/schemas/JsonObject"
|
||||
}
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_capability_bootstrap_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user