OCaml CLI and library to the Karakeep bookmarking app
0
fork

Configure Feed

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

fmt

+1046 -777
+1 -1
.ocamlformat
··· 1 - version=0.28.1 1 + version=0.29.0
+1 -1
bin/dune
··· 13 13 fmt.cli 14 14 fmt.tty 15 15 eio_main 16 - jsont.bytesrw)) 16 + nox-json))
+105 -53
bin/main.ml
··· 4 4 (* Helper to run with Eio env *) 5 5 let run_with_client config_opt f = 6 6 Eio_main.run @@ fun env -> 7 - Eio.Switch.run @@ fun sw -> 8 - with_client ~env ~sw config_opt f 7 + Eio.Switch.run @@ fun sw -> with_client ~env ~sw config_opt f 9 8 10 9 (* Bookmark commands *) 11 10 ··· 27 26 let info = Cmd.info "list" ~doc in 28 27 Cmd.v info 29 28 Term.( 30 - const run $ config_opt_term $ output_format_term $ setup_logging $ limit_term 31 - $ cursor_term $ archived_term $ favourited_term $ include_content_term) 29 + const run $ config_opt_term $ output_format_term $ setup_logging 30 + $ limit_term $ cursor_term $ archived_term $ favourited_term 31 + $ include_content_term) 32 32 33 33 let list_all_bookmarks_cmd = 34 34 let run config_opt fmt () archived favourited = ··· 58 58 let doc = "Get details of a specific bookmark." in 59 59 let info = Cmd.info "get" ~doc in 60 60 Cmd.v info 61 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 61 + Term.( 62 + const run $ config_opt_term $ output_format_term $ setup_logging 63 + $ bookmark_id_term) 62 64 63 65 let create_bookmark_cmd = 64 66 let run config_opt fmt () url title note summary tags favourited archived = ··· 80 82 let arch_term = 81 83 Arg.(value & flag & info [ "archive"; "archived" ] ~doc:"Mark as archived.") 82 84 in 83 - let fav_opt = Term.(const (fun b -> if b then Some true else None) $ fav_term) in 84 - let arch_opt = Term.(const (fun b -> if b then Some true else None) $ arch_term) in 85 + let fav_opt = 86 + Term.(const (fun b -> if b then Some true else None) $ fav_term) 87 + in 88 + let arch_opt = 89 + Term.(const (fun b -> if b then Some true else None) $ arch_term) 90 + in 85 91 Cmd.v info 86 92 Term.( 87 - const run $ config_opt_term $ output_format_term $ setup_logging $ url_term 88 - $ title_term $ note_term $ summary_term $ tags_term $ fav_opt $ arch_opt) 93 + const run $ config_opt_term $ output_format_term $ setup_logging 94 + $ url_term $ title_term $ note_term $ summary_term $ tags_term $ fav_opt 95 + $ arch_opt) 89 96 90 97 let update_bookmark_cmd = 91 98 let run config_opt fmt () bookmark_id title note summary = 92 99 handle_errors (fun () -> 93 100 run_with_client config_opt (fun client -> 94 101 let bookmark = 95 - Karakeep.update_bookmark client bookmark_id ?title ?note ?summary () 102 + Karakeep.update_bookmark client bookmark_id ?title ?note ?summary 103 + () 96 104 in 97 105 print_bookmark fmt bookmark); 98 106 0) ··· 114 122 in 115 123 let doc = "Delete a bookmark." in 116 124 let info = Cmd.info "delete" ~doc in 117 - Cmd.v info Term.(const run $ config_opt_term $ setup_logging $ bookmark_id_term) 125 + Cmd.v info 126 + Term.(const run $ config_opt_term $ setup_logging $ bookmark_id_term) 118 127 119 128 let archive_bookmark_cmd = 120 129 let run config_opt fmt () bookmark_id = ··· 129 138 let doc = "Archive a bookmark." in 130 139 let info = Cmd.info "archive" ~doc in 131 140 Cmd.v info 132 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 141 + Term.( 142 + const run $ config_opt_term $ output_format_term $ setup_logging 143 + $ bookmark_id_term) 133 144 134 145 let unarchive_bookmark_cmd = 135 146 let run config_opt fmt () bookmark_id = ··· 144 155 let doc = "Unarchive a bookmark." in 145 156 let info = Cmd.info "unarchive" ~doc in 146 157 Cmd.v info 147 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 158 + Term.( 159 + const run $ config_opt_term $ output_format_term $ setup_logging 160 + $ bookmark_id_term) 148 161 149 162 let favourite_bookmark_cmd = 150 163 let run config_opt fmt () bookmark_id = ··· 159 172 let doc = "Mark a bookmark as favourite." in 160 173 let info = Cmd.info "fav" ~doc in 161 174 Cmd.v info 162 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 175 + Term.( 176 + const run $ config_opt_term $ output_format_term $ setup_logging 177 + $ bookmark_id_term) 163 178 164 179 let unfavourite_bookmark_cmd = 165 180 let run config_opt fmt () bookmark_id = ··· 174 189 let doc = "Remove favourite mark from a bookmark." in 175 190 let info = Cmd.info "unfav" ~doc in 176 191 Cmd.v info 177 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 192 + Term.( 193 + const run $ config_opt_term $ output_format_term $ setup_logging 194 + $ bookmark_id_term) 178 195 179 196 let summarize_bookmark_cmd = 180 197 let run config_opt fmt () bookmark_id = ··· 183 200 let response = Karakeep.summarize_bookmark client bookmark_id in 184 201 match fmt with 185 202 | Text -> print_endline response.summary 186 - | Json -> 187 - let json = Jsont_bytesrw.encode_string Karakeep.summarize_response_jsont response in 188 - (match json with 189 - | Ok s -> print_endline s 190 - | Error e -> Logs.err (fun m -> m "JSON encoding error: %s" e)) 203 + | Json -> ( 204 + try 205 + let json = 206 + Json.to_string Karakeep.summarize_response_jsont response 207 + in 208 + print_endline json 209 + with Json.Error e -> 210 + Logs.err (fun m -> 211 + m "JSON encoding error: %s" (Json.Error.to_string e))) 191 212 | Quiet -> print_endline response.summary); 192 213 0) 193 214 in 194 215 let doc = "Generate an AI summary for a bookmark." in 195 216 let info = Cmd.info "summarize" ~doc in 196 217 Cmd.v info 197 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 218 + Term.( 219 + const run $ config_opt_term $ output_format_term $ setup_logging 220 + $ bookmark_id_term) 198 221 199 222 let search_bookmarks_cmd = 200 223 let run config_opt fmt () query limit cursor = ··· 247 270 in 248 271 let doc = "List all tags." in 249 272 let info = Cmd.info "list" ~doc in 250 - Cmd.v info Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 273 + Cmd.v info 274 + Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 251 275 252 276 let get_tag_cmd = 253 277 let run config_opt fmt () tag_id = ··· 260 284 let doc = "Get details of a specific tag." in 261 285 let info = Cmd.info "get" ~doc in 262 286 Cmd.v info 263 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ tag_id_term) 287 + Term.( 288 + const run $ config_opt_term $ output_format_term $ setup_logging 289 + $ tag_id_term) 264 290 265 291 let tag_bookmarks_cmd = 266 292 let run config_opt fmt () tag_id limit cursor = ··· 276 302 let info = Cmd.info "bookmarks" ~doc in 277 303 Cmd.v info 278 304 Term.( 279 - const run $ config_opt_term $ output_format_term $ setup_logging $ tag_id_term 280 - $ limit_term $ cursor_term) 305 + const run $ config_opt_term $ output_format_term $ setup_logging 306 + $ tag_id_term $ limit_term $ cursor_term) 281 307 282 308 let rename_tag_cmd = 283 309 let run config_opt fmt () tag_id name = ··· 291 317 let info = Cmd.info "rename" ~doc in 292 318 Cmd.v info 293 319 Term.( 294 - const run $ config_opt_term $ output_format_term $ setup_logging $ tag_id_term 295 - $ name_term) 320 + const run $ config_opt_term $ output_format_term $ setup_logging 321 + $ tag_id_term $ name_term) 296 322 297 323 let delete_tag_cmd = 298 324 let run config_opt () tag_id = ··· 313 339 let tag_refs = List.map (fun t -> `TagName t) tags in 314 340 let _ = Karakeep.attach_tags client ~tag_refs bookmark_id in 315 341 Logs.app (fun m -> 316 - m "Attached %d tags to bookmark %s" (List.length tags) bookmark_id)); 342 + m "Attached %d tags to bookmark %s" (List.length tags) 343 + bookmark_id)); 317 344 0) 318 345 in 319 346 let doc = "Attach tags to a bookmark." in 320 347 let info = Cmd.info "attach" ~doc in 321 348 Cmd.v info 322 - Term.(const run $ config_opt_term $ setup_logging $ bookmark_id_term $ tags_term) 349 + Term.( 350 + const run $ config_opt_term $ setup_logging $ bookmark_id_term $ tags_term) 323 351 324 352 let detach_tags_cmd = 325 353 let run config_opt () bookmark_id tags = ··· 328 356 let tag_refs = List.map (fun t -> `TagName t) tags in 329 357 let _ = Karakeep.detach_tags client ~tag_refs bookmark_id in 330 358 Logs.app (fun m -> 331 - m "Detached %d tags from bookmark %s" (List.length tags) bookmark_id)); 359 + m "Detached %d tags from bookmark %s" (List.length tags) 360 + bookmark_id)); 332 361 0) 333 362 in 334 363 let doc = "Detach tags from a bookmark." in 335 364 let info = Cmd.info "detach" ~doc in 336 365 Cmd.v info 337 - Term.(const run $ config_opt_term $ setup_logging $ bookmark_id_term $ tags_term) 366 + Term.( 367 + const run $ config_opt_term $ setup_logging $ bookmark_id_term $ tags_term) 338 368 339 369 let tags_cmd = 340 370 let doc = "Tag operations." in ··· 362 392 in 363 393 let doc = "List all lists." in 364 394 let info = Cmd.info "list" ~doc in 365 - Cmd.v info Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 395 + Cmd.v info 396 + Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 366 397 367 398 let get_list_cmd = 368 399 let run config_opt fmt () list_id = ··· 375 406 let doc = "Get details of a specific list." in 376 407 let info = Cmd.info "get" ~doc in 377 408 Cmd.v info 378 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ list_id_term) 409 + Term.( 410 + const run $ config_opt_term $ output_format_term $ setup_logging 411 + $ list_id_term) 379 412 380 413 let create_list_cmd = 381 414 let run config_opt fmt () name icon description parent_id query = ··· 395 428 let info = Cmd.info "create" ~doc in 396 429 Cmd.v info 397 430 Term.( 398 - const run $ config_opt_term $ output_format_term $ setup_logging $ name_term 399 - $ icon_term $ description_term $ parent_id_term $ query_term) 431 + const run $ config_opt_term $ output_format_term $ setup_logging 432 + $ name_term $ icon_term $ description_term $ parent_id_term $ query_term) 400 433 401 434 let update_list_cmd = 402 435 let run config_opt fmt () list_id name icon description query = 403 436 handle_errors (fun () -> 404 437 run_with_client config_opt (fun client -> 405 438 let lst = 406 - Karakeep.update_list client ?name ?icon ?description ?query list_id 439 + Karakeep.update_list client ?name ?icon ?description ?query 440 + list_id 407 441 in 408 442 print_list fmt lst); 409 443 0) ··· 412 446 let info = Cmd.info "update" ~doc in 413 447 Cmd.v info 414 448 Term.( 415 - const run $ config_opt_term $ output_format_term $ setup_logging $ list_id_term 416 - $ name_opt_term $ icon_opt_term $ description_term $ query_term) 449 + const run $ config_opt_term $ output_format_term $ setup_logging 450 + $ list_id_term $ name_opt_term $ icon_opt_term $ description_term 451 + $ query_term) 417 452 418 453 let delete_list_cmd = 419 454 let run config_opt () list_id = ··· 441 476 let info = Cmd.info "bookmarks" ~doc in 442 477 Cmd.v info 443 478 Term.( 444 - const run $ config_opt_term $ output_format_term $ setup_logging $ list_id_term 445 - $ limit_term $ cursor_term) 479 + const run $ config_opt_term $ output_format_term $ setup_logging 480 + $ list_id_term $ limit_term $ cursor_term) 446 481 447 482 let add_to_list_cmd = 448 483 let run config_opt () list_id bookmark_id = ··· 459 494 let doc = "Bookmark ID to add." in 460 495 Arg.(required & pos 1 (some string) None & info [] ~docv:"BOOKMARK_ID" ~doc) 461 496 in 462 - Cmd.v info Term.(const run $ config_opt_term $ setup_logging $ list_id_term $ bid_term) 497 + Cmd.v info 498 + Term.(const run $ config_opt_term $ setup_logging $ list_id_term $ bid_term) 463 499 464 500 let remove_from_list_cmd = 465 501 let run config_opt () list_id bookmark_id = ··· 476 512 let doc = "Bookmark ID to remove." in 477 513 Arg.(required & pos 1 (some string) None & info [] ~docv:"BOOKMARK_ID" ~doc) 478 514 in 479 - Cmd.v info Term.(const run $ config_opt_term $ setup_logging $ list_id_term $ bid_term) 515 + Cmd.v info 516 + Term.(const run $ config_opt_term $ setup_logging $ list_id_term $ bid_term) 480 517 481 518 let lists_cmd = 482 519 let doc = "List operations." in ··· 499 536 let run config_opt fmt () limit cursor = 500 537 handle_errors (fun () -> 501 538 run_with_client config_opt (fun client -> 502 - let result = Karakeep.fetch_all_highlights client ?limit ?cursor () in 539 + let result = 540 + Karakeep.fetch_all_highlights client ?limit ?cursor () 541 + in 503 542 print_highlights fmt result.highlights); 504 543 0) 505 544 in ··· 507 546 let info = Cmd.info "list" ~doc in 508 547 Cmd.v info 509 548 Term.( 510 - const run $ config_opt_term $ output_format_term $ setup_logging $ limit_term 511 - $ cursor_term) 549 + const run $ config_opt_term $ output_format_term $ setup_logging 550 + $ limit_term $ cursor_term) 512 551 513 552 let get_highlight_cmd = 514 553 let run config_opt fmt () highlight_id = 515 554 handle_errors (fun () -> 516 555 run_with_client config_opt (fun client -> 517 - let highlight = Karakeep.fetch_highlight_details client highlight_id in 556 + let highlight = 557 + Karakeep.fetch_highlight_details client highlight_id 558 + in 518 559 print_highlight fmt highlight); 519 560 0) 520 561 in 521 562 let doc = "Get details of a specific highlight." in 522 563 let info = Cmd.info "get" ~doc in 523 564 Cmd.v info 524 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ highlight_id_term) 565 + Term.( 566 + const run $ config_opt_term $ output_format_term $ setup_logging 567 + $ highlight_id_term) 525 568 526 569 let bookmark_highlights_cmd = 527 570 let run config_opt fmt () bookmark_id = 528 571 handle_errors (fun () -> 529 572 run_with_client config_opt (fun client -> 530 - let highlights = Karakeep.fetch_bookmark_highlights client bookmark_id in 573 + let highlights = 574 + Karakeep.fetch_bookmark_highlights client bookmark_id 575 + in 531 576 print_highlights fmt highlights); 532 577 0) 533 578 in 534 579 let doc = "List highlights for a bookmark." in 535 580 let info = Cmd.info "for-bookmark" ~doc in 536 581 Cmd.v info 537 - Term.(const run $ config_opt_term $ output_format_term $ setup_logging $ bookmark_id_term) 582 + Term.( 583 + const run $ config_opt_term $ output_format_term $ setup_logging 584 + $ bookmark_id_term) 538 585 539 586 let delete_highlight_cmd = 540 587 let run config_opt () highlight_id = ··· 546 593 in 547 594 let doc = "Delete a highlight." in 548 595 let info = Cmd.info "delete" ~doc in 549 - Cmd.v info Term.(const run $ config_opt_term $ setup_logging $ highlight_id_term) 596 + Cmd.v info 597 + Term.(const run $ config_opt_term $ setup_logging $ highlight_id_term) 550 598 551 599 let highlights_cmd = 552 600 let doc = "Highlight operations." in ··· 571 619 in 572 620 let doc = "Show current user info." in 573 621 let info = Cmd.info "whoami" ~doc in 574 - Cmd.v info Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 622 + Cmd.v info 623 + Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 575 624 576 625 let stats_cmd = 577 626 let run config_opt fmt () = ··· 583 632 in 584 633 let doc = "Show user statistics." in 585 634 let info = Cmd.info "stats" ~doc in 586 - Cmd.v info Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 635 + Cmd.v info 636 + Term.(const run $ config_opt_term $ output_format_term $ setup_logging) 587 637 588 638 (* Main command *) 589 639 ··· 622 672 `P " Be quiet. Takes over $(b,-v)."; 623 673 `P "$(b,--verbose-http)"; 624 674 `Noblank; 625 - `P " Enable verbose HTTP-level logging including TLS details and hexdumps."; 675 + `P 676 + " Enable verbose HTTP-level logging including TLS details and \ 677 + hexdumps."; 626 678 `P "$(b,--json), $(b,-J)"; 627 679 `Noblank; 628 680 `P " Output in JSON format.";
+3 -4
dune-project
··· 15 15 "An OCaml client library for the Karakeep bookmark service API. 16 16 Provides full API coverage for bookmarks, tags, lists, highlights, 17 17 and user operations. Built on Eio for structured concurrency with 18 - a type-safe interface using jsont for JSON encoding/decoding.") 18 + a type-safe interface using nox-json for JSON encoding/decoding.") 19 19 (depends 20 20 (ocaml (>= "5.2.0")) 21 21 (requests (>= "0.0.1")) 22 22 (eio (>= "1.2")) 23 23 eio_main 24 - (jsont (>= "0.1.0")) 25 - bytesrw 24 + nox-json 26 25 (ptime (>= "1.2.0")) 27 26 (fmt (>= "0.9.0")) 28 27 (uri (>= "4.0.0")) 29 28 (cmdliner (>= "1.3.0")) 30 29 (logs (>= "0.7.0")) 31 - xdge 30 + nox-xdge 32 31 tomlt 33 32 (odoc :with-doc)))
+3 -4
karakeep.opam
··· 5 5 An OCaml client library for the Karakeep bookmark service API. 6 6 Provides full API coverage for bookmarks, tags, lists, highlights, 7 7 and user operations. Built on Eio for structured concurrency with 8 - a type-safe interface using jsont for JSON encoding/decoding.""" 8 + a type-safe interface using nox-json for JSON encoding/decoding.""" 9 9 maintainer: ["anil@recoil.org"] 10 10 authors: ["Anil Madhavapeddy"] 11 11 license: "ISC" ··· 17 17 "requests" {>= "0.0.1"} 18 18 "eio" {>= "1.2"} 19 19 "eio_main" 20 - "jsont" {>= "0.1.0"} 21 - "bytesrw" 20 + "nox-json" 22 21 "ptime" {>= "1.2.0"} 23 22 "fmt" {>= "0.9.0"} 24 23 "uri" {>= "4.0.0"} 25 24 "cmdliner" {>= "1.3.0"} 26 25 "logs" {>= "0.7.0"} 27 - "xdge" 26 + "nox-xdge" 28 27 "tomlt" 29 28 "odoc" {with-doc} 30 29 ]
+2 -3
lib/cmd/dune
··· 12 12 fmt.tty 13 13 eio 14 14 eio_main 15 - jsont 16 - jsont.bytesrw 17 - xdge 15 + nox-json 16 + nox-xdge 18 17 tomlt.eio))
+47 -45
lib/cmd/karakeep_auth_cmd.ml
··· 27 27 Arg.(required & pos 0 (some string) None & info [] ~docv:"PROFILE" ~doc) 28 28 29 29 (* Helper to run with filesystem *) 30 - let with_fs f = 31 - Eio_main.run @@ fun env -> f env#fs 30 + let with_fs f = Eio_main.run @@ fun env -> f env#fs 32 31 33 32 (* Login command *) 34 33 ··· 39 38 if api_key = "" then begin 40 39 Fmt.epr "Error: API key cannot be empty.@."; 41 40 1 42 - end else begin 41 + end 42 + else begin 43 43 let creds : Karakeep_config.credentials = { api_key; base_url } in 44 44 (* Determine profile name *) 45 - let profile_name = match profile with 45 + let profile_name = 46 + match profile with 46 47 | Some p -> p 47 48 | None -> 48 49 let profiles = Karakeep_config.list_profiles fs in ··· 61 62 62 63 let login_cmd () = 63 64 let doc = "Configure API credentials for a Karakeep instance." in 64 - let man = [ 65 - `S Manpage.s_description; 66 - `P "Prompts for an API key and saves it to the specified profile."; 67 - `P "API keys can be generated from your Karakeep instance settings."; 68 - `S "EXAMPLES"; 69 - `P "$(b,karakeep auth login)"; 70 - `Noblank; 71 - `P " Login with default profile."; 72 - `P "$(b,karakeep auth login --profile work --base-url https://work.example.com)"; 73 - `Noblank; 74 - `P " Configure a work profile with a different instance."; 75 - ] in 65 + let man = 66 + [ 67 + `S Manpage.s_description; 68 + `P "Prompts for an API key and saves it to the specified profile."; 69 + `P "API keys can be generated from your Karakeep instance settings."; 70 + `S "EXAMPLES"; 71 + `P "$(b,karakeep auth login)"; 72 + `Noblank; 73 + `P " Login with default profile."; 74 + `P 75 + "$(b,karakeep auth login --profile work --base-url \ 76 + https://work.example.com)"; 77 + `Noblank; 78 + `P " Configure a work profile with a different instance."; 79 + ] 80 + in 76 81 let info = Cmd.info "login" ~doc ~man in 77 82 let login' profile base_url = with_fs (login_action ~profile ~base_url) in 78 83 Cmd.v info Term.(const login' $ profile_term $ base_url_term) ··· 80 85 (* Logout command *) 81 86 82 87 let logout_action ~profile fs = 83 - let profile_name = match profile with 88 + let profile_name = 89 + match profile with 84 90 | Some p -> p 85 91 | None -> Karakeep_config.get_current_profile fs 86 92 in ··· 110 116 if profiles <> [] then 111 117 Fmt.pr "Available profiles: %s@." (String.concat ", " profiles); 112 118 Fmt.pr "@."; 113 - let profile_name = match profile with 114 - | Some p -> p 115 - | None -> current 116 - in 119 + let profile_name = match profile with Some p -> p | None -> current in 117 120 match Karakeep_config.load_credentials fs ~profile:profile_name () with 118 121 | None -> 119 122 Fmt.pr "Profile '%s': Not configured.@." profile_name; ··· 141 144 if profiles = [] then begin 142 145 Fmt.pr "No profiles found. Use 'karakeep auth login' to create one.@."; 143 146 0 144 - end else begin 147 + end 148 + else begin 145 149 Fmt.pr "Profiles:@."; 146 - List.iter (fun p -> 147 - let marker = if p = current then " (current)" else "" in 148 - match Karakeep_config.load_credentials fs ~profile:p () with 149 - | Some creds -> Fmt.pr " %s%s - %s@." p marker creds.base_url 150 - | None -> Fmt.pr " %s%s@." p marker 151 - ) profiles; 150 + List.iter 151 + (fun p -> 152 + let marker = if p = current then " (current)" else "" in 153 + match Karakeep_config.load_credentials fs ~profile:p () with 154 + | Some creds -> Fmt.pr " %s%s - %s@." p marker creds.base_url 155 + | None -> Fmt.pr " %s%s@." p marker) 156 + profiles; 152 157 0 153 158 end 154 159 ··· 166 171 Karakeep_config.set_current_profile fs profile; 167 172 Fmt.pr "Switched to profile: %s@." profile; 168 173 0 169 - end else begin 174 + end 175 + else begin 170 176 Fmt.epr "Profile '%s' not found.@." profile; 171 177 if profiles <> [] then 172 178 Fmt.epr "Available profiles: %s@." (String.concat ", " profiles); ··· 197 203 let profile_cmd () = 198 204 let doc = "Profile management commands." in 199 205 let info = Cmd.info "profile" ~doc in 200 - Cmd.group info [ 201 - profile_list_cmd (); 202 - profile_switch_cmd (); 203 - profile_current_cmd (); 204 - ] 206 + Cmd.group info 207 + [ profile_list_cmd (); profile_switch_cmd (); profile_current_cmd () ] 205 208 206 209 (* Auth command group *) 207 210 208 211 let auth_cmd () = 209 212 let doc = "Authentication commands." in 210 - let man = [ 211 - `S Manpage.s_description; 212 - `P "Manage authentication credentials for Karakeep instances."; 213 - `P "Credentials are stored in ~/.config/karakeep/profiles/<name>/credentials.toml"; 214 - ] in 213 + let man = 214 + [ 215 + `S Manpage.s_description; 216 + `P "Manage authentication credentials for Karakeep instances."; 217 + `P 218 + "Credentials are stored in \ 219 + ~/.config/karakeep/profiles/<name>/credentials.toml"; 220 + ] 221 + in 215 222 let info = Cmd.info "auth" ~doc ~man in 216 - Cmd.group info [ 217 - login_cmd (); 218 - logout_cmd (); 219 - status_cmd (); 220 - profile_cmd (); 221 - ] 223 + Cmd.group info [ login_cmd (); logout_cmd (); status_cmd (); profile_cmd () ]
+2 -1
lib/cmd/karakeep_auth_cmd.mli
··· 5 5 6 6 (** Karakeep authentication CLI commands. 7 7 8 - Provides commands for managing API key credentials across multiple profiles. *) 8 + Provides commands for managing API key credentials across multiple profiles. 9 + *) 9 10 10 11 (** {1 Command Line Terms} *) 11 12
+72 -40
lib/cmd/karakeep_cmd.ml
··· 5 5 6 6 open Cmdliner 7 7 8 - type config = { 9 - base_url : string; 10 - api_key : string; 11 - } 8 + type config = { base_url : string; api_key : string } 12 9 13 10 (* Helper to read API key from file *) 14 11 let read_api_key_file path = ··· 35 32 let api_key_file_term = 36 33 let doc = "File containing the API key (legacy, use 'auth login' instead)." in 37 34 Arg.( 38 - value 39 - & opt string ".karakeep-api" 35 + value & opt string ".karakeep-api" 40 36 & info [ "api-key-file" ] ~docv:"FILE" ~doc) 41 37 42 38 (* API key direct term *) 43 39 let api_key_direct_term = 44 40 let doc = "API key for authentication (overrides profile)." in 45 41 let env = Cmd.Env.info "KARAKEEP_API_KEY" ~doc in 46 - Arg.(value & opt (some string) None & info [ "api-key"; "k" ] ~docv:"KEY" ~doc ~env) 42 + Arg.( 43 + value 44 + & opt (some string) None 45 + & info [ "api-key"; "k" ] ~docv:"KEY" ~doc ~env) 47 46 48 47 (* Config options from CLI - not yet resolved *) 49 48 type config_opt = { ··· 57 56 let make api_key_direct base_url_opt profile api_key_file = 58 57 { api_key_direct; base_url_opt; profile; api_key_file } 59 58 in 60 - Term.(const make $ api_key_direct_term $ base_url_opt_term $ profile_term $ api_key_file_term) 59 + Term.( 60 + const make $ api_key_direct_term $ base_url_opt_term $ profile_term 61 + $ api_key_file_term) 61 62 62 63 (* Resolve config with Eio filesystem *) 63 64 let resolve_config ~fs (opt : config_opt) : config = ··· 70 71 match opt.api_key_direct with 71 72 | Some key -> 72 73 (* Direct API key provided, use default or env base URL *) 73 - let url = match opt.base_url_opt with 74 + let url = 75 + match opt.base_url_opt with 74 76 | Some u -> u 75 77 | None -> Karakeep_config.default_base_url 76 78 in 77 79 (key, url) 78 80 | None -> 79 81 (* Check environment variable *) 80 - let env_key = try Sys.getenv "KARAKEEP_API_KEY" with Not_found -> "" in 82 + let env_key = 83 + try Sys.getenv "KARAKEEP_API_KEY" with Not_found -> "" 84 + in 81 85 if env_key <> "" then begin 82 - let url = match opt.base_url_opt with 86 + let url = 87 + match opt.base_url_opt with 83 88 | Some u -> u 84 - | None -> try Sys.getenv "KARAKEEP_BASE_URL" 85 - with Not_found -> Karakeep_config.default_base_url 89 + | None -> ( 90 + try Sys.getenv "KARAKEEP_BASE_URL" 91 + with Not_found -> Karakeep_config.default_base_url) 86 92 in 87 93 (env_key, url) 88 - end else begin 94 + end 95 + else begin 89 96 (* Try XDG profile credentials *) 90 - let profile_name = match opt.profile with 97 + let profile_name = 98 + match opt.profile with 91 99 | Some p -> p 92 100 | None -> Karakeep_config.get_current_profile fs 93 101 in 94 - match Karakeep_config.load_credentials fs ~profile:profile_name () with 102 + match 103 + Karakeep_config.load_credentials fs ~profile:profile_name () 104 + with 95 105 | Some creds -> 96 106 (* Apply base_url override if provided *) 97 - let url = match opt.base_url_opt with 107 + let url = 108 + match opt.base_url_opt with 98 109 | Some u -> u 99 110 | None -> creds.Karakeep_config.base_url 100 111 in ··· 103 114 (* Fall back to legacy .karakeep-api file *) 104 115 let file_key = read_api_key_file opt.api_key_file in 105 116 if file_key <> "" then begin 106 - let url = match opt.base_url_opt with 117 + let url = 118 + match opt.base_url_opt with 107 119 | Some u -> u 108 120 | None -> Karakeep_config.default_base_url 109 121 in 110 122 (file_key, url) 111 - end else 112 - failwith "No credentials found. Use 'karakeep auth login' or --api-key" 123 + end 124 + else 125 + failwith 126 + "No credentials found. Use 'karakeep auth login' or --api-key" 113 127 end 114 128 in 115 129 { base_url; api_key } ··· 121 135 122 136 let cursor_term = 123 137 let doc = "Pagination cursor for fetching next page." in 124 - Arg.(value & opt (some string) None & info [ "cursor"; "c" ] ~docv:"CURSOR" ~doc) 138 + Arg.( 139 + value & opt (some string) None & info [ "cursor"; "c" ] ~docv:"CURSOR" ~doc) 125 140 126 141 (* Filter terms *) 127 142 let archived_term = 128 143 let doc = "Filter for archived items." in 129 144 let archived = (Some true, Arg.info [ "archived" ] ~doc) in 130 145 let not_archived = 131 - (Some false, Arg.info [ "no-archived" ] ~doc:"Filter for non-archived items.") 146 + ( Some false, 147 + Arg.info [ "no-archived" ] ~doc:"Filter for non-archived items." ) 132 148 in 133 149 Arg.(value & vflag None [ archived; not_archived ]) 134 150 ··· 136 152 let doc = "Filter for favourited items." in 137 153 let fav = (Some true, Arg.info [ "favourited"; "fav" ] ~doc) in 138 154 let not_fav = 139 - (Some false, Arg.info [ "no-favourited"; "no-fav" ] ~doc:"Filter for non-favourited items.") 155 + ( Some false, 156 + Arg.info 157 + [ "no-favourited"; "no-fav" ] 158 + ~doc:"Filter for non-favourited items." ) 140 159 in 141 160 Arg.(value & vflag None [ fav; not_fav ]) 142 161 143 162 let include_content_term = 144 163 let doc = "Include full content in response." in 145 164 let include_it = (true, Arg.info [ "include-content" ] ~doc) in 146 - let exclude_it = (false, Arg.info [ "no-content" ] ~doc:"Exclude content from response.") in 165 + let exclude_it = 166 + (false, Arg.info [ "no-content" ] ~doc:"Exclude content from response.") 167 + in 147 168 Arg.(value & vflag true [ include_it; exclude_it ]) 148 169 149 170 (* Entity ID terms *) ··· 170 191 171 192 let title_term = 172 193 let doc = "Title for the bookmark." in 173 - Arg.(value & opt (some string) None & info [ "title"; "t" ] ~docv:"TITLE" ~doc) 194 + Arg.( 195 + value & opt (some string) None & info [ "title"; "t" ] ~docv:"TITLE" ~doc) 174 196 175 197 let note_term = 176 198 let doc = "Note to attach to the bookmark." in ··· 203 225 204 226 let description_term = 205 227 let doc = "Description for the list." in 206 - Arg.(value & opt (some string) None & info [ "description"; "d" ] ~docv:"TEXT" ~doc) 228 + Arg.( 229 + value 230 + & opt (some string) None 231 + & info [ "description"; "d" ] ~docv:"TEXT" ~doc) 207 232 208 233 let parent_id_term = 209 234 let doc = "Parent list ID for nesting." in ··· 211 236 212 237 let query_term = 213 238 let doc = "Query for smart list." in 214 - Arg.(value & opt (some string) None & info [ "query"; "q" ] ~docv:"QUERY" ~doc) 239 + Arg.( 240 + value & opt (some string) None & info [ "query"; "q" ] ~docv:"QUERY" ~doc) 215 241 216 242 let search_query_term = 217 243 let doc = "Search query." in ··· 248 274 249 275 let output_format_term = 250 276 let json = (Json, Arg.info [ "json"; "J" ] ~doc:"Output in JSON format.") in 251 - let ids_only = (Quiet, Arg.info [ "ids-only" ] ~doc:"Output only IDs (one per line).") in 277 + let ids_only = 278 + (Quiet, Arg.info [ "ids-only" ] ~doc:"Output only IDs (one per line).") 279 + in 252 280 Arg.(value & vflag Text [ json; ids_only ]) 253 281 254 282 (* Logging setup *) ··· 277 305 in 278 306 f client 279 307 280 - (* JSON encoding helpers using jsont *) 308 + (* JSON encoding helpers using nox-json *) 281 309 let encode_json codec v = 282 - match Jsont_bytesrw.encode_string codec v with 283 - | Ok s -> s 284 - | Error e -> raise (Karakeep.err (Karakeep.Json_error { reason = e })) 310 + try Json.to_string codec v 311 + with Json.Error e -> 312 + raise 313 + (Karakeep.err (Karakeep.Json_error { reason = Json.Error.to_string e })) 285 314 286 315 let json_of_bookmark b = encode_json Karakeep.bookmark_jsont b 287 316 let json_of_tag t = encode_json Karakeep.tag_jsont t ··· 294 323 295 324 let print_json_array to_json items = 296 325 print_string "["; 297 - List.iteri (fun i item -> 298 - if i > 0 then print_string ","; 299 - print_string (to_json item)) items; 326 + List.iteri 327 + (fun i item -> 328 + if i > 0 then print_string ","; 329 + print_string (to_json item)) 330 + items; 300 331 print_endline "]" 301 332 302 333 let print_bookmark fmt (b : Karakeep.bookmark) = ··· 304 335 | Text -> 305 336 let title = Karakeep.bookmark_title b in 306 337 let status = 307 - (if b.archived then "[A]" else "") 308 - ^ if b.favourited then "[*]" else "" 338 + (if b.archived then "[A]" else "") ^ if b.favourited then "[*]" else "" 309 339 in 310 340 Printf.printf "%s %s %s\n" b.id title status 311 341 | Json -> print_endline (json_of_bookmark b) ··· 331 361 match fmt with 332 362 | Text -> 333 363 let type_str = 334 - match l.list_type with Karakeep.Manual -> "" | Karakeep.Smart -> "[smart]" 364 + match l.list_type with 365 + | Karakeep.Manual -> "" 366 + | Karakeep.Smart -> "[smart]" 335 367 in 336 368 Printf.printf "%s %s %s %s\n" l.id l.icon l.name type_str 337 369 | Json -> print_endline (json_of_list l) ··· 382 414 (* Error handling *) 383 415 let handle_errors f = 384 416 try f () with 385 - | Eio.Io (Karakeep.E err, _) -> 417 + | Eio.Io (Karakeep.E err, _) -> ( 386 418 Logs.err (fun m -> m "Karakeep error: %s" (Karakeep.error_to_string err)); 387 - (match err with 419 + match err with 388 420 | Karakeep.Api_error { status; _ } when status = 404 -> 2 389 421 | Karakeep.Api_error { status; _ } when status >= 400 && status < 500 -> 2 390 422 | Karakeep.Api_error _ -> 2
+31 -26
lib/cmd/karakeep_cmd.mli
··· 12 12 {2 Basic Usage} 13 13 14 14 {[ 15 - open Cmdliner 15 + open Cmdliner 16 16 17 - let my_command = 18 - let open Karakeep_cmd in 19 - let run config = 20 - with_client config (fun client -> 21 - let bookmarks = Karakeep.fetch_all_bookmarks client () in 22 - List.iter (fun b -> print_endline (Karakeep.bookmark_title b)) bookmarks) 23 - in 24 - Cmd.v (Cmd.info "my-command") Term.(const run $ config_term) 17 + let my_command = 18 + let open Karakeep_cmd in 19 + let run config = 20 + with_client config (fun client -> 21 + let bookmarks = Karakeep.fetch_all_bookmarks client () in 22 + List.iter 23 + (fun b -> print_endline (Karakeep.bookmark_title b)) 24 + bookmarks) 25 + in 26 + Cmd.v (Cmd.info "my-command") Term.(const run $ config_term) 25 27 ]} *) 26 28 27 29 (** {1 Configuration} *) 28 30 29 31 type config = { 30 32 base_url : string; (** Base URL of the Karakeep instance *) 31 - api_key : string; (** API key for authentication *) 33 + api_key : string; (** API key for authentication *) 32 34 } 33 35 (** Configuration for connecting to a Karakeep instance. *) 34 36 35 37 type config_opt 36 - (** Configuration options from CLI, not yet resolved. 37 - Use {!resolve_config} or {!with_client} with an Eio env to resolve. *) 38 + (** Configuration options from CLI, not yet resolved. Use {!resolve_config} or 39 + {!with_client} with an Eio env to resolve. *) 38 40 39 41 val config_opt_term : config_opt Cmdliner.Term.t 40 42 (** Cmdliner term that parses configuration options from command-line arguments. 41 43 The actual credentials are resolved at runtime by {!resolve_config} or 42 44 {!with_client} when given an Eio environment. 43 45 44 - Configuration is resolved in priority order: 45 - 1. [--api-key KEY] flag 46 - 2. [KARAKEEP_API_KEY] environment variable 47 - 3. XDG profile credentials (~/.config/karakeep/profiles/...) 48 - 4. Legacy [--api-key-file FILE] (default: .karakeep-api) 46 + Configuration is resolved in priority order: 1. [--api-key KEY] flag 2. 47 + [KARAKEEP_API_KEY] environment variable 3. XDG profile credentials 48 + (~/.config/karakeep/profiles/...) 4. Legacy [--api-key-file FILE] (default: 49 + .karakeep-api) 49 50 50 51 Options: 51 52 - [--profile NAME] or [-P NAME]: Select a specific profile ··· 146 147 (** {1 Output Terms} *) 147 148 148 149 type output_format = 149 - | Text (** Human-readable text output *) 150 - | Json (** JSON output *) 150 + | Text (** Human-readable text output *) 151 + | Json (** JSON output *) 151 152 | Quiet (** Minimal output (IDs only) *) 152 153 153 154 val output_format_term : output_format Cmdliner.Term.t ··· 156 157 (** {1 Logging Setup} *) 157 158 158 159 val setup_logging : unit Cmdliner.Term.t 159 - (** Term that sets up logging based on verbosity flags. 160 - Use with [Logs_cli] and [Fmt_cli] for standard options. *) 160 + (** Term that sets up logging based on verbosity flags. Use with [Logs_cli] and 161 + [Fmt_cli] for standard options. *) 161 162 162 163 val logs_term : Logs.level option Cmdliner.Term.t 163 164 (** Term for log level from [Logs_cli]. *) ··· 168 169 (** {1 Client Helpers} *) 169 170 170 171 val with_client : 171 - env:< clock : _ Eio.Time.clock ; fs : Eio.Fs.dir_ty Eio.Path.t ; net : _ Eio.Net.t ; .. > -> 172 + env: 173 + < clock : _ Eio.Time.clock 174 + ; fs : Eio.Fs.dir_ty Eio.Path.t 175 + ; net : _ Eio.Net.t 176 + ; .. > -> 172 177 sw:Eio.Switch.t -> 173 178 config_opt -> 174 179 (Karakeep.t -> 'a) -> 175 180 'a 176 - (** [with_client ~env ~sw config_opt f] resolves configuration and runs [f] 177 - with a Karakeep client. 181 + (** [with_client ~env ~sw config_opt f] resolves configuration and runs [f] with 182 + a Karakeep client. 178 183 179 184 {[ 180 185 let run config_opt = ··· 222 227 (** {1 Error Handling} *) 223 228 224 229 val handle_errors : (unit -> int) -> int 225 - (** [handle_errors f] runs [f ()] and catches Karakeep errors, 226 - printing them to stderr and returning appropriate exit codes. 230 + (** [handle_errors f] runs [f ()] and catches Karakeep errors, printing them to 231 + stderr and returning appropriate exit codes. 227 232 228 233 Exit codes: 229 234 - 0: Success
+27 -27
lib/cmd/karakeep_config.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 - type credentials = { 7 - api_key : string; 8 - base_url : string; 9 - } 6 + type credentials = { api_key : string; base_url : string } 10 7 11 8 let app_name = "karakeep" 12 9 let default_base_url = "https://hoard.recoil.org" ··· 14 11 15 12 (* TOML codec for credentials *) 16 13 let credentials_tomlt = 17 - Tomlt.(Table.( 18 - obj (fun api_key base_url -> { api_key; base_url }) 19 - |> mem "api_key" string ~enc:(fun c -> c.api_key) 20 - |> mem "base_url" string ~enc:(fun c -> c.base_url) ~dec_absent:default_base_url 21 - |> finish 22 - )) 14 + Tomlt.( 15 + Table.( 16 + obj (fun api_key base_url -> { api_key; base_url }) 17 + |> mem "api_key" string ~enc:(fun c -> c.api_key) 18 + |> mem "base_url" string 19 + ~enc:(fun c -> c.base_url) 20 + ~dec_absent:default_base_url 21 + |> finish)) 23 22 24 23 (* App config stores current profile name *) 25 24 type app_config = { current_profile : string } 26 25 27 26 let app_config_tomlt = 28 - Tomlt.(Table.( 29 - obj (fun current_profile -> { current_profile }) 30 - |> mem "current_profile" string ~enc:(fun c -> c.current_profile) ~dec_absent:default_profile 31 - |> finish 32 - )) 27 + Tomlt.( 28 + Table.( 29 + obj (fun current_profile -> { current_profile }) 30 + |> mem "current_profile" string 31 + ~enc:(fun c -> c.current_profile) 32 + ~dec_absent:default_profile 33 + |> finish)) 33 34 34 35 (* Directory helpers *) 35 36 ··· 59 60 (* Config file paths *) 60 61 61 62 let app_config_file fs = Eio.Path.(base_config_dir fs / "config.toml") 62 - let credentials_file fs profile = Eio.Path.(profile_dir fs profile / "credentials.toml") 63 + 64 + let credentials_file fs profile = 65 + Eio.Path.(profile_dir fs profile / "credentials.toml") 63 66 64 67 (* App config operations *) 65 68 ··· 102 105 (* Credential operations *) 103 106 104 107 let load_credentials fs ?profile () = 105 - let profile = match profile with 106 - | Some p -> p 107 - | None -> get_current_profile fs 108 + let profile = 109 + match profile with Some p -> p | None -> get_current_profile fs 108 110 in 109 111 let path = credentials_file fs profile in 110 112 try ··· 114 116 with Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> None 115 117 116 118 let save_credentials fs ?profile creds = 117 - let profile = match profile with 118 - | Some p -> p 119 - | None -> get_current_profile fs 119 + let profile = 120 + match profile with Some p -> p | None -> get_current_profile fs 120 121 in 121 122 let path = credentials_file fs profile in 122 123 Tomlt_eio.encode_file credentials_tomlt creds path 123 124 124 125 let clear_credentials fs ?profile () = 125 - let profile = match profile with 126 - | Some p -> p 127 - | None -> get_current_profile fs 126 + let profile = 127 + match profile with Some p -> p | None -> get_current_profile fs 128 128 in 129 129 let path = credentials_file fs profile in 130 130 try Eio.Path.unlink path ··· 145 145 match Sys.getenv_opt "KARAKEEP_API_KEY" with 146 146 | Some key when key <> "" -> Some key 147 147 | _ -> 148 - (* Then try .karakeep-api file *) 149 - read_api_key_file ".karakeep-api" 148 + (* Then try .karakeep-api file *) 149 + read_api_key_file ".karakeep-api"
+25 -26
lib/cmd/karakeep_config.mli
··· 5 5 6 6 (** Karakeep configuration management with XDG support. 7 7 8 - This module provides profile-based credential storage following XDG 9 - Base Directory conventions. Configuration is stored in TOML format at: 8 + This module provides profile-based credential storage following XDG Base 9 + Directory conventions. Configuration is stored in TOML format at: 10 10 11 11 {v 12 12 ~/.config/karakeep/ ··· 16 16 │ └── credentials.toml 17 17 └── work/ 18 18 └── credentials.toml 19 - v} 20 - *) 19 + v} *) 21 20 22 21 (** {1 Configuration Types} *) 23 22 24 - type credentials = { 25 - api_key : string; 26 - base_url : string; 27 - } 23 + type credentials = { api_key : string; base_url : string } 28 24 (** Stored credentials for a Karakeep instance. *) 29 25 30 26 (** {1 Constants} *) ··· 41 37 (** {1 Directory Paths} *) 42 38 43 39 val base_config_dir : Eio.Fs.dir_ty Eio.Path.t -> Eio.Fs.dir_ty Eio.Path.t 44 - (** [base_config_dir fs] returns the base config directory for karakeep. 45 - Creates the directory if it doesn't exist. *) 40 + (** [base_config_dir fs] returns the base config directory for karakeep. Creates 41 + the directory if it doesn't exist. *) 46 42 47 43 val profiles_dir : Eio.Fs.dir_ty Eio.Path.t -> Eio.Fs.dir_ty Eio.Path.t 48 - (** [profiles_dir fs] returns the profiles subdirectory. 49 - Creates the directory if it doesn't exist. *) 44 + (** [profiles_dir fs] returns the profiles subdirectory. Creates the directory 45 + if it doesn't exist. *) 50 46 51 47 val profile_dir : Eio.Fs.dir_ty Eio.Path.t -> string -> Eio.Fs.dir_ty Eio.Path.t 52 48 (** [profile_dir fs profile] returns the directory for a specific profile. ··· 55 51 (** {1 Profile Management} *) 56 52 57 53 val get_current_profile : Eio.Fs.dir_ty Eio.Path.t -> string 58 - (** [get_current_profile fs] returns the current profile name. 59 - Returns ["default"] if no profile is set. *) 54 + (** [get_current_profile fs] returns the current profile name. Returns 55 + ["default"] if no profile is set. *) 60 56 61 57 val set_current_profile : Eio.Fs.dir_ty Eio.Path.t -> string -> unit 62 58 (** [set_current_profile fs name] sets the current profile. *) ··· 66 62 67 63 (** {1 Credential Storage} *) 68 64 69 - val load_credentials : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> credentials option 70 - (** [load_credentials fs ?profile ()] loads credentials for a profile. 71 - Uses current profile if not specified. Returns [None] if not found. *) 65 + val load_credentials : 66 + Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> credentials option 67 + (** [load_credentials fs ?profile ()] loads credentials for a profile. Uses 68 + current profile if not specified. Returns [None] if not found. *) 72 69 73 - val save_credentials : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> credentials -> unit 74 - (** [save_credentials fs ?profile creds] saves credentials to a profile. 75 - Uses current profile if not specified. *) 70 + val save_credentials : 71 + Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> credentials -> unit 72 + (** [save_credentials fs ?profile creds] saves credentials to a profile. Uses 73 + current profile if not specified. *) 76 74 77 - val clear_credentials : Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> unit 78 - (** [clear_credentials fs ?profile ()] removes credentials for a profile. 79 - Uses current profile if not specified. *) 75 + val clear_credentials : 76 + Eio.Fs.dir_ty Eio.Path.t -> ?profile:string -> unit -> unit 77 + (** [clear_credentials fs ?profile ()] removes credentials for a profile. Uses 78 + current profile if not specified. *) 80 79 81 80 (** {1 Legacy Migration} *) 82 81 83 82 val load_legacy_api_key : unit -> string option 84 - (** [load_legacy_api_key ()] attempts to read API key from legacy locations: 85 - 1. KARAKEEP_API_KEY environment variable 86 - 2. .karakeep-api file in current directory *) 83 + (** [load_legacy_api_key ()] attempts to read API key from legacy locations: 1. 84 + KARAKEEP_API_KEY environment variable 2. .karakeep-api file in current 85 + directory *)
+1 -1
lib/dune
··· 1 1 (library 2 2 (name karakeep) 3 3 (public_name karakeep) 4 - (libraries karakeep.proto requests eio jsont jsont.bytesrw ptime fmt logs)) 4 + (libraries karakeep.proto requests eio nox-json ptime fmt logs))
+67 -58
lib/karakeep.ml
··· 22 22 type Eio.Exn.err += E of error 23 23 24 24 let err e = Eio.Exn.create (E e) 25 - 26 25 let is_api_error = function Api_error _ -> true | _ -> false 27 - 28 - let is_not_found = function 29 - | Api_error { status = 404; _ } -> true 30 - | _ -> false 26 + let is_not_found = function Api_error { status = 404; _ } -> true | _ -> false 31 27 32 28 let error_to_string = function 33 29 | Api_error { status; code; message } -> ··· 38 34 39 35 (** {1 Client} *) 40 36 41 - type t = { 42 - session : Requests.t; 43 - base_url : string; 44 - } 37 + type t = { session : Requests.t; base_url : string } 45 38 46 39 let create ~sw ~env ~base_url ~api_key = 47 - let session = Requests.create ~sw env in 40 + let session = Requests.v ~sw env in 48 41 let session = 49 42 Requests.set_auth session (Requests.Auth.bearer ~token:api_key) 50 43 in ··· 71 64 | _ -> 72 65 "?" 73 66 ^ String.concat "&" 74 - (List.map (fun (k, v) -> Uri.pct_encode k ^ "=" ^ Uri.pct_encode v) params) 67 + (List.map 68 + (fun (k, v) -> Uri.pct_encode k ^ "=" ^ Uri.pct_encode v) 69 + params) 75 70 76 71 (** Helpers for building query parameters *) 77 72 ··· 84 79 let add_bool key = add_param key (function true -> "true" | false -> "false") 85 80 86 81 let decode_json codec body_str = 87 - match Jsont_bytesrw.decode_string' codec body_str with 82 + match Json.of_string codec body_str with 88 83 | Ok v -> v 89 - | Error e -> 90 - raise (err (Json_error { reason = Jsont.Error.to_string e })) 84 + | Error e -> raise (err (Json_error { reason = Json.Error.to_string e })) 91 85 92 86 let encode_json codec value = 93 - match Jsont_bytesrw.encode_string' codec value with 94 - | Ok s -> s 95 - | Error e -> 96 - raise (err (Json_error { reason = Jsont.Error.to_string e })) 87 + try Json.to_string codec value 88 + with Json.Error e -> 89 + raise (err (Json_error { reason = Json.Error.to_string e })) 97 90 98 91 let handle_error_response status body = 99 - match Jsont_bytesrw.decode_string' error_response_jsont body with 92 + match Json.of_string error_response_jsont body with 100 93 | Ok err_resp -> 101 - raise (err (Api_error { status; code = err_resp.code; message = err_resp.message })) 94 + raise 95 + (err 96 + (Api_error 97 + { status; code = err_resp.code; message = err_resp.message })) 102 98 | Error _ -> 103 99 raise (err (Api_error { status; code = "unknown"; message = body })) 104 100 ··· 153 149 154 150 let fetch_bookmarks t ?limit ?cursor ?include_content ?archived ?favourited () = 155 151 let params = 156 - [] 157 - |> add_int "limit" limit 158 - |> add_opt "cursor" cursor 152 + [] |> add_int "limit" limit |> add_opt "cursor" cursor 159 153 |> add_bool "includeContent" include_content 160 154 |> add_bool "archived" archived 161 155 |> add_bool "favourited" favourited 162 156 in 163 - let url = t.base_url / "api/v1/bookmarks" ^ query_string params in 157 + let url = (t.base_url / "api/v1/bookmarks") ^ query_string params in 164 158 get_json t url paginated_bookmarks_jsont 165 159 166 160 let fetch_all_bookmarks t ?page_size ?max_pages ?archived ?favourited () = ··· 168 162 let rec fetch_all acc cursor pages_fetched = 169 163 match max_pages with 170 164 | Some max when pages_fetched >= max -> List.rev acc 171 - | _ -> 172 - let result = fetch_bookmarks t ~limit ?cursor ?archived ?favourited () in 165 + | _ -> ( 166 + let result = 167 + fetch_bookmarks t ~limit ?cursor ?archived ?favourited () 168 + in 173 169 let acc = List.rev_append result.bookmarks acc in 174 - (match result.next_cursor with 175 - | None -> List.rev acc 176 - | Some c -> fetch_all acc (Some c) (pages_fetched + 1)) 170 + match result.next_cursor with 171 + | None -> List.rev acc 172 + | Some c -> fetch_all acc (Some c) (pages_fetched + 1)) 177 173 in 178 174 fetch_all [] None 0 179 175 180 176 let search_bookmarks t ~query ?limit ?cursor ?include_content () = 181 177 let params = 182 178 [ ("q", query) ] 183 - |> add_int "limit" limit 184 - |> add_opt "cursor" cursor 179 + |> add_int "limit" limit |> add_opt "cursor" cursor 185 180 |> add_bool "includeContent" include_content 186 181 in 187 - let url = t.base_url / "api/v1/bookmarks/search" ^ query_string params in 182 + let url = (t.base_url / "api/v1/bookmarks/search") ^ query_string params in 188 183 get_json t url paginated_bookmarks_jsont 189 184 190 185 let fetch_bookmark_details t bookmark_id = 191 186 let url = t.base_url / "api/v1/bookmarks" / bookmark_id in 192 187 get_json t url bookmark_jsont 193 188 194 - let tag_ref_of_poly = function `TagId id -> TagId id | `TagName name -> TagName name 189 + let tag_ref_of_poly = function 190 + | `TagId id -> TagId id 191 + | `TagName name -> TagName name 195 192 196 - let rec create_bookmark t ~url ?title ?note ?summary ?favourited ?archived ?created_at 197 - ?tags () = 193 + let rec create_bookmark t ~url ?title ?note ?summary ?favourited ?archived 194 + ?created_at ?tags () = 198 195 let api_url = t.base_url / "api/v1/bookmarks" in 199 196 let req : create_bookmark_request = 200 197 { ··· 209 206 created_at; 210 207 } 211 208 in 212 - let bookmark = post_json t api_url create_bookmark_request_jsont req bookmark_jsont in 209 + let bookmark = 210 + post_json t api_url create_bookmark_request_jsont req bookmark_jsont 211 + in 213 212 (* Attach tags if provided *) 214 213 match tags with 215 214 | None | Some [] -> bookmark ··· 223 222 let url = t.base_url / "api/v1/bookmarks" / bookmark_id / "tags" in 224 223 let tags = List.map tag_ref_of_poly tag_refs in 225 224 let req = { tags } in 226 - let resp = post_json t url attach_tags_request_jsont req attach_tags_response_jsont in 225 + let resp = 226 + post_json t url attach_tags_request_jsont req attach_tags_response_jsont 227 + in 227 228 resp.attached 228 229 229 - let update_bookmark t bookmark_id ?title ?note ?summary ?favourited ?archived () = 230 + let update_bookmark t bookmark_id ?title ?note ?summary ?favourited ?archived () 231 + = 230 232 let url = t.base_url / "api/v1/bookmarks" / bookmark_id in 231 - let req : update_bookmark_request = { title; note; summary; archived; favourited } in 233 + let req : update_bookmark_request = 234 + { title; note; summary; archived; favourited } 235 + in 232 236 patch_json t url update_bookmark_request_jsont req bookmark_jsont 233 237 234 238 let delete_bookmark t bookmark_id = ··· 266 270 267 271 let fetch_bookmarks_with_tag t ?limit ?cursor ?include_content tag_id = 268 272 let params = 269 - [] 270 - |> add_int "limit" limit 271 - |> add_opt "cursor" cursor 273 + [] |> add_int "limit" limit |> add_opt "cursor" cursor 272 274 |> add_bool "includeContent" include_content 273 275 in 274 - let url = t.base_url / "api/v1/tags" / tag_id / "bookmarks" ^ query_string params in 276 + let url = 277 + (t.base_url / "api/v1/tags" / tag_id / "bookmarks") ^ query_string params 278 + in 275 279 get_json t url paginated_bookmarks_jsont 276 280 277 281 let update_tag t ~name tag_id = ··· 302 306 | Some Smart -> Some "smart" 303 307 | None -> None 304 308 in 305 - let req : create_list_request = { name; icon; description; parent_id; type_; query } in 309 + let req : create_list_request = 310 + { name; icon; description; parent_id; type_; query } 311 + in 306 312 post_json t url create_list_request_jsont req list_jsont 307 313 308 314 let update_list t ?name ?description ?icon ?parent_id ?query list_id = 309 315 let url = t.base_url / "api/v1/lists" / list_id in 310 - let req : update_list_request = { name; icon; description; parent_id; query } in 316 + let req : update_list_request = 317 + { name; icon; description; parent_id; query } 318 + in 311 319 patch_json t url update_list_request_jsont req list_jsont 312 320 313 321 let delete_list t list_id = ··· 316 324 317 325 let fetch_bookmarks_in_list t ?limit ?cursor ?include_content list_id = 318 326 let params = 319 - [] 320 - |> add_int "limit" limit 321 - |> add_opt "cursor" cursor 327 + [] |> add_int "limit" limit |> add_opt "cursor" cursor 322 328 |> add_bool "includeContent" include_content 323 329 in 324 - let url = t.base_url / "api/v1/lists" / list_id / "bookmarks" ^ query_string params in 330 + let url = 331 + (t.base_url / "api/v1/lists" / list_id / "bookmarks") ^ query_string params 332 + in 325 333 get_json t url paginated_bookmarks_jsont 326 334 327 335 let add_bookmark_to_list t list_id bookmark_id = ··· 338 346 (** {1 Highlight Operations} *) 339 347 340 348 let fetch_all_highlights t ?limit ?cursor () = 341 - let params = 342 - [] 343 - |> add_int "limit" limit 344 - |> add_opt "cursor" cursor 345 - in 346 - let url = t.base_url / "api/v1/highlights" ^ query_string params in 349 + let params = [] |> add_int "limit" limit |> add_opt "cursor" cursor in 350 + let url = (t.base_url / "api/v1/highlights") ^ query_string params in 347 351 get_json t url paginated_highlights_jsont 348 352 349 353 let fetch_bookmark_highlights t bookmark_id = ··· 355 359 let url = t.base_url / "api/v1/highlights" / highlight_id in 356 360 get_json t url highlight_jsont 357 361 358 - let create_highlight t ~bookmark_id ~start_offset ~end_offset ~text ?note ?color () = 362 + let create_highlight t ~bookmark_id ~start_offset ~end_offset ~text ?note ?color 363 + () = 359 364 let url = t.base_url / "api/v1/highlights" in 360 365 let req : create_highlight_request = 361 366 { bookmark_id; start_offset; end_offset; text; note; color } ··· 389 394 post_json t url attach_asset_request_jsont req asset_jsont 390 395 391 396 let replace_asset t ~new_asset_id bookmark_id asset_id = 392 - let url = t.base_url / "api/v1/bookmarks" / bookmark_id / "assets" / asset_id in 397 + let url = 398 + t.base_url / "api/v1/bookmarks" / bookmark_id / "assets" / asset_id 399 + in 393 400 let req : replace_asset_request = { asset_id = new_asset_id } in 394 401 let _ = put_json t url replace_asset_request_jsont req in 395 402 () 396 403 397 404 let detach_asset t bookmark_id asset_id = 398 - let url = t.base_url / "api/v1/bookmarks" / bookmark_id / "assets" / asset_id in 405 + let url = 406 + t.base_url / "api/v1/bookmarks" / bookmark_id / "assets" / asset_id 407 + in 399 408 delete_json t url 400 409 401 410 (** {1 User Operations} *)
+29 -14
lib/karakeep.mli
··· 12 12 13 13 Enable debug logging to trace API calls: 14 14 {[ 15 - Logs.Src.set_level Karakeep.src (Some Logs.Debug); 16 - Logs.set_reporter (Logs_fmt.reporter ()) 15 + Logs.Src.set_level Karakeep.src (Some Logs.Debug); 16 + Logs.set_reporter (Logs_fmt.reporter ()) 17 17 ]} 18 18 19 19 {2 Basic Usage} ··· 73 73 val src : Logs.Src.t 74 74 (** Logs source for Karakeep API client. Configure with: 75 75 {[ 76 - Logs.Src.set_level Karakeep.src (Some Logs.Debug) 76 + Logs.Src.set_level Karakeep.src (Some Logs.Debug) 77 77 ]} *) 78 78 79 79 (** {1 Error Handling} *) ··· 87 87 type Eio.Exn.err += E of error 88 88 89 89 val err : error -> exn 90 - (** [err e] creates an Eio exception from an error. 91 - Usage: [raise (err (Api_error { status = 404; code = "not_found"; message = "..." }))] *) 90 + (** [err e] creates an Eio exception from an error. Usage: 91 + [raise (err (Api_error { status = 404; code = "not_found"; message = "..." 92 + }))] *) 92 93 93 94 val is_api_error : error -> bool 94 95 (** [is_api_error e] returns [true] if the error is an API error. *) ··· 105 106 (** {1 Client} *) 106 107 107 108 type t 108 - (** The Karakeep client type. Wraps a Requests session with the base URL 109 - and authentication. *) 109 + (** The Karakeep client type. Wraps a Requests session with the base URL and 110 + authentication. *) 110 111 111 112 val create : 112 113 sw:Eio.Switch.t -> 113 - env:< clock : _ Eio.Time.clock ; net : _ Eio.Net.t ; fs : Eio.Fs.dir_ty Eio.Path.t ; .. > -> 114 + env: 115 + < clock : _ Eio.Time.clock 116 + ; net : _ Eio.Net.t 117 + ; fs : Eio.Fs.dir_ty Eio.Path.t 118 + ; .. > -> 114 119 base_url:string -> 115 120 api_key:string -> 116 121 t ··· 118 123 119 124 @param sw Switch for resource management 120 125 @param env Eio environment providing clock and network 121 - @param base_url Base URL of the Karakeep instance (e.g., "https://hoard.recoil.org") 126 + @param base_url 127 + Base URL of the Karakeep instance (e.g., "https://hoard.recoil.org") 122 128 @param api_key API key for authentication *) 123 129 124 130 (** {1 Bookmark Operations} *) ··· 226 232 (** {1 Tag Operations} *) 227 233 228 234 val attach_tags : 229 - t -> tag_refs:[ `TagId of tag_id | `TagName of string ] list -> bookmark_id -> tag_id list 235 + t -> 236 + tag_refs:[ `TagId of tag_id | `TagName of string ] list -> 237 + bookmark_id -> 238 + tag_id list 230 239 (** [attach_tags client ~tag_refs bookmark_id] attaches tags to a bookmark. 231 240 @raise Eio.Io with {!E} on API or network errors *) 232 241 233 242 val detach_tags : 234 - t -> tag_refs:[ `TagId of tag_id | `TagName of string ] list -> bookmark_id -> tag_id list 243 + t -> 244 + tag_refs:[ `TagId of tag_id | `TagName of string ] list -> 245 + bookmark_id -> 246 + tag_id list 235 247 (** [detach_tags client ~tag_refs bookmark_id] detaches tags from a bookmark. 236 248 @raise Eio.Io with {!E} on API or network errors *) 237 249 ··· 315 327 @raise Eio.Io with {!E} on API or network errors *) 316 328 317 329 val remove_bookmark_from_list : t -> list_id -> bookmark_id -> unit 318 - (** [remove_bookmark_from_list client list_id bookmark_id] removes a bookmark from a list. 330 + (** [remove_bookmark_from_list client list_id bookmark_id] removes a bookmark 331 + from a list. 319 332 @raise Eio.Io with {!E} on API or network errors *) 320 333 321 334 (** {1 Highlight Operations} *) ··· 326 339 @raise Eio.Io with {!E} on API or network errors *) 327 340 328 341 val fetch_bookmark_highlights : t -> bookmark_id -> highlight list 329 - (** [fetch_bookmark_highlights client bookmark_id] fetches highlights for a bookmark. 342 + (** [fetch_bookmark_highlights client bookmark_id] fetches highlights for a 343 + bookmark. 330 344 @raise Eio.Io with {!E} on API or network errors *) 331 345 332 346 val fetch_highlight_details : t -> highlight_id -> highlight ··· 369 383 (** [attach_asset client ~asset_id ~asset_type bookmark_id] attaches an asset. 370 384 @raise Eio.Io with {!E} on API or network errors *) 371 385 372 - val replace_asset : t -> new_asset_id:asset_id -> bookmark_id -> asset_id -> unit 386 + val replace_asset : 387 + t -> new_asset_id:asset_id -> bookmark_id -> asset_id -> unit 373 388 (** [replace_asset client ~new_asset_id bookmark_id asset_id] replaces an asset. 374 389 @raise Eio.Io with {!E} on API or network errors *) 375 390
+1 -1
lib/proto/dune
··· 1 1 (library 2 2 (name karakeep_proto) 3 3 (public_name karakeep.proto) 4 - (libraries jsont ptime)) 4 + (libraries nox-json ptime))
+428 -259
lib/proto/karakeep_proto.ml
··· 17 17 let (y, m, d), ((hh, mm, ss), _) = Ptime.to_date_time t in 18 18 Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ" y m d hh mm ss 19 19 in 20 - Jsont.of_of_string ~kind:"Ptime.t" dec ~enc 20 + Json.Codec.of_of_string ~kind:"Ptime.t" dec ~enc 21 21 22 22 let ptime_option_jsont = 23 - let null = Jsont.null None in 24 - let some = Jsont.map ~dec:(fun t -> Some t) ~enc:(function Some t -> t | None -> assert false) ptime_jsont in 25 - Jsont.any ~dec_null:null ~dec_string:some ~enc:(function None -> null | Some _ -> some) () 23 + let null = Json.Codec.null None in 24 + let some = 25 + Json.Codec.map 26 + ~dec:(fun t -> Some t) 27 + ~enc:(function Some t -> t | None -> assert false) 28 + ptime_jsont 29 + in 30 + Json.Codec.any ~dec_null:null ~dec_string:some 31 + ~enc:(function None -> null | Some _ -> some) 32 + () 26 33 27 34 (** {1 ID Types} *) 28 35 ··· 50 57 | Asset -> "asset" 51 58 | Unknown -> "unknown" 52 59 in 53 - Jsont.of_of_string ~kind:"bookmark_content_type" dec ~enc 60 + Json.Codec.of_of_string ~kind:"bookmark_content_type" dec ~enc 54 61 55 62 type asset_type = 56 63 | Screenshot ··· 84 91 | PrecrawledArchive -> "precrawledArchive" 85 92 | Unknown -> "unknown" 86 93 in 87 - Jsont.of_of_string ~kind:"asset_type" dec ~enc 94 + Json.Codec.of_of_string ~kind:"asset_type" dec ~enc 88 95 89 96 type tagging_status = Success | Failure | Pending 90 97 ··· 101 108 | Failure -> "failure" 102 109 | Pending -> "pending" 103 110 in 104 - Jsont.of_of_string ~kind:"tagging_status" dec ~enc 111 + Json.Codec.of_of_string ~kind:"tagging_status" dec ~enc 105 112 106 113 let string_of_tagging_status = function 107 114 | Success -> "success" ··· 117 124 | "smart" -> Ok Smart 118 125 | _ -> Ok Manual 119 126 in 120 - let enc = function Manual -> "manual" | Smart -> "smart" 121 - in 122 - Jsont.of_of_string ~kind:"list_type" dec ~enc 127 + let enc = function Manual -> "manual" | Smart -> "smart" in 128 + Json.Codec.of_of_string ~kind:"list_type" dec ~enc 123 129 124 130 type highlight_color = Yellow | Red | Green | Blue 125 131 ··· 138 144 | Green -> "green" 139 145 | Blue -> "blue" 140 146 in 141 - Jsont.of_of_string ~kind:"highlight_color" dec ~enc 147 + Json.Codec.of_of_string ~kind:"highlight_color" dec ~enc 142 148 143 149 let string_of_highlight_color = function 144 150 | Yellow -> "yellow" ··· 155 161 | "human" -> Ok Human 156 162 | _ -> Ok Human 157 163 in 158 - let enc = function AI -> "ai" | Human -> "human" 159 - in 160 - Jsont.of_of_string ~kind:"tag_attachment_type" dec ~enc 164 + let enc = function AI -> "ai" | Human -> "human" in 165 + Json.Codec.of_of_string ~kind:"tag_attachment_type" dec ~enc 161 166 162 167 let string_of_tag_attachment_type = function AI -> "ai" | Human -> "human" 163 168 ··· 182 187 date_modified : Ptime.t option; 183 188 } 184 189 185 - (** Helper codec for optional string that handles both absent members and null values *) 186 - let string_option = Jsont.option Jsont.string 190 + (** Helper codec for optional string that handles both absent members and null 191 + values *) 192 + let string_option = Json.Codec.option Json.Codec.string 187 193 188 194 let link_content_jsont = 189 195 let make url title description image_url image_asset_id screenshot_asset_id 190 196 full_page_archive_asset_id precrawled_archive_asset_id video_asset_id 191 - favicon html_content crawled_at author publisher date_published date_modified = 192 - { url; title; description; image_url; image_asset_id; screenshot_asset_id; 193 - full_page_archive_asset_id; precrawled_archive_asset_id; video_asset_id; 194 - favicon; html_content; crawled_at; author; publisher; date_published; date_modified } 197 + favicon html_content crawled_at author publisher date_published 198 + date_modified = 199 + { 200 + url; 201 + title; 202 + description; 203 + image_url; 204 + image_asset_id; 205 + screenshot_asset_id; 206 + full_page_archive_asset_id; 207 + precrawled_archive_asset_id; 208 + video_asset_id; 209 + favicon; 210 + html_content; 211 + crawled_at; 212 + author; 213 + publisher; 214 + date_published; 215 + date_modified; 216 + } 195 217 in 196 - Jsont.Object.map ~kind:"link_content" make 197 - |> Jsont.Object.mem "url" Jsont.string ~enc:(fun l -> l.url) 198 - |> Jsont.Object.mem "title" string_option ~dec_absent:None ~enc:(fun l -> l.title) 199 - |> Jsont.Object.mem "description" string_option ~dec_absent:None ~enc:(fun l -> l.description) 200 - |> Jsont.Object.mem "imageUrl" string_option ~dec_absent:None ~enc:(fun l -> l.image_url) 201 - |> Jsont.Object.mem "imageAssetId" string_option ~dec_absent:None ~enc:(fun l -> l.image_asset_id) 202 - |> Jsont.Object.mem "screenshotAssetId" string_option ~dec_absent:None ~enc:(fun l -> l.screenshot_asset_id) 203 - |> Jsont.Object.mem "fullPageArchiveAssetId" string_option ~dec_absent:None ~enc:(fun l -> l.full_page_archive_asset_id) 204 - |> Jsont.Object.mem "precrawledArchiveAssetId" string_option ~dec_absent:None ~enc:(fun l -> l.precrawled_archive_asset_id) 205 - |> Jsont.Object.mem "videoAssetId" string_option ~dec_absent:None ~enc:(fun l -> l.video_asset_id) 206 - |> Jsont.Object.mem "favicon" string_option ~dec_absent:None ~enc:(fun l -> l.favicon) 207 - |> Jsont.Object.mem "htmlContent" string_option ~dec_absent:None ~enc:(fun l -> l.html_content) 208 - |> Jsont.Object.mem "crawledAt" ptime_option_jsont ~dec_absent:None ~enc:(fun l -> l.crawled_at) 209 - |> Jsont.Object.mem "author" string_option ~dec_absent:None ~enc:(fun l -> l.author) 210 - |> Jsont.Object.mem "publisher" string_option ~dec_absent:None ~enc:(fun l -> l.publisher) 211 - |> Jsont.Object.mem "datePublished" ptime_option_jsont ~dec_absent:None ~enc:(fun l -> l.date_published) 212 - |> Jsont.Object.mem "dateModified" ptime_option_jsont ~dec_absent:None ~enc:(fun l -> l.date_modified) 213 - |> Jsont.Object.finish 218 + Json.Codec.Object.map ~kind:"link_content" make 219 + |> Json.Codec.Object.member "url" Json.Codec.string ~enc:(fun l -> l.url) 220 + |> Json.Codec.Object.member "title" string_option ~dec_absent:None 221 + ~enc:(fun l -> l.title) 222 + |> Json.Codec.Object.member "description" string_option ~dec_absent:None 223 + ~enc:(fun l -> l.description) 224 + |> Json.Codec.Object.member "imageUrl" string_option ~dec_absent:None 225 + ~enc:(fun l -> l.image_url) 226 + |> Json.Codec.Object.member "imageAssetId" string_option ~dec_absent:None 227 + ~enc:(fun l -> l.image_asset_id) 228 + |> Json.Codec.Object.member "screenshotAssetId" string_option ~dec_absent:None 229 + ~enc:(fun l -> l.screenshot_asset_id) 230 + |> Json.Codec.Object.member "fullPageArchiveAssetId" string_option 231 + ~dec_absent:None ~enc:(fun l -> l.full_page_archive_asset_id) 232 + |> Json.Codec.Object.member "precrawledArchiveAssetId" string_option 233 + ~dec_absent:None ~enc:(fun l -> l.precrawled_archive_asset_id) 234 + |> Json.Codec.Object.member "videoAssetId" string_option ~dec_absent:None 235 + ~enc:(fun l -> l.video_asset_id) 236 + |> Json.Codec.Object.member "favicon" string_option ~dec_absent:None 237 + ~enc:(fun l -> l.favicon) 238 + |> Json.Codec.Object.member "htmlContent" string_option ~dec_absent:None 239 + ~enc:(fun l -> l.html_content) 240 + |> Json.Codec.Object.member "crawledAt" ptime_option_jsont ~dec_absent:None 241 + ~enc:(fun l -> l.crawled_at) 242 + |> Json.Codec.Object.member "author" string_option ~dec_absent:None 243 + ~enc:(fun l -> l.author) 244 + |> Json.Codec.Object.member "publisher" string_option ~dec_absent:None 245 + ~enc:(fun l -> l.publisher) 246 + |> Json.Codec.Object.member "datePublished" ptime_option_jsont 247 + ~dec_absent:None ~enc:(fun l -> l.date_published) 248 + |> Json.Codec.Object.member "dateModified" ptime_option_jsont ~dec_absent:None 249 + ~enc:(fun l -> l.date_modified) 250 + |> Json.Codec.Object.seal 214 251 215 - type text_content = { 216 - text : string; 217 - source_url : string option; 218 - } 252 + type text_content = { text : string; source_url : string option } 219 253 220 254 let text_content_jsont = 221 255 let make text source_url = { text; source_url } in 222 - Jsont.Object.map ~kind:"text_content" make 223 - |> Jsont.Object.mem "text" Jsont.string ~enc:(fun t -> t.text) 224 - |> Jsont.Object.mem "sourceUrl" string_option ~dec_absent:None ~enc:(fun t -> t.source_url) 225 - |> Jsont.Object.finish 256 + Json.Codec.Object.map ~kind:"text_content" make 257 + |> Json.Codec.Object.member "text" Json.Codec.string ~enc:(fun t -> t.text) 258 + |> Json.Codec.Object.member "sourceUrl" string_option ~dec_absent:None 259 + ~enc:(fun t -> t.source_url) 260 + |> Json.Codec.Object.seal 226 261 227 262 type asset_content = { 228 263 asset_type : [ `Image | `PDF ]; ··· 240 275 | "pdf" -> Ok `PDF 241 276 | _ -> Ok `Image 242 277 in 243 - let enc = function `Image -> "image" | `PDF -> "pdf" 244 - in 245 - Jsont.of_of_string ~kind:"asset_content_type" dec ~enc 278 + let enc = function `Image -> "image" | `PDF -> "pdf" in 279 + Json.Codec.of_of_string ~kind:"asset_content_type" dec ~enc 246 280 247 - let int_option = Jsont.option Jsont.int 281 + let int_option = Json.Codec.option Json.Codec.int 248 282 249 283 let asset_content_jsont = 250 284 let make asset_type asset_id file_name source_url size content = 251 285 { asset_type; asset_id; file_name; source_url; size; content } 252 286 in 253 - Jsont.Object.map ~kind:"asset_content" make 254 - |> Jsont.Object.mem "assetType" asset_content_type_jsont ~enc:(fun a -> a.asset_type) 255 - |> Jsont.Object.mem "assetId" Jsont.string ~enc:(fun a -> a.asset_id) 256 - |> Jsont.Object.mem "fileName" string_option ~dec_absent:None ~enc:(fun a -> a.file_name) 257 - |> Jsont.Object.mem "sourceUrl" string_option ~dec_absent:None ~enc:(fun a -> a.source_url) 258 - |> Jsont.Object.mem "size" int_option ~dec_absent:None ~enc:(fun a -> a.size) 259 - |> Jsont.Object.mem "content" string_option ~dec_absent:None ~enc:(fun a -> a.content) 260 - |> Jsont.Object.finish 287 + Json.Codec.Object.map ~kind:"asset_content" make 288 + |> Json.Codec.Object.member "assetType" asset_content_type_jsont 289 + ~enc:(fun a -> a.asset_type) 290 + |> Json.Codec.Object.member "assetId" Json.Codec.string ~enc:(fun a -> 291 + a.asset_id) 292 + |> Json.Codec.Object.member "fileName" string_option ~dec_absent:None 293 + ~enc:(fun a -> a.file_name) 294 + |> Json.Codec.Object.member "sourceUrl" string_option ~dec_absent:None 295 + ~enc:(fun a -> a.source_url) 296 + |> Json.Codec.Object.member "size" int_option ~dec_absent:None ~enc:(fun a -> 297 + a.size) 298 + |> Json.Codec.Object.member "content" string_option ~dec_absent:None 299 + ~enc:(fun a -> a.content) 300 + |> Json.Codec.Object.seal 261 301 262 302 type content = 263 303 | Link of link_content ··· 267 307 268 308 (* Content is represented as an object with a "type" field discriminator *) 269 309 let content_jsont = 270 - let link_case = Jsont.Object.Case.map "link" link_content_jsont ~dec:(fun l -> Link l) in 271 - let text_case = Jsont.Object.Case.map "text" text_content_jsont ~dec:(fun t -> Text t) in 272 - let asset_case = Jsont.Object.Case.map "asset" asset_content_jsont ~dec:(fun a -> Asset a) in 310 + let link_case = 311 + Json.Codec.Object.Case.map "link" link_content_jsont ~dec:(fun l -> Link l) 312 + in 313 + let text_case = 314 + Json.Codec.Object.Case.map "text" text_content_jsont ~dec:(fun t -> Text t) 315 + in 316 + let asset_case = 317 + Json.Codec.Object.Case.map "asset" asset_content_jsont ~dec:(fun a -> 318 + Asset a) 319 + in 273 320 let enc_case = function 274 - | Link l -> Jsont.Object.Case.value link_case l 275 - | Text t -> Jsont.Object.Case.value text_case t 276 - | Asset a -> Jsont.Object.Case.value asset_case a 277 - | Unknown -> Jsont.Object.Case.value link_case { url = ""; title = None; description = None; 278 - image_url = None; image_asset_id = None; screenshot_asset_id = None; 279 - full_page_archive_asset_id = None; precrawled_archive_asset_id = None; 280 - video_asset_id = None; favicon = None; html_content = None; crawled_at = None; 281 - author = None; publisher = None; date_published = None; date_modified = None } 321 + | Link l -> Json.Codec.Object.Case.value link_case l 322 + | Text t -> Json.Codec.Object.Case.value text_case t 323 + | Asset a -> Json.Codec.Object.Case.value asset_case a 324 + | Unknown -> 325 + Json.Codec.Object.Case.value link_case 326 + { 327 + url = ""; 328 + title = None; 329 + description = None; 330 + image_url = None; 331 + image_asset_id = None; 332 + screenshot_asset_id = None; 333 + full_page_archive_asset_id = None; 334 + precrawled_archive_asset_id = None; 335 + video_asset_id = None; 336 + favicon = None; 337 + html_content = None; 338 + crawled_at = None; 339 + author = None; 340 + publisher = None; 341 + date_published = None; 342 + date_modified = None; 343 + } 344 + in 345 + let cases = 346 + Json.Codec.Object.Case.[ make link_case; make text_case; make asset_case ] 282 347 in 283 - let cases = Jsont.Object.Case.[make link_case; make text_case; make asset_case] in 284 - Jsont.Object.map ~kind:"content" Fun.id 285 - |> Jsont.Object.case_mem "type" Jsont.string ~enc:Fun.id ~enc_case cases 286 - |> Jsont.Object.finish 348 + Json.Codec.Object.map ~kind:"content" Fun.id 349 + |> Json.Codec.Object.case_member "type" Json.Codec.string ~enc:Fun.id 350 + ~enc_case cases 351 + |> Json.Codec.Object.seal 287 352 288 353 let title content = 289 354 match content with 290 355 | Link l -> Option.value l.title ~default:l.url 291 356 | Text t -> 292 - let excerpt = if String.length t.text > 50 then String.sub t.text 0 50 ^ "..." else t.text in 357 + let excerpt = 358 + if String.length t.text > 50 then String.sub t.text 0 50 ^ "..." 359 + else t.text 360 + in 293 361 excerpt 294 362 | Asset a -> Option.value a.file_name ~default:"Asset" 295 363 | Unknown -> "Unknown content" 296 364 297 365 (** {1 Resource Types} *) 298 366 299 - type asset = { 300 - id : asset_id; 301 - asset_type : asset_type; 302 - } 367 + type asset = { id : asset_id; asset_type : asset_type } 303 368 304 369 let asset_jsont = 305 370 let make id asset_type = { id; asset_type } in 306 - Jsont.Object.map ~kind:"asset" make 307 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun a -> a.id) 308 - |> Jsont.Object.mem "assetType" asset_type_jsont ~enc:(fun a -> a.asset_type) 309 - |> Jsont.Object.finish 371 + Json.Codec.Object.map ~kind:"asset" make 372 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun a -> a.id) 373 + |> Json.Codec.Object.member "assetType" asset_type_jsont ~enc:(fun a -> 374 + a.asset_type) 375 + |> Json.Codec.Object.seal 310 376 311 377 type bookmark_tag = { 312 378 id : tag_id; ··· 316 382 317 383 let bookmark_tag_jsont = 318 384 let make id name attached_by = { id; name; attached_by } in 319 - Jsont.Object.map ~kind:"bookmark_tag" make 320 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun t -> t.id) 321 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.name) 322 - |> Jsont.Object.mem "attachedBy" tag_attachment_type_jsont ~enc:(fun t -> t.attached_by) 323 - |> Jsont.Object.finish 385 + Json.Codec.Object.map ~kind:"bookmark_tag" make 386 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun t -> t.id) 387 + |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun t -> t.name) 388 + |> Json.Codec.Object.member "attachedBy" tag_attachment_type_jsont 389 + ~enc:(fun t -> t.attached_by) 390 + |> Json.Codec.Object.seal 324 391 325 392 type bookmark = { 326 393 id : bookmark_id; ··· 338 405 } 339 406 340 407 let bookmark_jsont = 341 - let make id created_at modified_at title archived favourited tagging_status note summary tags content assets = 342 - { id; created_at; modified_at; title; archived; favourited; tagging_status; note; summary; tags; content; assets } 408 + let make id created_at modified_at title archived favourited tagging_status 409 + note summary tags content assets = 410 + { 411 + id; 412 + created_at; 413 + modified_at; 414 + title; 415 + archived; 416 + favourited; 417 + tagging_status; 418 + note; 419 + summary; 420 + tags; 421 + content; 422 + assets; 423 + } 343 424 in 344 - Jsont.Object.map ~kind:"bookmark" make 345 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun b -> b.id) 346 - |> Jsont.Object.mem "createdAt" ptime_jsont ~enc:(fun b -> b.created_at) 347 - |> Jsont.Object.mem "modifiedAt" ptime_option_jsont ~dec_absent:None ~enc:(fun b -> b.modified_at) 348 - |> Jsont.Object.mem "title" (Jsont.option Jsont.string) ~dec_absent:None ~enc:(fun b -> b.title) 349 - |> Jsont.Object.mem "archived" Jsont.bool ~dec_absent:false ~enc:(fun b -> b.archived) 350 - |> Jsont.Object.mem "favourited" Jsont.bool ~dec_absent:false ~enc:(fun b -> b.favourited) 351 - |> Jsont.Object.mem "taggingStatus" (Jsont.option tagging_status_jsont) ~dec_absent:None ~enc:(fun b -> b.tagging_status) 352 - |> Jsont.Object.mem "note" (Jsont.option Jsont.string) ~dec_absent:None ~enc:(fun b -> b.note) 353 - |> Jsont.Object.mem "summary" (Jsont.option Jsont.string) ~dec_absent:None ~enc:(fun b -> b.summary) 354 - |> Jsont.Object.mem "tags" (Jsont.list bookmark_tag_jsont) ~dec_absent:[] ~enc:(fun b -> b.tags) 355 - |> Jsont.Object.mem "content" content_jsont ~enc:(fun b -> b.content) 356 - |> Jsont.Object.mem "assets" (Jsont.list asset_jsont) ~dec_absent:[] ~enc:(fun b -> b.assets) 357 - |> Jsont.Object.finish 425 + Json.Codec.Object.map ~kind:"bookmark" make 426 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun b -> b.id) 427 + |> Json.Codec.Object.member "createdAt" ptime_jsont ~enc:(fun b -> 428 + b.created_at) 429 + |> Json.Codec.Object.member "modifiedAt" ptime_option_jsont ~dec_absent:None 430 + ~enc:(fun b -> b.modified_at) 431 + |> Json.Codec.Object.member "title" (Json.Codec.option Json.Codec.string) 432 + ~dec_absent:None ~enc:(fun b -> b.title) 433 + |> Json.Codec.Object.member "archived" Json.Codec.bool ~dec_absent:false 434 + ~enc:(fun b -> b.archived) 435 + |> Json.Codec.Object.member "favourited" Json.Codec.bool ~dec_absent:false 436 + ~enc:(fun b -> b.favourited) 437 + |> Json.Codec.Object.member "taggingStatus" 438 + (Json.Codec.option tagging_status_jsont) ~dec_absent:None ~enc:(fun b -> 439 + b.tagging_status) 440 + |> Json.Codec.Object.member "note" (Json.Codec.option Json.Codec.string) 441 + ~dec_absent:None ~enc:(fun b -> b.note) 442 + |> Json.Codec.Object.member "summary" (Json.Codec.option Json.Codec.string) 443 + ~dec_absent:None ~enc:(fun b -> b.summary) 444 + |> Json.Codec.Object.member "tags" (Json.Codec.list bookmark_tag_jsont) 445 + ~dec_absent:[] ~enc:(fun b -> b.tags) 446 + |> Json.Codec.Object.member "content" content_jsont ~enc:(fun b -> b.content) 447 + |> Json.Codec.Object.member "assets" (Json.Codec.list asset_jsont) 448 + ~dec_absent:[] ~enc:(fun b -> b.assets) 449 + |> Json.Codec.Object.seal 358 450 359 451 let bookmark_title bookmark = 360 - match bookmark.title with 361 - | Some t -> t 362 - | None -> title bookmark.content 452 + match bookmark.title with Some t -> t | None -> title bookmark.content 363 453 364 454 (** {1 Paginated Responses} *) 365 455 ··· 370 460 371 461 let paginated_bookmarks_jsont = 372 462 let make bookmarks next_cursor = { bookmarks; next_cursor } in 373 - Jsont.Object.map ~kind:"paginated_bookmarks" make 374 - |> Jsont.Object.mem "bookmarks" (Jsont.list bookmark_jsont) ~dec_absent:[] ~enc:(fun p -> p.bookmarks) 375 - |> Jsont.Object.mem "nextCursor" string_option ~dec_absent:None ~enc:(fun p -> p.next_cursor) 376 - |> Jsont.Object.finish 463 + Json.Codec.Object.map ~kind:"paginated_bookmarks" make 464 + |> Json.Codec.Object.member "bookmarks" (Json.Codec.list bookmark_jsont) 465 + ~dec_absent:[] ~enc:(fun p -> p.bookmarks) 466 + |> Json.Codec.Object.member "nextCursor" string_option ~dec_absent:None 467 + ~enc:(fun p -> p.next_cursor) 468 + |> Json.Codec.Object.seal 377 469 378 470 (** {1 List Type} *) 379 471 ··· 391 483 let make id name description icon parent_id list_type query = 392 484 { id; name; description; icon; parent_id; list_type; query } 393 485 in 394 - Jsont.Object.map ~kind:"list" make 395 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun l -> l.id) 396 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun l -> l.name) 397 - |> Jsont.Object.mem "description" string_option ~dec_absent:None ~enc:(fun l -> l.description) 398 - |> Jsont.Object.mem "icon" Jsont.string ~dec_absent:"" ~enc:(fun l -> l.icon) 399 - |> Jsont.Object.mem "parentId" string_option ~dec_absent:None ~enc:(fun l -> l.parent_id) 400 - |> Jsont.Object.mem "type" list_type_jsont ~dec_absent:Manual ~enc:(fun l -> l.list_type) 401 - |> Jsont.Object.mem "query" string_option ~dec_absent:None ~enc:(fun l -> l.query) 402 - |> Jsont.Object.finish 486 + Json.Codec.Object.map ~kind:"list" make 487 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun l -> l.id) 488 + |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun l -> l.name) 489 + |> Json.Codec.Object.member "description" string_option ~dec_absent:None 490 + ~enc:(fun l -> l.description) 491 + |> Json.Codec.Object.member "icon" Json.Codec.string ~dec_absent:"" 492 + ~enc:(fun l -> l.icon) 493 + |> Json.Codec.Object.member "parentId" string_option ~dec_absent:None 494 + ~enc:(fun l -> l.parent_id) 495 + |> Json.Codec.Object.member "type" list_type_jsont ~dec_absent:Manual 496 + ~enc:(fun l -> l.list_type) 497 + |> Json.Codec.Object.member "query" string_option ~dec_absent:None 498 + ~enc:(fun l -> l.query) 499 + |> Json.Codec.Object.seal 403 500 404 501 type lists_response = { lists : _list list } 405 502 406 503 let lists_response_jsont = 407 504 let make lists = { lists } in 408 - Jsont.Object.map ~kind:"lists_response" make 409 - |> Jsont.Object.mem "lists" (Jsont.list list_jsont) ~dec_absent:[] ~enc:(fun r -> r.lists) 410 - |> Jsont.Object.finish 505 + Json.Codec.Object.map ~kind:"lists_response" make 506 + |> Json.Codec.Object.member "lists" (Json.Codec.list list_jsont) 507 + ~dec_absent:[] ~enc:(fun r -> r.lists) 508 + |> Json.Codec.Object.seal 411 509 412 510 (** {1 Tag Types} *) 413 511 ··· 428 526 in 429 527 let enc_ai lst = List.assoc_opt AI lst |> Option.value ~default:0 in 430 528 let enc_human lst = List.assoc_opt Human lst |> Option.value ~default:0 in 431 - Jsont.Object.map ~kind:"num_bookmarks_by_type" make 432 - |> Jsont.Object.mem "ai" Jsont.int ~dec_absent:0 ~enc:enc_ai 433 - |> Jsont.Object.mem "human" Jsont.int ~dec_absent:0 ~enc:enc_human 434 - |> Jsont.Object.finish 529 + Json.Codec.Object.map ~kind:"num_bookmarks_by_type" make 530 + |> Json.Codec.Object.member "ai" Json.Codec.int ~dec_absent:0 ~enc:enc_ai 531 + |> Json.Codec.Object.member "human" Json.Codec.int ~dec_absent:0 532 + ~enc:enc_human 533 + |> Json.Codec.Object.seal 435 534 436 535 let tag_jsont = 437 536 let make id name num_bookmarks num_bookmarks_by_attached_type = 438 537 { id; name; num_bookmarks; num_bookmarks_by_attached_type } 439 538 in 440 - Jsont.Object.map ~kind:"tag" make 441 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun t -> t.id) 442 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.name) 443 - |> Jsont.Object.mem "numBookmarks" Jsont.int ~dec_absent:0 ~enc:(fun t -> t.num_bookmarks) 444 - |> Jsont.Object.mem "numBookmarksByAttachedType" num_bookmarks_by_type_jsont 445 - ~dec_absent:[] ~enc:(fun t -> t.num_bookmarks_by_attached_type) 446 - |> Jsont.Object.finish 539 + Json.Codec.Object.map ~kind:"tag" make 540 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun t -> t.id) 541 + |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun t -> t.name) 542 + |> Json.Codec.Object.member "numBookmarks" Json.Codec.int ~dec_absent:0 543 + ~enc:(fun t -> t.num_bookmarks) 544 + |> Json.Codec.Object.member "numBookmarksByAttachedType" 545 + num_bookmarks_by_type_jsont ~dec_absent:[] ~enc:(fun t -> 546 + t.num_bookmarks_by_attached_type) 547 + |> Json.Codec.Object.seal 447 548 448 549 type tags_response = { tags : tag list } 449 550 450 551 let tags_response_jsont = 451 552 let make tags = { tags } in 452 - Jsont.Object.map ~kind:"tags_response" make 453 - |> Jsont.Object.mem "tags" (Jsont.list tag_jsont) ~dec_absent:[] ~enc:(fun r -> r.tags) 454 - |> Jsont.Object.finish 553 + Json.Codec.Object.map ~kind:"tags_response" make 554 + |> Json.Codec.Object.member "tags" (Json.Codec.list tag_jsont) ~dec_absent:[] 555 + ~enc:(fun r -> r.tags) 556 + |> Json.Codec.Object.seal 455 557 456 558 (** {1 Highlight Types} *) 457 559 ··· 468 570 } 469 571 470 572 let highlight_jsont = 471 - let make bookmark_id start_offset end_offset color text note id user_id created_at = 472 - { bookmark_id; start_offset; end_offset; color; text; note; id; user_id; created_at } 573 + let make bookmark_id start_offset end_offset color text note id user_id 574 + created_at = 575 + { 576 + bookmark_id; 577 + start_offset; 578 + end_offset; 579 + color; 580 + text; 581 + note; 582 + id; 583 + user_id; 584 + created_at; 585 + } 473 586 in 474 - Jsont.Object.map ~kind:"highlight" make 475 - |> Jsont.Object.mem "bookmarkId" Jsont.string ~enc:(fun h -> h.bookmark_id) 476 - |> Jsont.Object.mem "startOffset" Jsont.int ~enc:(fun h -> h.start_offset) 477 - |> Jsont.Object.mem "endOffset" Jsont.int ~enc:(fun h -> h.end_offset) 478 - |> Jsont.Object.mem "color" highlight_color_jsont ~dec_absent:Yellow ~enc:(fun h -> h.color) 479 - |> Jsont.Object.mem "text" string_option ~dec_absent:None ~enc:(fun h -> h.text) 480 - |> Jsont.Object.mem "note" string_option ~dec_absent:None ~enc:(fun h -> h.note) 481 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun h -> h.id) 482 - |> Jsont.Object.mem "userId" Jsont.string ~enc:(fun h -> h.user_id) 483 - |> Jsont.Object.mem "createdAt" ptime_jsont ~enc:(fun h -> h.created_at) 484 - |> Jsont.Object.finish 587 + Json.Codec.Object.map ~kind:"highlight" make 588 + |> Json.Codec.Object.member "bookmarkId" Json.Codec.string ~enc:(fun h -> 589 + h.bookmark_id) 590 + |> Json.Codec.Object.member "startOffset" Json.Codec.int ~enc:(fun h -> 591 + h.start_offset) 592 + |> Json.Codec.Object.member "endOffset" Json.Codec.int ~enc:(fun h -> 593 + h.end_offset) 594 + |> Json.Codec.Object.member "color" highlight_color_jsont ~dec_absent:Yellow 595 + ~enc:(fun h -> h.color) 596 + |> Json.Codec.Object.member "text" string_option ~dec_absent:None 597 + ~enc:(fun h -> h.text) 598 + |> Json.Codec.Object.member "note" string_option ~dec_absent:None 599 + ~enc:(fun h -> h.note) 600 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun h -> h.id) 601 + |> Json.Codec.Object.member "userId" Json.Codec.string ~enc:(fun h -> 602 + h.user_id) 603 + |> Json.Codec.Object.member "createdAt" ptime_jsont ~enc:(fun h -> 604 + h.created_at) 605 + |> Json.Codec.Object.seal 485 606 486 607 type paginated_highlights = { 487 608 highlights : highlight list; ··· 490 611 491 612 let paginated_highlights_jsont = 492 613 let make highlights next_cursor = { highlights; next_cursor } in 493 - Jsont.Object.map ~kind:"paginated_highlights" make 494 - |> Jsont.Object.mem "highlights" (Jsont.list highlight_jsont) ~dec_absent:[] ~enc:(fun p -> p.highlights) 495 - |> Jsont.Object.mem "nextCursor" string_option ~dec_absent:None ~enc:(fun p -> p.next_cursor) 496 - |> Jsont.Object.finish 614 + Json.Codec.Object.map ~kind:"paginated_highlights" make 615 + |> Json.Codec.Object.member "highlights" (Json.Codec.list highlight_jsont) 616 + ~dec_absent:[] ~enc:(fun p -> p.highlights) 617 + |> Json.Codec.Object.member "nextCursor" string_option ~dec_absent:None 618 + ~enc:(fun p -> p.next_cursor) 619 + |> Json.Codec.Object.seal 497 620 498 621 type highlights_response = { highlights : highlight list } 499 622 500 623 let highlights_response_jsont = 501 624 let make highlights = { highlights } in 502 - Jsont.Object.map ~kind:"highlights_response" make 503 - |> Jsont.Object.mem "highlights" (Jsont.list highlight_jsont) ~dec_absent:[] ~enc:(fun r -> r.highlights) 504 - |> Jsont.Object.finish 625 + Json.Codec.Object.map ~kind:"highlights_response" make 626 + |> Json.Codec.Object.member "highlights" (Json.Codec.list highlight_jsont) 627 + ~dec_absent:[] ~enc:(fun r -> r.highlights) 628 + |> Json.Codec.Object.seal 505 629 506 630 (** {1 User Types} *) 507 631 508 - type user_info = { 509 - id : string; 510 - name : string option; 511 - email : string option; 512 - } 632 + type user_info = { id : string; name : string option; email : string option } 513 633 514 634 let user_info_jsont = 515 635 let make id name email = { id; name; email } in 516 - Jsont.Object.map ~kind:"user_info" make 517 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun u -> u.id) 518 - |> Jsont.Object.mem "name" string_option ~dec_absent:None ~enc:(fun u -> u.name) 519 - |> Jsont.Object.mem "email" string_option ~dec_absent:None ~enc:(fun u -> u.email) 520 - |> Jsont.Object.finish 636 + Json.Codec.Object.map ~kind:"user_info" make 637 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun u -> u.id) 638 + |> Json.Codec.Object.member "name" string_option ~dec_absent:None 639 + ~enc:(fun u -> u.name) 640 + |> Json.Codec.Object.member "email" string_option ~dec_absent:None 641 + ~enc:(fun u -> u.email) 642 + |> Json.Codec.Object.seal 521 643 522 644 type user_stats = { 523 645 num_bookmarks : int; ··· 529 651 } 530 652 531 653 let user_stats_jsont = 532 - let make num_bookmarks num_favorites num_archived num_tags num_lists num_highlights = 533 - { num_bookmarks; num_favorites; num_archived; num_tags; num_lists; num_highlights } 654 + let make num_bookmarks num_favorites num_archived num_tags num_lists 655 + num_highlights = 656 + { 657 + num_bookmarks; 658 + num_favorites; 659 + num_archived; 660 + num_tags; 661 + num_lists; 662 + num_highlights; 663 + } 534 664 in 535 - Jsont.Object.map ~kind:"user_stats" make 536 - |> Jsont.Object.mem "numBookmarks" Jsont.int ~dec_absent:0 ~enc:(fun s -> s.num_bookmarks) 537 - |> Jsont.Object.mem "numFavourites" Jsont.int ~dec_absent:0 ~enc:(fun s -> s.num_favorites) 538 - |> Jsont.Object.mem "numArchived" Jsont.int ~dec_absent:0 ~enc:(fun s -> s.num_archived) 539 - |> Jsont.Object.mem "numTags" Jsont.int ~dec_absent:0 ~enc:(fun s -> s.num_tags) 540 - |> Jsont.Object.mem "numLists" Jsont.int ~dec_absent:0 ~enc:(fun s -> s.num_lists) 541 - |> Jsont.Object.mem "numHighlights" Jsont.int ~dec_absent:0 ~enc:(fun s -> s.num_highlights) 542 - |> Jsont.Object.finish 665 + Json.Codec.Object.map ~kind:"user_stats" make 666 + |> Json.Codec.Object.member "numBookmarks" Json.Codec.int ~dec_absent:0 667 + ~enc:(fun s -> s.num_bookmarks) 668 + |> Json.Codec.Object.member "numFavourites" Json.Codec.int ~dec_absent:0 669 + ~enc:(fun s -> s.num_favorites) 670 + |> Json.Codec.Object.member "numArchived" Json.Codec.int ~dec_absent:0 671 + ~enc:(fun s -> s.num_archived) 672 + |> Json.Codec.Object.member "numTags" Json.Codec.int ~dec_absent:0 673 + ~enc:(fun s -> s.num_tags) 674 + |> Json.Codec.Object.member "numLists" Json.Codec.int ~dec_absent:0 675 + ~enc:(fun s -> s.num_lists) 676 + |> Json.Codec.Object.member "numHighlights" Json.Codec.int ~dec_absent:0 677 + ~enc:(fun s -> s.num_highlights) 678 + |> Json.Codec.Object.seal 543 679 544 680 (** {1 Error Response} *) 545 681 546 - type error_response = { 547 - code : string; 548 - message : string; 549 - } 682 + type error_response = { code : string; message : string } 550 683 551 684 let error_response_jsont = 552 685 let make code message = { code; message } in 553 - Jsont.Object.map ~kind:"error_response" make 554 - |> Jsont.Object.mem "code" Jsont.string ~dec_absent:"unknown" ~enc:(fun e -> e.code) 555 - |> Jsont.Object.mem "message" Jsont.string ~dec_absent:"Unknown error" ~enc:(fun e -> e.message) 556 - |> Jsont.Object.finish 686 + Json.Codec.Object.map ~kind:"error_response" make 687 + |> Json.Codec.Object.member "code" Json.Codec.string ~dec_absent:"unknown" 688 + ~enc:(fun e -> e.code) 689 + |> Json.Codec.Object.member "message" Json.Codec.string 690 + ~dec_absent:"Unknown error" ~enc:(fun e -> e.message) 691 + |> Json.Codec.Object.seal 557 692 558 693 (** {1 Request Types} *) 559 694 ··· 573 708 let make type_ url text title note summary archived favourited created_at = 574 709 { type_; url; text; title; note; summary; archived; favourited; created_at } 575 710 in 576 - Jsont.Object.map ~kind:"create_bookmark_request" make 577 - |> Jsont.Object.mem "type" Jsont.string ~enc:(fun r -> r.type_) 578 - |> Jsont.Object.opt_mem "url" Jsont.string ~enc:(fun r -> r.url) 579 - |> Jsont.Object.opt_mem "text" Jsont.string ~enc:(fun r -> r.text) 580 - |> Jsont.Object.opt_mem "title" Jsont.string ~enc:(fun r -> r.title) 581 - |> Jsont.Object.opt_mem "note" Jsont.string ~enc:(fun r -> r.note) 582 - |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:(fun r -> r.summary) 583 - |> Jsont.Object.opt_mem "archived" Jsont.bool ~enc:(fun r -> r.archived) 584 - |> Jsont.Object.opt_mem "favourited" Jsont.bool ~enc:(fun r -> r.favourited) 585 - |> Jsont.Object.opt_mem "createdAt" ptime_jsont ~enc:(fun r -> r.created_at) 586 - |> Jsont.Object.finish 711 + Json.Codec.Object.map ~kind:"create_bookmark_request" make 712 + |> Json.Codec.Object.member "type" Json.Codec.string ~enc:(fun r -> r.type_) 713 + |> Json.Codec.Object.opt_member "url" Json.Codec.string ~enc:(fun r -> r.url) 714 + |> Json.Codec.Object.opt_member "text" Json.Codec.string ~enc:(fun r -> 715 + r.text) 716 + |> Json.Codec.Object.opt_member "title" Json.Codec.string ~enc:(fun r -> 717 + r.title) 718 + |> Json.Codec.Object.opt_member "note" Json.Codec.string ~enc:(fun r -> 719 + r.note) 720 + |> Json.Codec.Object.opt_member "summary" Json.Codec.string ~enc:(fun r -> 721 + r.summary) 722 + |> Json.Codec.Object.opt_member "archived" Json.Codec.bool ~enc:(fun r -> 723 + r.archived) 724 + |> Json.Codec.Object.opt_member "favourited" Json.Codec.bool ~enc:(fun r -> 725 + r.favourited) 726 + |> Json.Codec.Object.opt_member "createdAt" ptime_jsont ~enc:(fun r -> 727 + r.created_at) 728 + |> Json.Codec.Object.seal 587 729 588 730 type update_bookmark_request = { 589 731 title : string option; ··· 597 739 let make title note summary archived favourited = 598 740 { title; note; summary; archived; favourited } 599 741 in 600 - Jsont.Object.map ~kind:"update_bookmark_request" make 601 - |> Jsont.Object.opt_mem "title" Jsont.string ~enc:(fun r -> r.title) 602 - |> Jsont.Object.opt_mem "note" Jsont.string ~enc:(fun r -> r.note) 603 - |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:(fun r -> r.summary) 604 - |> Jsont.Object.opt_mem "archived" Jsont.bool ~enc:(fun r -> r.archived) 605 - |> Jsont.Object.opt_mem "favourited" Jsont.bool ~enc:(fun r -> r.favourited) 606 - |> Jsont.Object.finish 742 + Json.Codec.Object.map ~kind:"update_bookmark_request" make 743 + |> Json.Codec.Object.opt_member "title" Json.Codec.string ~enc:(fun r -> 744 + r.title) 745 + |> Json.Codec.Object.opt_member "note" Json.Codec.string ~enc:(fun r -> 746 + r.note) 747 + |> Json.Codec.Object.opt_member "summary" Json.Codec.string ~enc:(fun r -> 748 + r.summary) 749 + |> Json.Codec.Object.opt_member "archived" Json.Codec.bool ~enc:(fun r -> 750 + r.archived) 751 + |> Json.Codec.Object.opt_member "favourited" Json.Codec.bool ~enc:(fun r -> 752 + r.favourited) 753 + |> Json.Codec.Object.seal 607 754 608 755 type tag_ref = TagId of tag_id | TagName of string 609 756 610 757 let tag_ref_jsont = 611 758 (* Each tag ref is an object with either tagId or tagName *) 612 759 let make tagid tagname = 613 - match tagid, tagname with 760 + match (tagid, tagname) with 614 761 | Some id, _ -> TagId id 615 762 | _, Some name -> TagName name 616 763 | None, None -> TagName "" 617 764 in 618 - Jsont.Object.map ~kind:"tag_ref" make 619 - |> Jsont.Object.opt_mem "tagId" Jsont.string ~enc:(function TagId id -> Some id | _ -> None) 620 - |> Jsont.Object.opt_mem "tagName" Jsont.string ~enc:(function TagName n -> Some n | _ -> None) 621 - |> Jsont.Object.finish 765 + Json.Codec.Object.map ~kind:"tag_ref" make 766 + |> Json.Codec.Object.opt_member "tagId" Json.Codec.string ~enc:(function 767 + | TagId id -> Some id 768 + | _ -> None) 769 + |> Json.Codec.Object.opt_member "tagName" Json.Codec.string ~enc:(function 770 + | TagName n -> Some n 771 + | _ -> None) 772 + |> Json.Codec.Object.seal 622 773 623 774 type attach_tags_request = { tags : tag_ref list } 624 775 625 776 let attach_tags_request_jsont = 626 777 let make tags = { tags } in 627 - Jsont.Object.map ~kind:"attach_tags_request" make 628 - |> Jsont.Object.mem "tags" (Jsont.list tag_ref_jsont) ~enc:(fun r -> r.tags) 629 - |> Jsont.Object.finish 778 + Json.Codec.Object.map ~kind:"attach_tags_request" make 779 + |> Json.Codec.Object.member "tags" (Json.Codec.list tag_ref_jsont) 780 + ~enc:(fun r -> r.tags) 781 + |> Json.Codec.Object.seal 630 782 631 783 type attach_tags_response = { attached : tag_id list } 632 784 633 785 let attach_tags_response_jsont = 634 786 let make attached = { attached } in 635 - Jsont.Object.map ~kind:"attach_tags_response" make 636 - |> Jsont.Object.mem "attached" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun r -> r.attached) 637 - |> Jsont.Object.finish 787 + Json.Codec.Object.map ~kind:"attach_tags_response" make 788 + |> Json.Codec.Object.member "attached" (Json.Codec.list Json.Codec.string) 789 + ~dec_absent:[] ~enc:(fun r -> r.attached) 790 + |> Json.Codec.Object.seal 638 791 639 792 type detach_tags_response = { detached : tag_id list } 640 793 641 794 let detach_tags_response_jsont = 642 795 let make detached = { detached } in 643 - Jsont.Object.map ~kind:"detach_tags_response" make 644 - |> Jsont.Object.mem "detached" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun r -> r.detached) 645 - |> Jsont.Object.finish 796 + Json.Codec.Object.map ~kind:"detach_tags_response" make 797 + |> Json.Codec.Object.member "detached" (Json.Codec.list Json.Codec.string) 798 + ~dec_absent:[] ~enc:(fun r -> r.detached) 799 + |> Json.Codec.Object.seal 646 800 647 801 type create_list_request = { 648 802 name : string; ··· 657 811 let make name icon description parent_id type_ query = 658 812 { name; icon; description; parent_id; type_; query } 659 813 in 660 - Jsont.Object.map ~kind:"create_list_request" make 661 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name) 662 - |> Jsont.Object.mem "icon" Jsont.string ~enc:(fun r -> r.icon) 663 - |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun r -> r.description) 664 - |> Jsont.Object.opt_mem "parentId" Jsont.string ~enc:(fun r -> r.parent_id) 665 - |> Jsont.Object.opt_mem "type" Jsont.string ~enc:(fun r -> r.type_) 666 - |> Jsont.Object.opt_mem "query" Jsont.string ~enc:(fun r -> r.query) 667 - |> Jsont.Object.finish 814 + Json.Codec.Object.map ~kind:"create_list_request" make 815 + |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun r -> r.name) 816 + |> Json.Codec.Object.member "icon" Json.Codec.string ~enc:(fun r -> r.icon) 817 + |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun r -> 818 + r.description) 819 + |> Json.Codec.Object.opt_member "parentId" Json.Codec.string ~enc:(fun r -> 820 + r.parent_id) 821 + |> Json.Codec.Object.opt_member "type" Json.Codec.string ~enc:(fun r -> 822 + r.type_) 823 + |> Json.Codec.Object.opt_member "query" Json.Codec.string ~enc:(fun r -> 824 + r.query) 825 + |> Json.Codec.Object.seal 668 826 669 827 type update_list_request = { 670 828 name : string option; ··· 679 837 (* parent_id here comes from opt_mem so it's already option *) 680 838 { name; icon; description; parent_id = Some parent_id; query } 681 839 in 682 - Jsont.Object.map ~kind:"update_list_request" make 683 - |> Jsont.Object.opt_mem "name" Jsont.string ~enc:(fun r -> r.name) 684 - |> Jsont.Object.opt_mem "icon" Jsont.string ~enc:(fun r -> r.icon) 685 - |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun r -> r.description) 686 - |> Jsont.Object.opt_mem "parentId" Jsont.string ~enc:(fun r -> Option.join r.parent_id) 687 - |> Jsont.Object.opt_mem "query" Jsont.string ~enc:(fun r -> r.query) 688 - |> Jsont.Object.finish 840 + Json.Codec.Object.map ~kind:"update_list_request" make 841 + |> Json.Codec.Object.opt_member "name" Json.Codec.string ~enc:(fun r -> 842 + r.name) 843 + |> Json.Codec.Object.opt_member "icon" Json.Codec.string ~enc:(fun r -> 844 + r.icon) 845 + |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun r -> 846 + r.description) 847 + |> Json.Codec.Object.opt_member "parentId" Json.Codec.string ~enc:(fun r -> 848 + Option.join r.parent_id) 849 + |> Json.Codec.Object.opt_member "query" Json.Codec.string ~enc:(fun r -> 850 + r.query) 851 + |> Json.Codec.Object.seal 689 852 690 853 type create_highlight_request = { 691 854 bookmark_id : bookmark_id; ··· 700 863 let make bookmark_id start_offset end_offset text note color = 701 864 { bookmark_id; start_offset; end_offset; text; note; color } 702 865 in 703 - Jsont.Object.map ~kind:"create_highlight_request" make 704 - |> Jsont.Object.mem "bookmarkId" Jsont.string ~enc:(fun r -> r.bookmark_id) 705 - |> Jsont.Object.mem "startOffset" Jsont.int ~enc:(fun r -> r.start_offset) 706 - |> Jsont.Object.mem "endOffset" Jsont.int ~enc:(fun r -> r.end_offset) 707 - |> Jsont.Object.mem "text" Jsont.string ~enc:(fun r -> r.text) 708 - |> Jsont.Object.opt_mem "note" Jsont.string ~enc:(fun r -> r.note) 709 - |> Jsont.Object.opt_mem "color" highlight_color_jsont ~enc:(fun r -> r.color) 710 - |> Jsont.Object.finish 866 + Json.Codec.Object.map ~kind:"create_highlight_request" make 867 + |> Json.Codec.Object.member "bookmarkId" Json.Codec.string ~enc:(fun r -> 868 + r.bookmark_id) 869 + |> Json.Codec.Object.member "startOffset" Json.Codec.int ~enc:(fun r -> 870 + r.start_offset) 871 + |> Json.Codec.Object.member "endOffset" Json.Codec.int ~enc:(fun r -> 872 + r.end_offset) 873 + |> Json.Codec.Object.member "text" Json.Codec.string ~enc:(fun r -> r.text) 874 + |> Json.Codec.Object.opt_member "note" Json.Codec.string ~enc:(fun r -> 875 + r.note) 876 + |> Json.Codec.Object.opt_member "color" highlight_color_jsont ~enc:(fun r -> 877 + r.color) 878 + |> Json.Codec.Object.seal 711 879 712 880 type update_highlight_request = { color : highlight_color option } 713 881 714 882 let update_highlight_request_jsont = 715 883 let make color = { color } in 716 - Jsont.Object.map ~kind:"update_highlight_request" make 717 - |> Jsont.Object.opt_mem "color" highlight_color_jsont ~enc:(fun r -> r.color) 718 - |> Jsont.Object.finish 884 + Json.Codec.Object.map ~kind:"update_highlight_request" make 885 + |> Json.Codec.Object.opt_member "color" highlight_color_jsont ~enc:(fun r -> 886 + r.color) 887 + |> Json.Codec.Object.seal 719 888 720 889 type update_tag_request = { name : string } 721 890 722 891 let update_tag_request_jsont = 723 892 let make name = { name } in 724 - Jsont.Object.map ~kind:"update_tag_request" make 725 - |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name) 726 - |> Jsont.Object.finish 893 + Json.Codec.Object.map ~kind:"update_tag_request" make 894 + |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun r -> r.name) 895 + |> Json.Codec.Object.seal 727 896 728 - type attach_asset_request = { 729 - id : asset_id; 730 - asset_type : asset_type; 731 - } 897 + type attach_asset_request = { id : asset_id; asset_type : asset_type } 732 898 733 899 let attach_asset_request_jsont = 734 900 let make id asset_type = { id; asset_type } in 735 - Jsont.Object.map ~kind:"attach_asset_request" make 736 - |> Jsont.Object.mem "id" Jsont.string ~enc:(fun r -> r.id) 737 - |> Jsont.Object.mem "assetType" asset_type_jsont ~enc:(fun r -> r.asset_type) 738 - |> Jsont.Object.finish 901 + Json.Codec.Object.map ~kind:"attach_asset_request" make 902 + |> Json.Codec.Object.member "id" Json.Codec.string ~enc:(fun r -> r.id) 903 + |> Json.Codec.Object.member "assetType" asset_type_jsont ~enc:(fun r -> 904 + r.asset_type) 905 + |> Json.Codec.Object.seal 739 906 740 907 type replace_asset_request = { asset_id : asset_id } 741 908 742 909 let replace_asset_request_jsont = 743 910 let make asset_id = { asset_id } in 744 - Jsont.Object.map ~kind:"replace_asset_request" make 745 - |> Jsont.Object.mem "assetId" Jsont.string ~enc:(fun r -> r.asset_id) 746 - |> Jsont.Object.finish 911 + Json.Codec.Object.map ~kind:"replace_asset_request" make 912 + |> Json.Codec.Object.member "assetId" Json.Codec.string ~enc:(fun r -> 913 + r.asset_id) 914 + |> Json.Codec.Object.seal 747 915 748 916 type summarize_response = { summary : string } 749 917 750 918 let summarize_response_jsont = 751 919 let make summary = { summary } in 752 - Jsont.Object.map ~kind:"summarize_response" make 753 - |> Jsont.Object.mem "summary" Jsont.string ~enc:(fun r -> r.summary) 754 - |> Jsont.Object.finish 920 + Json.Codec.Object.map ~kind:"summarize_response" make 921 + |> Json.Codec.Object.member "summary" Json.Codec.string ~enc:(fun r -> 922 + r.summary) 923 + |> Json.Codec.Object.seal
+49 -54
lib/proto/karakeep_proto.mli
··· 5 5 6 6 (** Karakeep API protocol types and JSON codecs 7 7 8 - This module provides type definitions and jsont codecs for the Karakeep 8 + This module provides type definitions and nox-json codecs for the Karakeep 9 9 bookmark service API protocol messages. *) 10 10 11 11 (** {1 ID Types} *) ··· 34 34 | Asset (** An attached asset (image, PDF, etc.) *) 35 35 | Unknown (** Unknown content type *) 36 36 37 - val bookmark_content_type_jsont : bookmark_content_type Jsont.t 37 + val bookmark_content_type_jsont : bookmark_content_type Json.codec 38 38 39 39 (** Type of asset *) 40 40 type asset_type = ··· 47 47 | PrecrawledArchive (** Pre-crawled archive *) 48 48 | Unknown (** Unknown asset type *) 49 49 50 - val asset_type_jsont : asset_type Jsont.t 50 + val asset_type_jsont : asset_type Json.codec 51 51 52 52 (** Type of tagging status *) 53 53 type tagging_status = ··· 55 55 | Failure (** Tagging failed *) 56 56 | Pending (** Tagging is pending *) 57 57 58 - val tagging_status_jsont : tagging_status Jsont.t 58 + val tagging_status_jsont : tagging_status Json.codec 59 59 val string_of_tagging_status : tagging_status -> string 60 60 61 61 (** Type of bookmark list *) ··· 63 63 | Manual (** List is manually managed *) 64 64 | Smart (** List is dynamically generated based on a query *) 65 65 66 - val list_type_jsont : list_type Jsont.t 66 + val list_type_jsont : list_type Json.codec 67 67 68 68 (** Highlight color *) 69 69 type highlight_color = ··· 72 72 | Green (** Green highlight *) 73 73 | Blue (** Blue highlight *) 74 74 75 - val highlight_color_jsont : highlight_color Jsont.t 75 + val highlight_color_jsont : highlight_color Json.codec 76 76 val string_of_highlight_color : highlight_color -> string 77 77 78 78 (** Type of how a tag was attached *) ··· 80 80 | AI (** Tag was attached by AI *) 81 81 | Human (** Tag was attached by a human *) 82 82 83 - val tag_attachment_type_jsont : tag_attachment_type Jsont.t 83 + val tag_attachment_type_jsont : tag_attachment_type Json.codec 84 84 val string_of_tag_attachment_type : tag_attachment_type -> string 85 85 86 86 (** {1 Content Types} *) ··· 107 107 } 108 108 (** Link content for a bookmark *) 109 109 110 - val link_content_jsont : link_content Jsont.t 110 + val link_content_jsont : link_content Json.codec 111 111 112 112 type text_content = { 113 113 text : string; (** The text content *) ··· 115 115 } 116 116 (** Text content for a bookmark *) 117 117 118 - val text_content_jsont : text_content Jsont.t 118 + val text_content_jsont : text_content Json.codec 119 119 120 120 type asset_content = { 121 121 asset_type : [ `Image | `PDF ]; (** Type of the asset *) ··· 127 127 } 128 128 (** Asset content for a bookmark *) 129 129 130 - val asset_content_jsont : asset_content Jsont.t 130 + val asset_content_jsont : asset_content Json.codec 131 131 132 132 (** Content of a bookmark *) 133 133 type content = ··· 136 136 | Asset of asset_content (** Asset-type content *) 137 137 | Unknown (** Unknown content type *) 138 138 139 - val content_jsont : content Jsont.t 139 + val content_jsont : content Json.codec 140 140 141 141 val title : content -> string 142 - (** [title content] extracts a meaningful title from the bookmark content. 143 - For Link content, it returns the title if available, otherwise the URL. 144 - For Text content, it returns a short excerpt from the text. 145 - For Asset content, it returns the filename if available, otherwise a 146 - generic title. 147 - For Unknown content, it returns a generic title. *) 142 + (** [title content] extracts a meaningful title from the bookmark content. For 143 + Link content, it returns the title if available, otherwise the URL. For Text 144 + content, it returns a short excerpt from the text. For Asset content, it 145 + returns the filename if available, otherwise a generic title. For Unknown 146 + content, it returns a generic title. *) 148 147 149 148 (** {1 Resource Types} *) 150 149 ··· 154 153 } 155 154 (** Asset attached to a bookmark *) 156 155 157 - val asset_jsont : asset Jsont.t 156 + val asset_jsont : asset Json.codec 158 157 159 158 type bookmark_tag = { 160 159 id : tag_id; (** ID of the tag *) ··· 163 162 } 164 163 (** Tag with attachment information *) 165 164 166 - val bookmark_tag_jsont : bookmark_tag Jsont.t 165 + val bookmark_tag_jsont : bookmark_tag Json.codec 167 166 168 167 type bookmark = { 169 168 id : bookmark_id; (** Unique identifier for the bookmark *) ··· 181 180 } 182 181 (** A bookmark from the Karakeep service *) 183 182 184 - val bookmark_jsont : bookmark Jsont.t 183 + val bookmark_jsont : bookmark Json.codec 185 184 186 185 val bookmark_title : bookmark -> string 187 186 (** [bookmark_title bookmark] returns the best available title for a bookmark. ··· 196 195 } 197 196 (** Paginated response of bookmarks *) 198 197 199 - val paginated_bookmarks_jsont : paginated_bookmarks Jsont.t 198 + val paginated_bookmarks_jsont : paginated_bookmarks Json.codec 200 199 201 200 (** {1 List Type} *) 202 201 ··· 211 210 } 212 211 (** List in Karakeep *) 213 212 214 - val list_jsont : _list Jsont.t 213 + val list_jsont : _list Json.codec 215 214 216 215 type lists_response = { lists : _list list } 217 216 (** Response containing a list of lists *) 218 217 219 - val lists_response_jsont : lists_response Jsont.t 218 + val lists_response_jsont : lists_response Json.codec 220 219 221 220 (** {1 Tag Types} *) 222 221 ··· 229 228 } 230 229 (** Tag in Karakeep *) 231 230 232 - val tag_jsont : tag Jsont.t 231 + val tag_jsont : tag Json.codec 233 232 234 233 type tags_response = { tags : tag list } 235 234 (** Response containing a list of tags *) 236 235 237 - val tags_response_jsont : tags_response Jsont.t 236 + val tags_response_jsont : tags_response Json.codec 238 237 239 238 (** {1 Highlight Types} *) 240 239 ··· 251 250 } 252 251 (** Highlight in Karakeep *) 253 252 254 - val highlight_jsont : highlight Jsont.t 253 + val highlight_jsont : highlight Json.codec 255 254 256 255 type paginated_highlights = { 257 256 highlights : highlight list; (** List of highlights in the current page *) ··· 259 258 } 260 259 (** Paginated response of highlights *) 261 260 262 - val paginated_highlights_jsont : paginated_highlights Jsont.t 261 + val paginated_highlights_jsont : paginated_highlights Json.codec 263 262 264 263 type highlights_response = { highlights : highlight list } 265 264 (** Response containing a list of highlights *) 266 265 267 - val highlights_response_jsont : highlights_response Jsont.t 266 + val highlights_response_jsont : highlights_response Json.codec 268 267 269 268 (** {1 User Types} *) 270 269 ··· 275 274 } 276 275 (** User information *) 277 276 278 - val user_info_jsont : user_info Jsont.t 277 + val user_info_jsont : user_info Json.codec 279 278 280 279 type user_stats = { 281 280 num_bookmarks : int; (** Number of bookmarks *) ··· 287 286 } 288 287 (** User statistics *) 289 288 290 - val user_stats_jsont : user_stats Jsont.t 289 + val user_stats_jsont : user_stats Json.codec 291 290 292 291 (** {1 Error Response} *) 293 292 ··· 297 296 } 298 297 (** Error response from the API *) 299 298 300 - val error_response_jsont : error_response Jsont.t 299 + val error_response_jsont : error_response Json.codec 301 300 302 301 (** {1 Request Types} *) 303 302 ··· 314 313 } 315 314 (** Request to create a bookmark *) 316 315 317 - val create_bookmark_request_jsont : create_bookmark_request Jsont.t 316 + val create_bookmark_request_jsont : create_bookmark_request Json.codec 318 317 319 318 type update_bookmark_request = { 320 319 title : string option; ··· 325 324 } 326 325 (** Request to update a bookmark *) 327 326 328 - val update_bookmark_request_jsont : update_bookmark_request Jsont.t 327 + val update_bookmark_request_jsont : update_bookmark_request Json.codec 329 328 330 - type tag_ref = 331 - | TagId of tag_id 332 - | TagName of string 329 + type tag_ref = TagId of tag_id | TagName of string 333 330 334 331 type attach_tags_request = { tags : tag_ref list } 335 332 (** Request to attach tags to a bookmark *) 336 333 337 - val attach_tags_request_jsont : attach_tags_request Jsont.t 334 + val attach_tags_request_jsont : attach_tags_request Json.codec 338 335 339 336 type attach_tags_response = { attached : tag_id list } 340 337 (** Response from attaching tags *) 341 338 342 - val attach_tags_response_jsont : attach_tags_response Jsont.t 339 + val attach_tags_response_jsont : attach_tags_response Json.codec 343 340 344 341 type detach_tags_response = { detached : tag_id list } 345 342 (** Response from detaching tags *) 346 343 347 - val detach_tags_response_jsont : detach_tags_response Jsont.t 344 + val detach_tags_response_jsont : detach_tags_response Json.codec 348 345 349 346 type create_list_request = { 350 347 name : string; ··· 356 353 } 357 354 (** Request to create a list *) 358 355 359 - val create_list_request_jsont : create_list_request Jsont.t 356 + val create_list_request_jsont : create_list_request Json.codec 360 357 361 358 type update_list_request = { 362 359 name : string option; 363 360 icon : string option; 364 361 description : string option; 365 - parent_id : list_id option option; (** None to not update, Some None to clear *) 362 + parent_id : list_id option option; 363 + (** None to not update, Some None to clear *) 366 364 query : string option; 367 365 } 368 366 (** Request to update a list *) 369 367 370 - val update_list_request_jsont : update_list_request Jsont.t 368 + val update_list_request_jsont : update_list_request Json.codec 371 369 372 370 type create_highlight_request = { 373 371 bookmark_id : bookmark_id; ··· 379 377 } 380 378 (** Request to create a highlight *) 381 379 382 - val create_highlight_request_jsont : create_highlight_request Jsont.t 380 + val create_highlight_request_jsont : create_highlight_request Json.codec 383 381 384 382 type update_highlight_request = { color : highlight_color option } 385 383 (** Request to update a highlight *) 386 384 387 - val update_highlight_request_jsont : update_highlight_request Jsont.t 385 + val update_highlight_request_jsont : update_highlight_request Json.codec 388 386 389 387 type update_tag_request = { name : string } 390 388 (** Request to update a tag *) 391 389 392 - val update_tag_request_jsont : update_tag_request Jsont.t 390 + val update_tag_request_jsont : update_tag_request Json.codec 393 391 394 - type attach_asset_request = { 395 - id : asset_id; 396 - asset_type : asset_type; 397 - } 392 + type attach_asset_request = { id : asset_id; asset_type : asset_type } 398 393 (** Request to attach an asset *) 399 394 400 - val attach_asset_request_jsont : attach_asset_request Jsont.t 395 + val attach_asset_request_jsont : attach_asset_request Json.codec 401 396 402 397 type replace_asset_request = { asset_id : asset_id } 403 398 (** Request to replace an asset *) 404 399 405 - val replace_asset_request_jsont : replace_asset_request Jsont.t 400 + val replace_asset_request_jsont : replace_asset_request Json.codec 406 401 407 402 type summarize_response = { summary : string } 408 403 (** Response from summarize bookmark endpoint *) 409 404 410 - val summarize_response_jsont : summarize_response Jsont.t 405 + val summarize_response_jsont : summarize_response Json.codec 411 406 412 407 (** {1 Helper Codecs} *) 413 408 414 - val ptime_jsont : Ptime.t Jsont.t 409 + val ptime_jsont : Ptime.t Json.codec 415 410 (** Codec for Ptime.t values (ISO 8601 format) *) 416 411 417 - val ptime_option_jsont : Ptime.t option Jsont.t 412 + val ptime_option_jsont : Ptime.t option Json.codec 418 413 (** Codec for optional Ptime.t values *)
+55 -56
test/asset_test.ml
··· 26 26 27 27 Eio_main.run @@ fun env -> 28 28 Eio.Switch.run @@ fun sw -> 29 - 30 29 let client = Karakeep.create ~sw ~env ~base_url ~api_key in 31 30 32 31 (* Test asset URL and optionally fetch asset *) 33 32 Printf.printf "Fetching bookmarks with assets...\n"; 34 33 35 - (try 36 - let response = fetch_bookmarks client ~limit:5 () in 37 - (* Find a bookmark with assets *) 38 - let bookmark_with_assets = 39 - List.find_opt (fun b -> List.length b.assets > 0) response.bookmarks 40 - in 34 + try 35 + let response = fetch_bookmarks client ~limit:5 () in 36 + (* Find a bookmark with assets *) 37 + let bookmark_with_assets = 38 + List.find_opt (fun b -> List.length b.assets > 0) response.bookmarks 39 + in 41 40 42 - match bookmark_with_assets with 43 - | None -> 44 - Printf.printf "No bookmarks with assets found in the first 5 results.\n" 45 - | Some bookmark -> ( 46 - (* Print assets info *) 47 - let bookmark_title_str = bookmark_title bookmark in 48 - let url = 49 - match bookmark.content with 50 - | Link lc -> lc.url 51 - | Text tc -> Option.value tc.source_url ~default:"(text content)" 52 - | Asset ac -> Option.value ac.source_url ~default:"(asset content)" 53 - | Unknown -> "(unknown content)" 54 - in 55 - Printf.printf "Found bookmark \"%s\" with %d assets: %s\n" 56 - bookmark_title_str 57 - (List.length bookmark.assets) 58 - url; 41 + match bookmark_with_assets with 42 + | None -> 43 + Printf.printf "No bookmarks with assets found in the first 5 results.\n" 44 + | Some bookmark -> ( 45 + (* Print assets info *) 46 + let bookmark_title_str = bookmark_title bookmark in 47 + let url = 48 + match bookmark.content with 49 + | Link lc -> lc.url 50 + | Text tc -> Option.value tc.source_url ~default:"(text content)" 51 + | Asset ac -> Option.value ac.source_url ~default:"(asset content)" 52 + | Unknown -> "(unknown content)" 53 + in 54 + Printf.printf "Found bookmark \"%s\" with %d assets: %s\n" 55 + bookmark_title_str 56 + (List.length bookmark.assets) 57 + url; 59 58 60 - List.iter 61 - (fun (asset : asset) -> 62 - let asset_type_str = 63 - match asset.asset_type with 64 - | Screenshot -> "screenshot" 65 - | AssetScreenshot -> "assetScreenshot" 66 - | BannerImage -> "bannerImage" 67 - | FullPageArchive -> "fullPageArchive" 68 - | Video -> "video" 69 - | BookmarkAsset -> "bookmarkAsset" 70 - | PrecrawledArchive -> "precrawledArchive" 71 - | Unknown -> "unknown" 72 - in 73 - Printf.printf "- Asset ID: %s, Type: %s\n" asset.id asset_type_str; 59 + List.iter 60 + (fun (asset : asset) -> 61 + let asset_type_str = 62 + match asset.asset_type with 63 + | Screenshot -> "screenshot" 64 + | AssetScreenshot -> "assetScreenshot" 65 + | BannerImage -> "bannerImage" 66 + | FullPageArchive -> "fullPageArchive" 67 + | Video -> "video" 68 + | BookmarkAsset -> "bookmarkAsset" 69 + | PrecrawledArchive -> "precrawledArchive" 70 + | Unknown -> "unknown" 71 + in 72 + Printf.printf "- Asset ID: %s, Type: %s\n" asset.id asset_type_str; 74 73 75 - (* Get asset URL *) 76 - let asset_url = get_asset_url client asset.id in 77 - Printf.printf " URL: %s\n" asset_url) 78 - bookmark.assets; 74 + (* Get asset URL *) 75 + let asset_url = get_asset_url client asset.id in 76 + Printf.printf " URL: %s\n" asset_url) 77 + bookmark.assets; 79 78 80 - (* Optionally fetch one asset to verify it works *) 81 - match bookmark.assets with 82 - | asset :: _ -> ( 83 - Printf.printf "\nFetching asset %s...\n" asset.id; 84 - try 85 - let data = fetch_asset client asset.id in 86 - Printf.printf "Successfully fetched asset. Size: %d bytes\n" 87 - (String.length data) 88 - with e -> 89 - Printf.printf "Error fetching asset: %s\n" (Printexc.to_string e)) 90 - | [] -> ()) 91 - with e -> 92 - Printf.printf "Error in asset test: %s\n" (Printexc.to_string e); 93 - Printf.printf "Skipping the asset test due to API error.\n") 79 + (* Optionally fetch one asset to verify it works *) 80 + match bookmark.assets with 81 + | asset :: _ -> ( 82 + Printf.printf "\nFetching asset %s...\n" asset.id; 83 + try 84 + let data = fetch_asset client asset.id in 85 + Printf.printf "Successfully fetched asset. Size: %d bytes\n" 86 + (String.length data) 87 + with e -> 88 + Printf.printf "Error fetching asset: %s\n" (Printexc.to_string e)) 89 + | [] -> ()) 90 + with e -> 91 + Printf.printf "Error in asset test: %s\n" (Printexc.to_string e); 92 + Printf.printf "Skipping the asset test due to API error.\n"
+20 -21
test/create_test.ml
··· 26 26 27 27 Eio_main.run @@ fun env -> 28 28 Eio.Switch.run @@ fun sw -> 29 - 30 29 let client = Karakeep.create ~sw ~env ~base_url ~api_key in 31 30 32 31 (* Test creating a new bookmark *) ··· 36 35 let title = "OCaml Programming Language" in 37 36 let tags = [ "programming"; "ocaml"; "functional" ] in 38 37 39 - (try 40 - let bookmark = create_bookmark client ~url ~title ~tags () in 41 - Printf.printf "Successfully created bookmark:\n"; 42 - Printf.printf "- ID: %s\n" bookmark.id; 43 - Printf.printf "- Title: %s\n" (bookmark_title bookmark); 38 + try 39 + let bookmark = create_bookmark client ~url ~title ~tags () in 40 + Printf.printf "Successfully created bookmark:\n"; 41 + Printf.printf "- ID: %s\n" bookmark.id; 42 + Printf.printf "- Title: %s\n" (bookmark_title bookmark); 44 43 45 - let url = 46 - match bookmark.content with 47 - | Link lc -> lc.url 48 - | Text tc -> Option.value tc.source_url ~default:"(text content)" 49 - | Asset ac -> Option.value ac.source_url ~default:"(asset content)" 50 - | Unknown -> "(unknown content)" 51 - in 52 - Printf.printf "- URL: %s\n" url; 53 - Printf.printf "- Created: %s\n" (Ptime.to_rfc3339 bookmark.created_at); 54 - Printf.printf "- Tags: %s\n" 55 - (String.concat ", " 56 - (List.map (fun (tag : bookmark_tag) -> tag.name) bookmark.tags)) 57 - with e -> 58 - Printf.printf "Error creating bookmark: %s\n" (Printexc.to_string e); 59 - Printf.printf "Skipping the creation test due to API error.\n") 44 + let url = 45 + match bookmark.content with 46 + | Link lc -> lc.url 47 + | Text tc -> Option.value tc.source_url ~default:"(text content)" 48 + | Asset ac -> Option.value ac.source_url ~default:"(asset content)" 49 + | Unknown -> "(unknown content)" 50 + in 51 + Printf.printf "- URL: %s\n" url; 52 + Printf.printf "- Created: %s\n" (Ptime.to_rfc3339 bookmark.created_at); 53 + Printf.printf "- Tags: %s\n" 54 + (String.concat ", " 55 + (List.map (fun (tag : bookmark_tag) -> tag.name) bookmark.tags)) 56 + with e -> 57 + Printf.printf "Error creating bookmark: %s\n" (Printexc.to_string e); 58 + Printf.printf "Skipping the creation test due to API error.\n"
+2 -14
test/dune
··· 1 - (executable 2 - (name test) 3 - (libraries karakeep eio_main)) 4 - 5 - (executable 6 - (name create_test) 7 - (libraries karakeep eio_main)) 8 - 9 - (executable 10 - (name asset_test) 11 - (libraries karakeep eio_main)) 12 - 13 - (executable 14 - (name search_test) 1 + (executables 2 + (names test create_test asset_test search_test) 15 3 (libraries karakeep eio_main))
+39 -33
test/search_test.ml
··· 43 43 44 44 Eio_main.run @@ fun env -> 45 45 Eio.Switch.run @@ fun sw -> 46 - 47 46 let client = Karakeep.create ~sw ~env ~base_url ~api_key in 48 47 49 48 Printf.printf "=== Test: search_bookmarks ===\n"; ··· 53 52 54 53 Printf.printf "Searching for bookmarks with query: \"%s\"\n\n" search_term; 55 54 56 - (try 57 - (* Search for bookmarks with the search term *) 58 - let search_results = search_bookmarks client ~query:search_term ~limit:3 () in 55 + try 56 + (* Search for bookmarks with the search term *) 57 + let search_results = 58 + search_bookmarks client ~query:search_term ~limit:3 () 59 + in 59 60 60 - Printf.printf "Found %d matching bookmarks\n" (List.length search_results.bookmarks); 61 - Printf.printf "Next cursor: %s\n\n" 62 - (match search_results.next_cursor with Some c -> c | None -> "none"); 61 + Printf.printf "Found %d matching bookmarks\n" 62 + (List.length search_results.bookmarks); 63 + Printf.printf "Next cursor: %s\n\n" 64 + (match search_results.next_cursor with Some c -> c | None -> "none"); 63 65 64 - (* Display the search results *) 65 - List.iter print_bookmark search_results.bookmarks; 66 + (* Display the search results *) 67 + List.iter print_bookmark search_results.bookmarks; 66 68 67 - (* Test pagination if we have a next page *) 68 - (match search_results.next_cursor with 69 - | Some cursor -> 70 - Printf.printf "=== Testing search pagination ===\n"; 71 - Printf.printf "Fetching next page with cursor: %s\n\n" cursor; 69 + (* Test pagination if we have a next page *) 70 + match search_results.next_cursor with 71 + | Some cursor -> 72 + Printf.printf "=== Testing search pagination ===\n"; 73 + Printf.printf "Fetching next page with cursor: %s\n\n" cursor; 72 74 73 - let next_page = search_bookmarks client ~query:search_term ~limit:3 ~cursor () in 75 + let next_page = 76 + search_bookmarks client ~query:search_term ~limit:3 ~cursor () 77 + in 74 78 75 - Printf.printf "Found %d more bookmarks on next page\n\n" 76 - (List.length next_page.bookmarks); 79 + Printf.printf "Found %d more bookmarks on next page\n\n" 80 + (List.length next_page.bookmarks); 77 81 78 - List.iter print_bookmark next_page.bookmarks 79 - | None -> 80 - Printf.printf "No more pages available for this search query.\n") 81 - with e -> 82 - Printf.printf "An error occurred while searching: %s\n" (Printexc.to_string e); 83 - Printf.printf "\nFalling back to testing with a simple search term: \"ocaml\"\n\n"; 82 + List.iter print_bookmark next_page.bookmarks 83 + | None -> Printf.printf "No more pages available for this search query.\n" 84 + with e -> ( 85 + Printf.printf "An error occurred while searching: %s\n" 86 + (Printexc.to_string e); 87 + Printf.printf 88 + "\nFalling back to testing with a simple search term: \"ocaml\"\n\n"; 84 89 85 - try 86 - (* Try again with a simple, reliable search term *) 87 - let search_results = search_bookmarks client ~query:"ocaml" ~limit:3 () in 90 + try 91 + (* Try again with a simple, reliable search term *) 92 + let search_results = search_bookmarks client ~query:"ocaml" ~limit:3 () in 88 93 89 - Printf.printf "Found %d matching bookmarks\n" (List.length search_results.bookmarks); 90 - Printf.printf "Next cursor: %s\n\n" 91 - (match search_results.next_cursor with Some c -> c | None -> "none"); 94 + Printf.printf "Found %d matching bookmarks\n" 95 + (List.length search_results.bookmarks); 96 + Printf.printf "Next cursor: %s\n\n" 97 + (match search_results.next_cursor with Some c -> c | None -> "none"); 92 98 93 - (* Display the search results *) 94 - List.iter print_bookmark search_results.bookmarks 95 - with e -> 96 - Printf.printf "Fallback search also failed: %s\n" (Printexc.to_string e)) 99 + (* Display the search results *) 100 + List.iter print_bookmark search_results.bookmarks 101 + with e -> 102 + Printf.printf "Fallback search also failed: %s\n" (Printexc.to_string e))
+36 -35
test/test.ml
··· 43 43 44 44 Eio_main.run @@ fun env -> 45 45 Eio.Switch.run @@ fun sw -> 46 - 47 46 let client = Karakeep.create ~sw ~env ~base_url ~api_key in 48 47 49 48 (* Test 1: fetch_bookmarks - get a single page with pagination info *) 50 49 Printf.printf "=== Test 1: fetch_bookmarks (paginated) ===\n"; 51 - (try 52 - let response = fetch_bookmarks client ~limit:3 () in 53 - Printf.printf "Found bookmarks, showing %d (page 1)\n" 54 - (List.length response.bookmarks); 55 - Printf.printf "Next cursor: %s\n\n" 56 - (match response.next_cursor with Some c -> c | None -> "none"); 57 - List.iter print_bookmark response.bookmarks; 50 + try 51 + let response = fetch_bookmarks client ~limit:3 () in 52 + Printf.printf "Found bookmarks, showing %d (page 1)\n" 53 + (List.length response.bookmarks); 54 + Printf.printf "Next cursor: %s\n\n" 55 + (match response.next_cursor with Some c -> c | None -> "none"); 56 + List.iter print_bookmark response.bookmarks; 58 57 59 - (* Test 2: fetch_all_bookmarks - get multiple pages automatically *) 60 - Printf.printf "=== Test 2: fetch_all_bookmarks (with limit) ===\n"; 61 - let all_bookmarks = fetch_all_bookmarks client ~page_size:2 ~max_pages:2 () in 62 - Printf.printf "Fetched %d bookmarks from up to 2 pages\n\n" 63 - (List.length all_bookmarks); 58 + (* Test 2: fetch_all_bookmarks - get multiple pages automatically *) 59 + Printf.printf "=== Test 2: fetch_all_bookmarks (with limit) ===\n"; 60 + let all_bookmarks = 61 + fetch_all_bookmarks client ~page_size:2 ~max_pages:2 () 62 + in 63 + Printf.printf "Fetched %d bookmarks from up to 2 pages\n\n" 64 + (List.length all_bookmarks); 64 65 65 - List.iter print_bookmark 66 - (List.fold_left 67 - (fun acc x -> if List.length acc < 4 then acc @ [ x ] else acc) 68 - [] all_bookmarks); 69 - Printf.printf "... and %d more bookmarks\n\n" 70 - (max 0 (List.length all_bookmarks - 4)); 66 + List.iter print_bookmark 67 + (List.fold_left 68 + (fun acc x -> if List.length acc < 4 then acc @ [ x ] else acc) 69 + [] all_bookmarks); 70 + Printf.printf "... and %d more bookmarks\n\n" 71 + (max 0 (List.length all_bookmarks - 4)); 71 72 72 - (* Test 3: fetch_bookmark_details - get a specific bookmark *) 73 - (match response.bookmarks with 74 - | first_bookmark :: _ -> 75 - Printf.printf "=== Test 3: fetch_bookmark_details ===\n"; 76 - Printf.printf "Fetching details for bookmark ID: %s\n\n" 77 - first_bookmark.id; 73 + (* Test 3: fetch_bookmark_details - get a specific bookmark *) 74 + match response.bookmarks with 75 + | first_bookmark :: _ -> ( 76 + Printf.printf "=== Test 3: fetch_bookmark_details ===\n"; 77 + Printf.printf "Fetching details for bookmark ID: %s\n\n" 78 + first_bookmark.id; 78 79 79 - (try 80 - let bookmark = fetch_bookmark_details client first_bookmark.id in 81 - print_bookmark bookmark 82 - with e -> 83 - Printf.printf "Error fetching bookmark details: %s\n" (Printexc.to_string e)) 84 - | [] -> 85 - Printf.printf "No bookmarks found to test fetch_bookmark_details\n") 86 - with e -> 87 - Printf.printf "Error in basic tests: %s\n" (Printexc.to_string e); 88 - Printf.printf "Skipping remaining tests due to API error.\n") 80 + try 81 + let bookmark = fetch_bookmark_details client first_bookmark.id in 82 + print_bookmark bookmark 83 + with e -> 84 + Printf.printf "Error fetching bookmark details: %s\n" 85 + (Printexc.to_string e)) 86 + | [] -> Printf.printf "No bookmarks found to test fetch_bookmark_details\n" 87 + with e -> 88 + Printf.printf "Error in basic tests: %s\n" (Printexc.to_string e); 89 + Printf.printf "Skipping remaining tests due to API error.\n"