this repo has no description

Add ResultReference module for composing JMAP requests

- Created a new ResultReference module to simplify creating and using result references as described in RFC8620 Section 3.7
- Added helper functions to create common reference patterns
- Implemented demo functionality in fastmail_list to show how to chain multiple JMAP requests efficiently
- Added a new -demo-refs command line option to demonstrate this functionality
- Updated AGENT.md to mark task 11 as completed

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+213 -1
bin
lib
+1 -1
AGENT.md
···
9. DONE Read the mailbox attribute spec in specs/ and add a typed interface to the
JMAP labels defined in there.
10. DONE Integrate the human-readable keyword and label printing into fastmail-list.
-
11. Add an OCaml interface to compose result references together explicitly into a
+
11. DONE Add an OCaml interface to compose result references together explicitly into a
single request, from reading the specs.
+132
bin/fastmail_list.ml
···
in
is_unread_keyword || is_not_seen
+
(** Example function demonstrating how to use result references for chained requests *)
+
let demo_result_references conn account_id =
+
let open Jmap.Types in
+
+
(* Create a request that chains the following operations:
+
1. Get mailboxes
+
2. Query emails in the first mailbox found
+
3. Get the full email objects for those IDs
+
*)
+
+
(* Create method call IDs *)
+
let mailbox_get_id = "mailboxGet" in
+
let email_query_id = "emailQuery" in
+
let email_get_id = "emailGet" in
+
+
(* First call: Get mailboxes *)
+
let mailbox_get_call = {
+
name = "Mailbox/get";
+
arguments = `O [
+
("accountId", `String account_id);
+
];
+
method_call_id = mailbox_get_id;
+
} in
+
+
(* Second call: Query emails in the first mailbox using result reference *)
+
(* Create reference to the first mailbox ID from the previous result *)
+
let mailbox_id_ref = Jmap.ResultReference.create
+
~result_of:mailbox_get_id
+
~name:"Mailbox/get"
+
~path:"/list/0/id" in
+
+
(* Use the reference to create the query arguments *)
+
let (mailbox_id_ref_key, mailbox_id_ref_value) =
+
Jmap.ResultReference.reference_arg "inMailbox" mailbox_id_ref in
+
+
let email_query_call = {
+
name = "Email/query";
+
arguments = `O [
+
("accountId", `String account_id);
+
("filter", `O [
+
(mailbox_id_ref_key, mailbox_id_ref_value)
+
]);
+
("limit", `Float 10.0);
+
];
+
method_call_id = email_query_id;
+
} in
+
+
(* Third call: Get full email objects using the query result *)
+
(* Create reference to the email IDs from the query result *)
+
let email_ids_ref = Jmap.ResultReference.create
+
~result_of:email_query_id
+
~name:"Email/query"
+
~path:"/ids" in
+
+
(* Use the reference to create the get arguments *)
+
let (email_ids_ref_key, email_ids_ref_value) =
+
Jmap.ResultReference.reference_arg "ids" email_ids_ref in
+
+
let email_get_call = {
+
name = "Email/get";
+
arguments = `O [
+
("accountId", `String account_id);
+
(email_ids_ref_key, email_ids_ref_value)
+
];
+
method_call_id = email_get_id;
+
} in
+
+
(* Create the complete request with all three method calls *)
+
let request = {
+
using = [
+
Jmap.Capability.to_string Jmap.Capability.Core;
+
Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail
+
];
+
method_calls = [
+
mailbox_get_call;
+
email_query_call;
+
email_get_call
+
];
+
created_ids = None;
+
} in
+
+
(* Make the request *)
+
let* response_result = Jmap.Api.make_request conn.config request in
+
Printf.printf "\nResult Reference Demo:\n";
+
Printf.printf "=====================\n";
+
+
match response_result with
+
| Error err ->
+
Printf.printf "Error executing chained request: %s\n"
+
(match err with
+
| Jmap.Api.Connection_error msg -> "Connection error: " ^ msg
+
| Jmap.Api.HTTP_error (code, body) -> Printf.sprintf "HTTP error %d: %s" code body
+
| Jmap.Api.Parse_error msg -> "Parse error: " ^ msg
+
| Jmap.Api.Authentication_error -> "Authentication error");
+
Lwt.return_unit
+
| Ok response ->
+
(* Process the response *)
+
try
+
(* Look for the Email/get method response *)
+
let email_get_result = List.find (fun (inv : Ezjsonm.value invocation) ->
+
inv.name = "Email/get"
+
) response.method_responses in
+
+
(* Extract the email list from the response *)
+
let list = Ezjsonm.find email_get_result.arguments ["list"] in
+
match list with
+
| `A emails ->
+
Printf.printf "Successfully retrieved %d emails using chained result references!\n"
+
(List.length emails);
+
Lwt.return_unit
+
| _ ->
+
Printf.printf "Unexpected email list format in response.\n";
+
Lwt.return_unit
+
with
+
| Not_found ->
+
Printf.printf "No Email/get result found in response.\n";
+
Lwt.return_unit
+
| e ->
+
Printf.printf "Error processing response: %s\n" (Printexc.to_string e);
+
Lwt.return_unit
+
(** Main function *)
let main () =
(* Parse command-line arguments *)
let unread_only = ref false in
let show_labels = ref false in
let debug_level = ref 0 in
+
let demo_refs = ref false in
let args = [
("-unread", Arg.Set unread_only, "List only unread messages");
("-labels", Arg.Set show_labels, "Show labels/keywords associated with messages");
("-debug", Arg.Int (fun level -> debug_level := level), "Set debug level (0-4, where 4 is most verbose)");
+
("-demo-refs", Arg.Set demo_refs, "Demonstrate result references");
] in
let usage_msg = "Usage: JMAP_API_TOKEN=your_token fastmail_list [options]" in
···
Printf.eprintf " -unread List only unread messages\n";
Printf.eprintf " -labels Show labels/keywords associated with messages\n";
Printf.eprintf " -debug LEVEL Set debug level (0-4, where 4 is most verbose)\n";
+
Printf.eprintf " -demo-refs Demonstrate result references\n";
exit 1
| Some token ->
(* Only print token info at Info level or higher *)
···
| [] ->
Printf.eprintf "No accounts found\n";
exit 1
+
in
+
+
(* Run result references demo if requested *)
+
let* () =
+
if !demo_refs then
+
demo_result_references conn primary_account_id
+
else
+
Lwt.return_unit
in
(* Get the Inbox mailbox *)
+48
lib/jmap.ml
···
}
end
+
(** Module for working with ResultReferences as described in Section 3.7 of RFC8620 *)
+
module ResultReference = struct
+
open Types
+
+
(** Create a reference to a previous method result *)
+
let create ~result_of ~name ~path =
+
{ result_of; name; path }
+
+
(** Create a JSON pointer path to access a specific property *)
+
let property_path property =
+
"/" ^ property
+
+
(** Create a JSON pointer path to access all items in an array with a specific property *)
+
let array_items_path ?(property="") array_property =
+
let base = "/" ^ array_property ^ "/*" in
+
if property = "" then base
+
else base ^ "/" ^ property
+
+
(** Create argument with result reference.
+
Returns string key prefixed with # and ResultReference value. *)
+
let reference_arg arg_name ref_obj =
+
(* Prefix argument name with # *)
+
let prefixed_name = "#" ^ arg_name in
+
+
(* Convert reference object to JSON *)
+
let json_value = `O [
+
("resultOf", `String ref_obj.result_of);
+
("name", `String ref_obj.name);
+
("path", `String ref_obj.path)
+
] in
+
+
(prefixed_name, json_value)
+
+
(** Create a reference to all IDs returned by a query method *)
+
let query_ids ~result_of =
+
create
+
~result_of
+
~name:"Foo/query"
+
~path:"/ids"
+
+
(** Create a reference to properties of objects returned by a get method *)
+
let get_property ~result_of ~property =
+
create
+
~result_of
+
~name:"Foo/get"
+
~path:("/list/*/" ^ property)
+
end
+
module Api = struct
open Lwt.Syntax
open Types
+32
lib/jmap.mli
···
(** {1 API Client} *)
+
(** Module for working with ResultReferences as described in Section 3.7 of RFC8620.
+
Provides utilities to create and compose results from previous methods. *)
+
module ResultReference : sig
+
(** Create a reference to a previous method result *)
+
val create :
+
result_of:string ->
+
name:string ->
+
path:string ->
+
Types.result_reference
+
+
(** Create a JSON pointer path to access a specific property *)
+
val property_path : string -> string
+
+
(** Create a JSON pointer path to access all items in an array with a specific property *)
+
val array_items_path : ?property:string -> string -> string
+
+
(** Create argument with result reference.
+
Returns string key prefixed with # and ResultReference value. *)
+
val reference_arg : string -> Types.result_reference -> string * Ezjsonm.value
+
+
(** Create a reference to all IDs returned by a query method *)
+
val query_ids :
+
result_of:string ->
+
Types.result_reference
+
+
(** Create a reference to properties of objects returned by a get method *)
+
val get_property :
+
result_of:string ->
+
property:string ->
+
Types.result_reference
+
end
+
(** Module for making JMAP API requests over HTTP.
Provides functionality to interact with JMAP servers according to RFC8620. *)
module Api : sig