GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Make every scan/get/rollback --json error a {code, message} object (#… · SocketDev/socket-patch@8d316df

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • crates/socket-patch-cli/src/commands
    • get.rs
    • remove.rs
    • repair.rs
    • rollback.rs
    • scan
      • hosted.rs
      • mod.rs
      • policy.rs
      • vendor_flow.rs
    • vendor.rs
    • vex.rs
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎crates/socket-patch-cli/src/commands/get.rs‎

Copy file name to clipboardExpand all lines: crates/socket-patch-cli/src/commands/get.rs
+87-39Lines changed: 87 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use crate::ecosystem_dispatch::{
4343
crawl_all_ecosystems, find_all_packages_for_rollback, find_packages_for_rollback,
4444
partition_purls,
4545
};
46+
use crate::json_envelope::{usage_error, Command as JsonCommand};
4647
use crate::ui::{print_json, select_one, SelectError};
4748

4849
/// Best-effort ecosystem extractor for a `pkg:<eco>/...` PURL. Used as
@@ -228,26 +229,28 @@ async fn report_fetch_failure(
228229
) -> i32 {
229230
let msg = error.to_string();
230231
track_patch_fetch_failed(identifier, &msg, fallback_to_proxy, api_token, org_slug).await;
231-
report_error(json, msg);
232+
report_error(json, "patch_fetch_failed", msg);
232233
1
233234
}
234235

235-
/// Report an error to the caller: a `{status, error}` envelope on
236-
/// stdout when `json` is true, otherwise a plain `Error: ...` on stderr.
237-
fn report_error(json: bool, message: impl std::fmt::Display) {
236+
/// Report an error to the caller: a `{status: "error", error: {code,
237+
/// message}}` object on stdout when `json` is true, otherwise a plain
238+
/// `Error: ...` on stderr. Every top-level `get` failure goes through here
239+
/// (or [`report_lock_failure`]) so the error shape cannot drift.
240+
fn report_error(json: bool, code: &str, message: impl std::fmt::Display) {
238241
let message = message.to_string();
239242
if json {
240-
print_json(&serde_json::json!({"status": "error", "error": message}));
243+
crate::json_envelope::print_legacy_error(code, &message);
241244
} else {
242245
eprintln!("Error: {message}");
243246
}
244247
}
245248

246249
/// Report a failed apply-lock acquire in get's legacy error shape — the
247-
/// `{status: "error", error: "<message>"}` envelope every other hard error
248-
/// here uses, plus the stable `errorCode` (`lock_held` / `lock_io`) the
249-
/// other lock sites emit — and return the envelope for the caller's
250-
/// early-return guard. The message/code mapping is
250+
/// `{status: "error", error: {code, message}}` object every other hard
251+
/// error here uses, with the stable code (`lock_held` / `lock_io`) the
252+
/// other lock sites emit — and return it for the caller's early-return
253+
/// guard. The message/code mapping is
251254
/// [`crate::commands::lock_cli::lock_failure`]'s, so the waited clause and
252255
/// the I/O rendering cannot drift from `apply`'s.
253256
fn report_lock_failure(
@@ -257,11 +260,7 @@ fn report_lock_failure(
257260
timeout: Duration,
258261
) -> serde_json::Value {
259262
let (code, message) = lock_failure(err, timeout);
260-
let envelope = serde_json::json!({
261-
"status": "error",
262-
"errorCode": code,
263-
"error": message,
264-
});
263+
let envelope = crate::json_envelope::legacy_error(code, &message);
265264
if json {
266265
print_json(&envelope);
267266
} else {
@@ -1075,7 +1074,10 @@ pub(crate) fn select_patches(
10751074
.collect();
10761075
print_json(&serde_json::json!({
10771076
"status": "selection_required",
1078-
"error": format!("Multiple patches available for {purl}. Re-run with the chosen UUID as the identifier (`socket-patch get <uuid>`) to select one."),
1077+
"error": {
1078+
"code": "selection_required",
1079+
"message": format!("Multiple patches available for {purl}. Re-run with the chosen UUID as the identifier (`socket-patch get <uuid>`) to select one."),
1080+
},
10791081
"purl": purl,
10801082
"options": options_json,
10811083
}));
@@ -2399,7 +2401,8 @@ fn apply_key_covers(key: &str, record: &str) -> bool {
23992401
/// as on every `failed` record); any other failed manifest patch (one this
24002402
/// run did not select) gets its own `failed` record (`uuid_of` looks up
24012403
/// its uuid); a run-level reason rides the envelope's top-level
2402-
/// `errorCode` / `error`. `failed` grows by every record marked or
2404+
/// `error: {code, message}` (status stays `partial_failure`: the downloads
2405+
/// it reports still landed). `failed` grows by every record marked or
24032406
/// appended here. Returns `applied`: how many of the run's recorded
24042407
/// patches apply really patched (or found already patched).
24052408
fn fold_apply_failures(
@@ -2465,8 +2468,10 @@ fn fold_apply_failures(
24652468
let failed = envelope["failed"].as_u64().unwrap_or(0) as usize + added;
24662469
envelope["failed"] = serde_json::json!(failed);
24672470
if let Some((code, error)) = &report.run_error {
2468-
envelope["errorCode"] = serde_json::json!(code);
2469-
envelope["error"] = serde_json::json!(error);
2471+
crate::json_envelope::set_error_keep_status(
2472+
envelope,
2473+
crate::json_envelope::EnvelopeError::new(code, error),
2474+
);
24702475
}
24712476
applied
24722477
}
@@ -2512,8 +2517,11 @@ pub async fn download_and_apply_patches_with(
25122517
// and destroy every tracked patch record.
25132518
Err(e) => {
25142519
let err = format!("Failed to read manifest: {e}");
2515-
report_error(params.json, &err);
2516-
return (1, serde_json::json!({"status": "error", "error": err}));
2520+
report_error(params.json, "manifest_unreadable", &err);
2521+
return (
2522+
1,
2523+
crate::json_envelope::legacy_error("manifest_unreadable", &err),
2524+
);
25172525
}
25182526
};
25192527

@@ -2565,8 +2573,11 @@ pub async fn download_and_apply_patches_with(
25652573
// unwind exactly those (a pre-existing record's blobs stay).
25662574
unwind_new_blobs(&blobs_dir, &new_blobs).await;
25672575
let msg = format!("Failed to write manifest: {e}");
2568-
report_error(params.json, &msg);
2569-
return (1, serde_json::json!({ "status": "error", "error": msg }));
2576+
report_error(params.json, "manifest_write_failed", &msg);
2577+
return (
2578+
1,
2579+
crate::json_envelope::legacy_error("manifest_write_failed", &msg),
2580+
);
25702581
}
25712582
}
25722583
// Every selected patch that is now recorded is owed the nested apply:
@@ -2664,11 +2675,13 @@ pub async fn run(args: GetArgs) -> i32 {
26642675
.filter(|&&f| f)
26652676
.count();
26662677
if type_flags > 1 {
2667-
report_error(
2678+
return usage_error(
2679+
JsonCommand::Get,
26682680
args.common.json,
2681+
args.common.dry_run,
2682+
"invalid_args",
26692683
"Only one of --id, --cve, --ghsa, or --package can be specified",
26702684
);
2671-
return 2;
26722685
}
26732686
// v5: hosted by default, like scan. `--save-only` (records a manifest
26742687
// entry) and global installs (no project lockfile) mean agent mode.
@@ -2683,20 +2696,27 @@ pub async fn run(args: GetArgs) -> i32 {
26832696
// Global installs have no project lockfile: an explicit hosted or
26842697
// vendored mode would rewire the cwd project, not the global copy.
26852698
if let Some(conflict) = super::global_mode_conflict(&args.common, mode) {
2686-
report_error(args.common.json, conflict);
2687-
return 2;
2699+
return usage_error(
2700+
JsonCommand::Get,
2701+
args.common.json,
2702+
args.common.dry_run,
2703+
"global_scope_unsupported",
2704+
&conflict,
2705+
);
26882706
}
26892707
if args.save_only && mode != super::scan::ScanMode::Agent {
2690-
report_error(
2708+
return usage_error(
2709+
JsonCommand::Get,
26912710
args.common.json,
2692-
format!(
2711+
args.common.dry_run,
2712+
"invalid_args",
2713+
&format!(
26932714
"--save-only cannot be used with --mode {}: hosted mode never writes the \
26942715
manifest, and vendored mode's vendor step IS the persistence (plain \
26952716
`get --save-only` already records without applying)",
26962717
mode.cli_name()
26972718
),
26982719
);
2699-
return 2;
27002720
}
27012721
// Strict airgap (CLI_CONTRACT.md `--offline`: never contact the
27022722
// network; operations that need remote data fail loudly). Every `get`
@@ -2707,6 +2727,7 @@ pub async fn run(args: GetArgs) -> i32 {
27072727
if args.common.offline {
27082728
report_error(
27092729
args.common.json,
2730+
"offline_unsupported",
27102731
"Fetching patches needs network access, so `get` cannot run with \
27112732
--offline/SOCKET_OFFLINE (strict airgap)",
27122733
);
@@ -2729,8 +2750,13 @@ pub async fn run(args: GetArgs) -> i32 {
27292750
// a typo reads as a plain message instead of a raw API 400 body.
27302751
if args.id || args.cve || args.ghsa {
27312752
if let Some(err) = forced_identifier_error(&args.identifier, id_type) {
2732-
report_error(args.common.json, err);
2733-
return 2;
2753+
return usage_error(
2754+
JsonCommand::Get,
2755+
args.common.json,
2756+
args.common.dry_run,
2757+
"identifier_invalid",
2758+
&err,
2759+
);
27342760
}
27352761
}
27362762

@@ -3356,7 +3382,11 @@ async fn agent_dry_run(
33563382
let manifest = match read_manifest(&args.common.resolved_manifest_path()).await {
33573383
Ok(m) => m.unwrap_or_else(PatchManifest::new),
33583384
Err(e) => {
3359-
report_error(args.common.json, format!("Failed to read manifest: {e}"));
3385+
report_error(
3386+
args.common.json,
3387+
"manifest_unreadable",
3388+
format!("Failed to read manifest: {e}"),
3389+
);
33603390
return 1;
33613391
}
33623392
};
@@ -3446,7 +3476,11 @@ async fn save_patch_record(
34463476
// treated as empty would be rewritten below with only this one
34473477
// patch, destroying every tracked record.
34483478
Err(e) => {
3449-
report_error(args.common.json, format!("Failed to read manifest: {e}"));
3479+
report_error(
3480+
args.common.json,
3481+
"manifest_unreadable",
3482+
format!("Failed to read manifest: {e}"),
3483+
);
34503484
return Err(1);
34513485
}
34523486
};
@@ -3463,6 +3497,7 @@ async fn save_patch_record(
34633497
if files.is_empty() {
34643498
report_error(
34653499
args.common.json,
3500+
"patch_no_applicable_files",
34663501
format!(
34673502
"Patch {} has no applicable files; nothing to apply",
34683503
patch.purl
@@ -3488,7 +3523,10 @@ async fn save_patch_record(
34883523
"found": 1,
34893524
"downloaded": 0,
34903525
"applied": 0,
3491-
"error": "Blob decode or write failed",
3526+
"error": {
3527+
"code": "blob_write_failed",
3528+
"message": "Blob decode or write failed",
3529+
},
34923530
"patches": [{
34933531
"purl": patch.purl,
34943532
"uuid": patch.uuid,
@@ -3511,7 +3549,11 @@ async fn save_patch_record(
35113549
if let Err(e) = write_manifest(manifest_path, &manifest).await {
35123550
// No record points at the blobs just written: unwind exactly those.
35133551
unwind_new_blobs(&blobs_dir, &new_blobs).await;
3514-
report_error(args.common.json, format!("Failed to write manifest: {e}"));
3552+
report_error(
3553+
args.common.json,
3554+
"manifest_write_failed",
3555+
format!("Failed to write manifest: {e}"),
3556+
);
35153557
return Err(1);
35163558
}
35173559
Ok(action)
@@ -3958,8 +4000,10 @@ async fn run_get_vendored(
39584000
result["vendor"] =
39594001
serde_json::to_value(&*venv).unwrap_or_else(|_| serde_json::json!({}));
39604002
}
3961-
result["status"] = serde_json::json!("error");
3962-
result["error"] = serde_json::json!({ "code": code, "message": message });
4003+
crate::json_envelope::set_error(
4004+
&mut result,
4005+
crate::json_envelope::EnvelopeError::new(code, message),
4006+
);
39634007
print_json(&result);
39644008
} else {
39654009
eprintln!(
@@ -4953,8 +4997,12 @@ mod tests {
49534997
applied: Vec::new(),
49544998
};
49554999
assert_eq!(fold_apply_failures(&mut env, &report, |_| None), 0);
4956-
assert_eq!(env["errorCode"], "yarn_pnp_unsupported", "{env}");
4957-
assert_eq!(env["error"], "pnp", "{env}");
5000+
assert!(env.get("errorCode").is_none(), "{env}");
5001+
assert_eq!(
5002+
env["error"],
5003+
serde_json::json!({"code": "yarn_pnp_unsupported", "message": "pnp"}),
5004+
"{env}"
5005+
);
49585006
assert_eq!(env["failed"], 0, "{env}");
49595007
}
49605008

Collapse file

‎crates/socket-patch-cli/src/commands/remove.rs‎

Copy file name to clipboardExpand all lines: crates/socket-patch-cli/src/commands/remove.rs
+7-4Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,14 @@ pub async fn run(args: RemoveArgs) -> i32 {
319319
// `--preserve-state` restores the tree and keeps the state — together
320320
// they select the do-nothing quadrant.
321321
if args.preserve_state && args.skip_rollback {
322-
eprintln!(
323-
"Error: --preserve-state cannot be used with --skip-rollback: the \
324-
combination would be a no-op (nothing would change)"
322+
return crate::json_envelope::usage_error(
323+
Command::Remove,
324+
args.common.json,
325+
args.common.dry_run,
326+
"invalid_args",
327+
"--preserve-state cannot be used with --skip-rollback: the \
328+
combination would be a no-op (nothing would change)",
325329
);
326-
return 2;
327330
}
328331

329332
let (telemetry_client, _) =
Collapse file

‎crates/socket-patch-cli/src/commands/repair.rs‎

Copy file name to clipboardExpand all lines: crates/socket-patch-cli/src/commands/repair.rs
+7-8Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,13 @@ pub async fn run(args: RepairArgs) -> i32 {
4747
// --offline implies strict airgap: no network calls. `--download-only`
4848
// is the inverse (network-only). The two are now mutually exclusive.
4949
if args.common.offline && args.download_only {
50-
let msg = "--offline and --download-only are mutually exclusive";
51-
if args.common.json {
52-
let env = error_envelope(Command::Repair, args.common.dry_run, "invalid_args", msg);
53-
println!("{}", env.to_pretty_json());
54-
} else {
55-
eprintln!("Error: {msg}");
56-
}
57-
return 2;
50+
return crate::json_envelope::usage_error(
51+
Command::Repair,
52+
args.common.json,
53+
args.common.dry_run,
54+
"invalid_args",
55+
"--offline and --download-only are mutually exclusive",
56+
);
5857
}
5958

6059
let manifest_path = args.common.resolved_manifest_path();

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.