sourcecode 1.33.6__py3-none-any.whl → 1.33.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +131 -67
- sourcecode/error_schema.py +56 -0
- sourcecode/mcp/registry.py +862 -0
- sourcecode/mcp/runner.py +40 -7
- sourcecode/mcp/server.py +126 -13
- sourcecode/prepare_context.py +4 -0
- sourcecode/repository_ir.py +8 -2
- {sourcecode-1.33.6.dist-info → sourcecode-1.33.8.dist-info}/METADATA +2 -2
- {sourcecode-1.33.6.dist-info → sourcecode-1.33.8.dist-info}/RECORD +13 -12
- sourcecode/cache.tmp_new +0 -772
- {sourcecode-1.33.6.dist-info → sourcecode-1.33.8.dist-info}/WHEEL +0 -0
- {sourcecode-1.33.6.dist-info → sourcecode-1.33.8.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.33.6.dist-info → sourcecode-1.33.8.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import Any, Optional, cast
|
|
|
10
10
|
import typer
|
|
11
11
|
|
|
12
12
|
from sourcecode import __version__
|
|
13
|
+
from sourcecode.error_schema import INVALID_INPUT_CODE, build_error_envelope
|
|
13
14
|
from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
|
|
14
15
|
from sourcecode.progress import Progress
|
|
15
16
|
from sourcecode.repository_ir import extract_java_endpoints as _extract_java_endpoints
|
|
@@ -305,14 +306,13 @@ def _preprocess_argv() -> None:
|
|
|
305
306
|
def _emit_error_json(error: str, message: str, **context: object) -> None:
|
|
306
307
|
"""Write a structured JSON error envelope to stderr.
|
|
307
308
|
|
|
308
|
-
Format: {"error": "<code>", "message": "<human text>", ...<context>}
|
|
309
|
+
Format: {"error": {"code": "<code>", "message": "<human text>", ...}, ...<context>}
|
|
309
310
|
All CLI validation and runtime errors must go through this helper so that
|
|
310
311
|
agents and tools can parse stderr reliably regardless of error type.
|
|
311
312
|
"""
|
|
312
313
|
import json as _json
|
|
313
314
|
import sys as _sys
|
|
314
|
-
payload
|
|
315
|
-
payload.update(context)
|
|
315
|
+
payload = build_error_envelope(error, message, **context)
|
|
316
316
|
_sys.stderr.write(_json.dumps(payload, ensure_ascii=False) + "\n")
|
|
317
317
|
_sys.stderr.flush()
|
|
318
318
|
|
|
@@ -326,18 +326,15 @@ try:
|
|
|
326
326
|
def _json_click_usage_error_show(self: Any, file: Any = None) -> None: # type: ignore[override]
|
|
327
327
|
import json as _je
|
|
328
328
|
import sys as _jse
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
"
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
_opt = getattr(self, "option_name", None) or getattr(self, "param_hint", None)
|
|
339
|
-
if _opt:
|
|
340
|
-
payload["flag"] = str(_opt).strip("'\"")
|
|
329
|
+
_flag = str((getattr(self, "option_name", None) or getattr(self, "param_hint", None)) or "").strip("'\"")
|
|
330
|
+
_context: dict[str, object] = {}
|
|
331
|
+
if _flag:
|
|
332
|
+
_context["flag"] = _flag
|
|
333
|
+
payload = build_error_envelope(
|
|
334
|
+
INVALID_INPUT_CODE,
|
|
335
|
+
self.format_message(),
|
|
336
|
+
**_context,
|
|
337
|
+
)
|
|
341
338
|
_jse.stderr.write(_je.dumps(payload, ensure_ascii=False) + "\n")
|
|
342
339
|
_jse.stderr.flush()
|
|
343
340
|
|
|
@@ -798,35 +795,59 @@ def main(
|
|
|
798
795
|
)
|
|
799
796
|
mode = fallback
|
|
800
797
|
elif mode not in _MODE_CHOICES:
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
798
|
+
_emit_error_json(
|
|
799
|
+
INVALID_INPUT_CODE,
|
|
800
|
+
f"Invalid value '{mode}' for --mode. Valid options: {', '.join(_MODE_CHOICES)}",
|
|
801
|
+
flag="--mode",
|
|
802
|
+
value=mode,
|
|
803
|
+
valid_values=list(_MODE_CHOICES),
|
|
804
|
+
hint="Choose one of the supported --mode values.",
|
|
805
|
+
expected=f"One of: {', '.join(_MODE_CHOICES)}",
|
|
804
806
|
)
|
|
805
807
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
806
808
|
_RANK_CHOICES = ("relevance", "centrality", "git-churn")
|
|
807
809
|
if rank_by not in _RANK_CHOICES:
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
810
|
+
_emit_error_json(
|
|
811
|
+
INVALID_INPUT_CODE,
|
|
812
|
+
f"Invalid value '{rank_by}' for --rank-by. Valid options: {', '.join(_RANK_CHOICES)}",
|
|
813
|
+
flag="--rank-by",
|
|
814
|
+
value=rank_by,
|
|
815
|
+
valid_values=list(_RANK_CHOICES),
|
|
816
|
+
hint="Choose one of the supported --rank-by values.",
|
|
817
|
+
expected=f"One of: {', '.join(_RANK_CHOICES)}",
|
|
811
818
|
)
|
|
812
819
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
813
820
|
|
|
814
821
|
if symbol is not None and not symbol.strip():
|
|
815
|
-
|
|
822
|
+
_emit_error_json(
|
|
823
|
+
INVALID_INPUT_CODE,
|
|
824
|
+
"symbol query cannot be empty",
|
|
825
|
+
flag="--symbol",
|
|
826
|
+
hint="Pass a non-empty symbol or omit --symbol.",
|
|
827
|
+
expected="A non-empty symbol query.",
|
|
828
|
+
)
|
|
816
829
|
raise typer.Exit(code=2)
|
|
817
830
|
|
|
818
831
|
if symbol and mode not in ("contract", "standard"):
|
|
819
|
-
|
|
820
|
-
|
|
832
|
+
_emit_error_json(
|
|
833
|
+
INVALID_INPUT_CODE,
|
|
834
|
+
f"--symbol requires --mode contract or standard (got '{mode}'). "
|
|
821
835
|
"Symbol search uses the contract pipeline which does not run in raw mode.",
|
|
822
|
-
|
|
836
|
+
flag="--symbol",
|
|
837
|
+
mode=mode,
|
|
838
|
+
hint="Switch to --mode contract or --mode standard.",
|
|
839
|
+
expected="A contract or standard analysis mode.",
|
|
823
840
|
)
|
|
824
841
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
825
842
|
|
|
826
843
|
if entrypoints_only and mode not in ("contract", "standard"):
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
844
|
+
_emit_error_json(
|
|
845
|
+
INVALID_INPUT_CODE,
|
|
846
|
+
f"--entrypoints-only requires --mode contract or standard (got '{mode}').",
|
|
847
|
+
flag="--entrypoints-only",
|
|
848
|
+
mode=mode,
|
|
849
|
+
hint="Switch to --mode contract or --mode standard.",
|
|
850
|
+
expected="A contract or standard analysis mode.",
|
|
830
851
|
)
|
|
831
852
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
832
853
|
|
|
@@ -855,15 +876,15 @@ def main(
|
|
|
855
876
|
# compact is designed to be a bounded summary; --full removes truncation limits,
|
|
856
877
|
# which contradicts compact's purpose. Use --agent --full for expanded output.
|
|
857
878
|
if compact and full:
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
"
|
|
861
|
-
"
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
"
|
|
865
|
-
|
|
866
|
-
|
|
879
|
+
_emit_error_json(
|
|
880
|
+
INVALID_INPUT_CODE,
|
|
881
|
+
"--compact and --full are mutually exclusive. "
|
|
882
|
+
"--compact produces a bounded summary; --full removes truncation limits "
|
|
883
|
+
"and is meant for --agent mode. Use --agent --full for expanded output.",
|
|
884
|
+
hint="Remove one of the conflicting flags.",
|
|
885
|
+
expected="Exactly one of --compact or --full.",
|
|
886
|
+
flag_conflict=["--compact", "--full"],
|
|
887
|
+
)
|
|
867
888
|
raise typer.Exit(code=1)
|
|
868
889
|
|
|
869
890
|
# P0-2 FIX: --full without --compact or --agent has no effect in contract/raw mode.
|
|
@@ -889,29 +910,35 @@ def main(
|
|
|
889
910
|
# Validate format choices
|
|
890
911
|
if format not in FORMAT_CHOICES:
|
|
891
912
|
_emit_error_json(
|
|
892
|
-
|
|
913
|
+
INVALID_INPUT_CODE,
|
|
893
914
|
f"Invalid value '{format}' for --format. Valid values: {', '.join(FORMAT_CHOICES)}.",
|
|
894
915
|
flag="--format",
|
|
895
916
|
value=format,
|
|
896
917
|
valid_values=list(FORMAT_CHOICES),
|
|
918
|
+
hint="Choose one of the supported --format values.",
|
|
919
|
+
expected=f"One of: {', '.join(FORMAT_CHOICES)}",
|
|
897
920
|
)
|
|
898
921
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
899
922
|
if graph_detail not in GRAPH_DETAIL_CHOICES:
|
|
900
923
|
_emit_error_json(
|
|
901
|
-
|
|
924
|
+
INVALID_INPUT_CODE,
|
|
902
925
|
f"Invalid value '{graph_detail}' for --graph-detail. Valid values: {', '.join(GRAPH_DETAIL_CHOICES)}.",
|
|
903
926
|
flag="--graph-detail",
|
|
904
927
|
value=graph_detail,
|
|
905
928
|
valid_values=list(GRAPH_DETAIL_CHOICES),
|
|
929
|
+
hint="Choose one of the supported --graph-detail values.",
|
|
930
|
+
expected=f"One of: {', '.join(GRAPH_DETAIL_CHOICES)}",
|
|
906
931
|
)
|
|
907
932
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
908
933
|
if docs_depth not in DOCS_DEPTH_CHOICES:
|
|
909
934
|
_emit_error_json(
|
|
910
|
-
|
|
935
|
+
INVALID_INPUT_CODE,
|
|
911
936
|
f"Invalid value '{docs_depth}' for --docs-depth. Valid values: {', '.join(DOCS_DEPTH_CHOICES)}.",
|
|
912
937
|
flag="--docs-depth",
|
|
913
938
|
value=docs_depth,
|
|
914
939
|
valid_values=list(DOCS_DEPTH_CHOICES),
|
|
940
|
+
hint="Choose one of the supported --docs-depth values.",
|
|
941
|
+
expected=f"One of: {', '.join(DOCS_DEPTH_CHOICES)}",
|
|
915
942
|
)
|
|
916
943
|
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
917
944
|
|
|
@@ -922,16 +949,20 @@ def main(
|
|
|
922
949
|
target = Path(_raw_path_input).resolve()
|
|
923
950
|
if not target.exists():
|
|
924
951
|
_emit_error_json(
|
|
925
|
-
|
|
952
|
+
INVALID_INPUT_CODE,
|
|
926
953
|
f"Directory '{_raw_path_input}' does not exist.",
|
|
927
954
|
path=_raw_path_input,
|
|
955
|
+
hint="Pass an existing repository directory.",
|
|
956
|
+
expected="An existing directory path.",
|
|
928
957
|
)
|
|
929
958
|
raise typer.Exit(code=1)
|
|
930
959
|
if not target.is_dir():
|
|
931
960
|
_emit_error_json(
|
|
932
|
-
|
|
961
|
+
INVALID_INPUT_CODE,
|
|
933
962
|
f"Path '{_raw_path_input}' is not a directory.",
|
|
934
963
|
path=_raw_path_input,
|
|
964
|
+
hint="Pass a repository directory, not a file.",
|
|
965
|
+
expected="A directory path.",
|
|
935
966
|
)
|
|
936
967
|
raise typer.Exit(code=1)
|
|
937
968
|
|
|
@@ -1027,12 +1058,15 @@ def main(
|
|
|
1027
1058
|
effective_depth = max(depth, _java_min_depth) if _is_java and depth < _java_min_depth else depth
|
|
1028
1059
|
|
|
1029
1060
|
if symbol is not None and _is_java:
|
|
1030
|
-
|
|
1031
|
-
|
|
1061
|
+
_emit_error_json(
|
|
1062
|
+
INVALID_INPUT_CODE,
|
|
1063
|
+
"--symbol is not supported for Java/JVM repositories. "
|
|
1032
1064
|
"Per-file AST extraction is unavailable for JVM — symbol search only works with Python, TypeScript, and JavaScript. "
|
|
1033
1065
|
"Alternatives: use --agent --compact to get file relevance scores, "
|
|
1034
1066
|
"or use --git-context to find recently changed files.",
|
|
1035
|
-
|
|
1067
|
+
flag="--symbol",
|
|
1068
|
+
hint="Use a non-Java repository or omit --symbol.",
|
|
1069
|
+
expected="A repository where symbol extraction is supported.",
|
|
1036
1070
|
)
|
|
1037
1071
|
raise typer.Exit(code=1)
|
|
1038
1072
|
|
|
@@ -1329,10 +1363,15 @@ def main(
|
|
|
1329
1363
|
if parsed_graph_edges is not None:
|
|
1330
1364
|
invalid_edges = sorted(parsed_graph_edges - GRAPH_EDGE_CHOICES)
|
|
1331
1365
|
if invalid_edges:
|
|
1332
|
-
|
|
1333
|
-
|
|
1366
|
+
_emit_error_json(
|
|
1367
|
+
INVALID_INPUT_CODE,
|
|
1368
|
+
f"invalid values for --graph-edges: "
|
|
1334
1369
|
f"{', '.join(invalid_edges)}. Valid options: {', '.join(sorted(GRAPH_EDGE_CHOICES))}",
|
|
1335
|
-
|
|
1370
|
+
flag="--graph-edges",
|
|
1371
|
+
value=invalid_edges,
|
|
1372
|
+
valid_values=sorted(GRAPH_EDGE_CHOICES),
|
|
1373
|
+
hint="Choose one or more supported graph edge types.",
|
|
1374
|
+
expected=f"One or more of: {', '.join(sorted(GRAPH_EDGE_CHOICES))}",
|
|
1336
1375
|
)
|
|
1337
1376
|
raise typer.Exit(code=1)
|
|
1338
1377
|
graph_detail_typed = cast(GraphDetail, graph_detail)
|
|
@@ -2413,17 +2452,24 @@ def prepare_context_cmd(
|
|
|
2413
2452
|
raise typer.Exit()
|
|
2414
2453
|
|
|
2415
2454
|
if task is None:
|
|
2416
|
-
|
|
2417
|
-
|
|
2455
|
+
_emit_error_json(
|
|
2456
|
+
INVALID_INPUT_CODE,
|
|
2457
|
+
f"task is required. Available: {', '.join(TASKS)}\n"
|
|
2418
2458
|
"Use --task-help for descriptions.",
|
|
2419
|
-
|
|
2459
|
+
flag="task",
|
|
2460
|
+
hint="Pass one of the documented prepare-context tasks.",
|
|
2461
|
+
expected=f"One of: {', '.join(TASKS)}",
|
|
2420
2462
|
)
|
|
2421
2463
|
raise typer.Exit(code=1)
|
|
2422
2464
|
|
|
2423
2465
|
if task not in TASKS:
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2466
|
+
_emit_error_json(
|
|
2467
|
+
INVALID_INPUT_CODE,
|
|
2468
|
+
f"unknown task '{task}'. Available: {', '.join(TASKS)}",
|
|
2469
|
+
flag="task",
|
|
2470
|
+
value=task,
|
|
2471
|
+
hint="Choose one of the supported prepare-context tasks.",
|
|
2472
|
+
expected=f"One of: {', '.join(TASKS)}",
|
|
2427
2473
|
)
|
|
2428
2474
|
raise typer.Exit(code=1)
|
|
2429
2475
|
|
|
@@ -2438,10 +2484,15 @@ def prepare_context_cmd(
|
|
|
2438
2484
|
# Invalid values must error loudly — silently falling through to JSON is a lie.
|
|
2439
2485
|
_PC_FORMAT_CHOICES = ("json", "github-comment")
|
|
2440
2486
|
if format is not None and format not in _PC_FORMAT_CHOICES:
|
|
2441
|
-
|
|
2442
|
-
|
|
2487
|
+
_emit_error_json(
|
|
2488
|
+
INVALID_INPUT_CODE,
|
|
2489
|
+
f"invalid value '{format}' for --format. "
|
|
2443
2490
|
f"Valid options: {', '.join(_PC_FORMAT_CHOICES)}.",
|
|
2444
|
-
|
|
2491
|
+
flag="--format",
|
|
2492
|
+
value=format,
|
|
2493
|
+
valid_values=list(_PC_FORMAT_CHOICES),
|
|
2494
|
+
hint="Choose one of the supported prepare-context output formats.",
|
|
2495
|
+
expected=f"One of: {', '.join(_PC_FORMAT_CHOICES)}",
|
|
2445
2496
|
)
|
|
2446
2497
|
raise typer.Exit(code=2)
|
|
2447
2498
|
# github-comment only renders for review-pr; warn and normalize for other tasks.
|
|
@@ -2456,9 +2507,11 @@ def prepare_context_cmd(
|
|
|
2456
2507
|
target = path.resolve()
|
|
2457
2508
|
if not target.exists() or not target.is_dir():
|
|
2458
2509
|
_emit_error_json(
|
|
2459
|
-
|
|
2510
|
+
INVALID_INPUT_CODE,
|
|
2460
2511
|
f"'{target}' is not a valid directory.",
|
|
2461
2512
|
path=str(target),
|
|
2513
|
+
hint="Pass an existing repository directory.",
|
|
2514
|
+
expected="A directory path.",
|
|
2462
2515
|
)
|
|
2463
2516
|
raise typer.Exit(code=1)
|
|
2464
2517
|
|
|
@@ -3080,9 +3133,11 @@ def repo_ir_cmd(
|
|
|
3080
3133
|
root = path.resolve()
|
|
3081
3134
|
if not root.is_dir():
|
|
3082
3135
|
_emit_error_json(
|
|
3083
|
-
|
|
3136
|
+
INVALID_INPUT_CODE,
|
|
3084
3137
|
f"'{root}' is not a valid directory.",
|
|
3085
3138
|
path=str(root),
|
|
3139
|
+
hint="Pass an existing repository directory.",
|
|
3140
|
+
expected="A directory path.",
|
|
3086
3141
|
)
|
|
3087
3142
|
raise typer.Exit(1)
|
|
3088
3143
|
|
|
@@ -3239,9 +3294,11 @@ def impact_cmd(
|
|
|
3239
3294
|
root = path.resolve()
|
|
3240
3295
|
if not root.is_dir():
|
|
3241
3296
|
_emit_error_json(
|
|
3242
|
-
|
|
3297
|
+
INVALID_INPUT_CODE,
|
|
3243
3298
|
f"'{root}' is not a valid directory.",
|
|
3244
3299
|
path=str(root),
|
|
3300
|
+
hint="Pass an existing repository directory.",
|
|
3301
|
+
expected="A directory path.",
|
|
3245
3302
|
)
|
|
3246
3303
|
raise typer.Exit(1)
|
|
3247
3304
|
|
|
@@ -3348,9 +3405,11 @@ def endpoints_cmd(
|
|
|
3348
3405
|
target = path.resolve()
|
|
3349
3406
|
if not target.exists() or not target.is_dir():
|
|
3350
3407
|
_emit_error_json(
|
|
3351
|
-
|
|
3408
|
+
INVALID_INPUT_CODE,
|
|
3352
3409
|
f"'{target}' is not a valid directory.",
|
|
3353
3410
|
path=str(target),
|
|
3411
|
+
hint="Pass an existing repository directory.",
|
|
3412
|
+
expected="A directory path.",
|
|
3354
3413
|
)
|
|
3355
3414
|
raise typer.Exit(code=1)
|
|
3356
3415
|
|
|
@@ -3635,19 +3694,24 @@ def modernize_cmd(
|
|
|
3635
3694
|
root = path.resolve()
|
|
3636
3695
|
if not root.is_dir():
|
|
3637
3696
|
_emit_error_json(
|
|
3638
|
-
|
|
3697
|
+
INVALID_INPUT_CODE,
|
|
3639
3698
|
f"'{root}' is not a valid directory.",
|
|
3640
3699
|
path=str(root),
|
|
3700
|
+
hint="Pass an existing repository directory.",
|
|
3701
|
+
expected="A directory path.",
|
|
3641
3702
|
)
|
|
3642
3703
|
raise typer.Exit(1)
|
|
3643
3704
|
|
|
3644
3705
|
file_list = find_java_files(root)
|
|
3645
3706
|
if not file_list:
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
"
|
|
3649
|
-
|
|
3650
|
-
|
|
3707
|
+
_emit_error_json(
|
|
3708
|
+
INVALID_INPUT_CODE,
|
|
3709
|
+
"No Java files found in repository.",
|
|
3710
|
+
path=str(root),
|
|
3711
|
+
hint="Pass a repository containing Java source files.",
|
|
3712
|
+
expected="At least one Java file.",
|
|
3713
|
+
)
|
|
3714
|
+
raise typer.Exit(1)
|
|
3651
3715
|
|
|
3652
3716
|
_prog = Progress()
|
|
3653
3717
|
_prog.start(f"building IR ({len(file_list)} files) for modernization analysis")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Unified structured error schema for CLI and MCP surfaces."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
INVALID_INPUT_CODE = "INVALID_INPUT"
|
|
7
|
+
EXECUTION_FAILED_CODE = "EXECUTION_FAILED"
|
|
8
|
+
INTERNAL_ERROR_CODE = "INTERNAL_ERROR"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def default_error_hint(code: str) -> str:
|
|
12
|
+
if code == INVALID_INPUT_CODE:
|
|
13
|
+
return "Check the input value, path, or flag and try again."
|
|
14
|
+
if code == EXECUTION_FAILED_CODE:
|
|
15
|
+
return "Run the underlying CLI command directly to inspect stderr."
|
|
16
|
+
if code == INTERNAL_ERROR_CODE:
|
|
17
|
+
return "Retry the command. If it persists, capture the stack trace for debugging."
|
|
18
|
+
return "Inspect the command input and retry."
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def default_error_expected(code: str) -> str:
|
|
22
|
+
if code == INVALID_INPUT_CODE:
|
|
23
|
+
return "A supported value, path, or argument shape."
|
|
24
|
+
if code == EXECUTION_FAILED_CODE:
|
|
25
|
+
return "Successful CLI execution."
|
|
26
|
+
if code == INTERNAL_ERROR_CODE:
|
|
27
|
+
return "A successful internal operation."
|
|
28
|
+
return "A valid command result."
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_error_object(
|
|
32
|
+
code: str,
|
|
33
|
+
message: str,
|
|
34
|
+
*,
|
|
35
|
+
hint: str | None = None,
|
|
36
|
+
expected: str | None = None,
|
|
37
|
+
) -> dict[str, str]:
|
|
38
|
+
return {
|
|
39
|
+
"code": code,
|
|
40
|
+
"message": message,
|
|
41
|
+
"hint": hint or default_error_hint(code),
|
|
42
|
+
"expected": expected or default_error_expected(code),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_error_envelope(
|
|
47
|
+
code: str,
|
|
48
|
+
message: str,
|
|
49
|
+
*,
|
|
50
|
+
hint: str | None = None,
|
|
51
|
+
expected: str | None = None,
|
|
52
|
+
**context: Any,
|
|
53
|
+
) -> dict[str, Any]:
|
|
54
|
+
payload: dict[str, Any] = {"error": build_error_object(code, message, hint=hint, expected=expected)}
|
|
55
|
+
payload.update(context)
|
|
56
|
+
return payload
|