OCaml client library for Claude Code
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

claude: complete Err -> Error module rename across call sites

Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.

+16 -191
+1 -1
lib/claude.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 - module Err = Err 6 + module Error = Error 7 7 module Client = Client 8 8 module Options = Options 9 9 module Response = Response
+10 -10
lib/claude.mli
··· 149 149 150 150 {1 Error Handling} 151 151 152 - The library uses a structured exception type {!Err.E} for all errors: 152 + The library uses a structured exception type {!Error.E} for all errors: 153 153 154 154 {[ 155 155 try Claude.Client.query client "Hello" 156 - with Claude.Err.E err -> 157 - Printf.eprintf "Error: %s\n" (Claude.Err.to_string err) 156 + with Claude.Error.E err -> 157 + Printf.eprintf "Error: %s\n" (Claude.Error.to_string err) 158 158 ]} 159 159 160 160 Error types include: 161 - - {!Err.Cli_not_found}: Claude CLI not found 162 - - {!Err.Process_error}: Process execution failure 163 - - {!Err.Protocol_error}: JSON/protocol parsing error 164 - - {!Err.Timeout}: Operation timed out 165 - - {!Err.Permission_denied}: Tool permission denied 166 - - {!Err.Hook_error}: Hook callback error 161 + - {!Error.Cli_not_found}: Claude CLI not found 162 + - {!Error.Process_error}: Process execution failure 163 + - {!Error.Protocol_error}: JSON/protocol parsing error 164 + - {!Error.Timeout}: Operation timed out 165 + - {!Error.Permission_denied}: Tool permission denied 166 + - {!Error.Hook_error}: Hook callback error 167 167 168 168 {1 Logging} 169 169 ··· 177 177 178 178 (** {1 Core Modules} *) 179 179 180 - module Err = Err 180 + module Error = Error 181 181 (** Error handling with structured exception type. *) 182 182 183 183 module Client = Client
+4 -4
lib/client.ml
··· 8 8 module Log = (val Logs.src_log src : Logs.LOG) 9 9 10 10 let encode_or_raise ~msg codec v = 11 - Json.encode codec v |> Result.map_error Json.Error.to_string |> Err.ok ~msg 11 + Json.encode codec v |> Result.map_error Json.Error.to_string |> Error.ok ~msg 12 12 13 13 (** Control response builders using Control codecs *) 14 14 module Control_response = struct ··· 44 44 (fun m -> 45 45 Json.encode json m 46 46 |> Result.map_error Json.Error.to_string 47 - |> Err.ok ~msg:"Hook_matcher_wire.encode: ") 47 + |> Error.ok ~msg:"Hook_matcher_wire.encode: ") 48 48 matchers 49 49 |> Json.list 50 50 end ··· 456 456 t.permission_log |> Option.map ( ! ) |> Option.value ~default:[] 457 457 458 458 let decode_or_raise ~msg codec v = 459 - Json.decode codec v |> Result.map_error Json.Error.to_string |> Err.ok' ~msg 459 + Json.decode codec v |> Result.map_error Json.Error.to_string |> Error.ok' ~msg 460 460 461 461 let decode_control_response response_json = 462 462 let response_field_codec = ··· 541 541 let response_data = 542 542 send_control_request t ~request_id request 543 543 |> Option.to_result ~none:"No response data from get_server_info request" 544 - |> Err.ok ~msg:"" 544 + |> Error.ok ~msg:"" 545 545 in 546 546 let server_info = 547 547 decode_or_raise ~msg:"Failed to decode server info: "
+1 -1
test/test.ml
··· 6 6 Test_client.suite; 7 7 Test_content_block.suite; 8 8 Test_control.suite; 9 - Test_err.suite; 9 + Test_error.suite; 10 10 Test_handler.suite; 11 11 Test_hooks.suite; 12 12 Test_incoming.suite;
-173
test/test_err.ml
··· 1 - (*--------------------------------------------------------------------------- 2 - Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 - SPDX-License-Identifier: ISC 4 - ---------------------------------------------------------------------------*) 5 - 6 - (** Tests for Err module: error formatting, raisers, and result helpers. *) 7 - 8 - let test_cli_not_found_format () = 9 - let err = Claude.Err.Cli_not_found "claude not in PATH" in 10 - Alcotest.(check string) 11 - "format" "CLI not found: claude not in PATH" (Claude.Err.to_string err) 12 - 13 - let test_process_error_format () = 14 - let err = Claude.Err.Process_error "exit code 1" in 15 - Alcotest.(check string) 16 - "format" "Process error: exit code 1" (Claude.Err.to_string err) 17 - 18 - let test_connection_error_format () = 19 - let err = Claude.Err.Connection_error "refused" in 20 - Alcotest.(check string) 21 - "format" "Connection error: refused" (Claude.Err.to_string err) 22 - 23 - let test_protocol_error_format () = 24 - let err = Claude.Err.Protocol_error "bad json" in 25 - Alcotest.(check string) 26 - "format" "Protocol error: bad json" (Claude.Err.to_string err) 27 - 28 - let test_timeout_format () = 29 - let err = Claude.Err.Timeout "30s elapsed" in 30 - Alcotest.(check string) 31 - "format" "Timeout: 30s elapsed" (Claude.Err.to_string err) 32 - 33 - let test_permission_denied_format () = 34 - let err = 35 - Claude.Err.Permission_denied { tool_name = "Bash"; message = "not allowed" } 36 - in 37 - Alcotest.(check string) 38 - "format" "Permission denied for tool 'Bash': not allowed" 39 - (Claude.Err.to_string err) 40 - 41 - let test_hook_error_format () = 42 - let err = 43 - Claude.Err.Hook_error { callback_id = "cb-1"; message = "hook failed" } 44 - in 45 - Alcotest.(check string) 46 - "format" "Hook error (callback_id=cb-1): hook failed" 47 - (Claude.Err.to_string err) 48 - 49 - let test_control_error_format () = 50 - let err = 51 - Claude.Err.Control_error { request_id = "req-42"; message = "invalid" } 52 - in 53 - Alcotest.(check string) 54 - "format" "Control error (request_id=req-42): invalid" 55 - (Claude.Err.to_string err) 56 - 57 - let test_raise_cli_not_found () = 58 - match Claude.Err.cli_not_found "missing" with 59 - | exception Claude.Err.E (Claude.Err.Cli_not_found "missing") -> () 60 - | exception _ -> Alcotest.fail "Wrong exception type" 61 - | _ -> Alcotest.fail "Expected exception" 62 - 63 - let test_raise_process_error () = 64 - match Claude.Err.process_error "crash" with 65 - | exception Claude.Err.E (Claude.Err.Process_error "crash") -> () 66 - | exception _ -> Alcotest.fail "Wrong exception type" 67 - | _ -> Alcotest.fail "Expected exception" 68 - 69 - let test_raise_connection_error () = 70 - match Claude.Err.connection_error "reset" with 71 - | exception Claude.Err.E (Claude.Err.Connection_error "reset") -> () 72 - | exception _ -> Alcotest.fail "Wrong exception type" 73 - | _ -> Alcotest.fail "Expected exception" 74 - 75 - let test_raise_protocol_error () = 76 - match Claude.Err.protocol_error "malformed" with 77 - | exception Claude.Err.E (Claude.Err.Protocol_error "malformed") -> () 78 - | exception _ -> Alcotest.fail "Wrong exception type" 79 - | _ -> Alcotest.fail "Expected exception" 80 - 81 - let test_raise_timeout () = 82 - match Claude.Err.timeout "expired" with 83 - | exception Claude.Err.E (Claude.Err.Timeout "expired") -> () 84 - | exception _ -> Alcotest.fail "Wrong exception type" 85 - | _ -> Alcotest.fail "Expected exception" 86 - 87 - let test_raise_permission_denied () = 88 - match Claude.Err.permission_denied ~tool_name:"Edit" ~message:"blocked" with 89 - | exception 90 - Claude.Err.E 91 - (Claude.Err.Permission_denied 92 - { tool_name = "Edit"; message = "blocked" }) -> 93 - () 94 - | exception _ -> Alcotest.fail "Wrong exception type" 95 - | _ -> Alcotest.fail "Expected exception" 96 - 97 - let test_raise_hook_error () = 98 - match Claude.Err.hook_error ~callback_id:"cb-x" ~message:"fail" with 99 - | exception 100 - Claude.Err.E 101 - (Claude.Err.Hook_error { callback_id = "cb-x"; message = "fail" }) -> 102 - () 103 - | exception _ -> Alcotest.fail "Wrong exception type" 104 - | _ -> Alcotest.fail "Expected exception" 105 - 106 - let test_raise_control_error () = 107 - match Claude.Err.control_error ~request_id:"req-1" ~message:"bad" with 108 - | exception 109 - Claude.Err.E 110 - (Claude.Err.Control_error { request_id = "req-1"; message = "bad" }) -> 111 - () 112 - | exception _ -> Alcotest.fail "Wrong exception type" 113 - | _ -> Alcotest.fail "Expected exception" 114 - 115 - let test_ok_success () = 116 - let v = Claude.Err.ok ~msg:"test: " (Ok 42) in 117 - Alcotest.(check int) "ok value" 42 v 118 - 119 - let test_ok_error () = 120 - match Claude.Err.ok ~msg:"prefix: " (Error "reason") with 121 - | exception Claude.Err.E (Claude.Err.Protocol_error msg) -> 122 - Alcotest.(check bool) "contains prefix" true (String.length msg > 0) 123 - | exception _ -> Alcotest.fail "Wrong exception type" 124 - | _ -> Alcotest.fail "Expected exception" 125 - 126 - let test_ok'_success () = 127 - let v = Claude.Err.ok' ~msg:"test: " (Ok "hello") in 128 - Alcotest.(check string) "ok' value" "hello" v 129 - 130 - let test_ok'_error () = 131 - match Claude.Err.ok' ~msg:"prefix: " (Error "reason") with 132 - | exception Claude.Err.E (Claude.Err.Protocol_error _) -> () 133 - | exception _ -> Alcotest.fail "Wrong exception type" 134 - | _ -> Alcotest.fail "Expected exception" 135 - 136 - let test_pp_output () = 137 - let err = Claude.Err.Timeout "10s" in 138 - let buf = Buffer.create 32 in 139 - let ppf = Format.formatter_of_buffer buf in 140 - Claude.Err.pp ppf err; 141 - Format.pp_print_flush ppf (); 142 - Alcotest.(check string) "pp output" "Timeout: 10s" (Buffer.contents buf) 143 - 144 - let suite = 145 - ( "err", 146 - [ 147 - Alcotest.test_case "Cli_not_found format" `Quick test_cli_not_found_format; 148 - Alcotest.test_case "Process_error format" `Quick test_process_error_format; 149 - Alcotest.test_case "Connection_error format" `Quick 150 - test_connection_error_format; 151 - Alcotest.test_case "Protocol_error format" `Quick 152 - test_protocol_error_format; 153 - Alcotest.test_case "Timeout format" `Quick test_timeout_format; 154 - Alcotest.test_case "Permission_denied format" `Quick 155 - test_permission_denied_format; 156 - Alcotest.test_case "Hook_error format" `Quick test_hook_error_format; 157 - Alcotest.test_case "Control_error format" `Quick test_control_error_format; 158 - Alcotest.test_case "raise cli_not_found" `Quick test_raise_cli_not_found; 159 - Alcotest.test_case "raise process_error" `Quick test_raise_process_error; 160 - Alcotest.test_case "raise connection_error" `Quick 161 - test_raise_connection_error; 162 - Alcotest.test_case "raise protocol_error" `Quick test_raise_protocol_error; 163 - Alcotest.test_case "raise timeout" `Quick test_raise_timeout; 164 - Alcotest.test_case "raise permission_denied" `Quick 165 - test_raise_permission_denied; 166 - Alcotest.test_case "raise hook_error" `Quick test_raise_hook_error; 167 - Alcotest.test_case "raise control_error" `Quick test_raise_control_error; 168 - Alcotest.test_case "ok success" `Quick test_ok_success; 169 - Alcotest.test_case "ok error" `Quick test_ok_error; 170 - Alcotest.test_case "ok' success" `Quick test_ok'_success; 171 - Alcotest.test_case "ok' error" `Quick test_ok'_error; 172 - Alcotest.test_case "pp output" `Quick test_pp_output; 173 - ] )
-2
test/test_err.mli
··· 1 - val suite : string * unit Alcotest.test_case list 2 - (** Test suite. *)