Other languages
The requests are not written in Go, so nothing about them is. jmapc generate -lang writes the same client in Rust or in TypeScript, from the same files,
and the runtime each one needs is generated with it. What differs between the
three is what each language has to say about a shape: where Go writes a struct
with a field per form, Rust writes an enum and TypeScript writes a union.
Rust
The same requests generate a Rust client:
jmapc generate -lang rust -out src/jmap_clientuse jmap_client::list_inbox_emails::{list_inbox_emails, ListInboxEmailsParams};
use jmap_client::Client;
let client = Client::with_bearer_token("https://example.com/.well-known/jmap", http, token);
let res = list_inbox_emails(&client, ListInboxEmailsParams {
mailbox_id: inbox,
limit: 25,
})
.await?;
for email in &res.list {
println!("{} {:?}", email.received_at, email.subject);
}The runtime comes with it — client.rs, types.rs, and the mod.rs that
declares them beside the requests — so mod jmap_client; is the whole of what a crate
has to add. The generated code requires serde and serde_json and nothing
else. Transmission is a Transport you implement over whichever HTTP client
the program already has, so no HTTP stack, no TLS backend and no async runtime
is added with it:
struct Http(reqwest::Client);
impl Transport for Http {
async fn send(&self, req: HttpRequest) -> Result<HttpResponse, TransportError> {
let mut out = self.0.request(req.method.parse()?, &req.url);
for (name, value) in req.headers {
out = out.header(name, value);
}
if let Some(body) = req.body {
out = out.body(body);
}
let res = out.send().await?;
Ok(HttpResponse {
status: res.status().as_u16(),
content_type: res
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.to_string(),
body: res.bytes().await?.to_vec(),
})
}
}That is also where authentication that a bearer token does not cover belongs — a signature over the request, a token refreshed on expiry — since the transport is the last stage before a request is sent.
A nullable property is an Option, so subject is Option<String>. A union of
shapes is an enum: a filter is Option<FilterOperatorOrEmailFilterCondition>,
untagged, where Go has a struct of the same name with a field per shape. The
primitives that
carry a format rather than a shape are named aliases of String, so an Id and
a TimeZoneId are distinguishable in a signature. And a record derives
Default, so a type with fifty optional properties is built by naming the two
that differ from the default and omitting the rest.
The generated code is already formatted the way rustfmt formats it, so
cargo fmt over the crate changes nothing.
TypeScript
The same requests generate a TypeScript client:
jmapc generate -lang typescript -out src/jmapClientimport { Client } from "./jmapClient/client.js"
import { listInboxEmails } from "./jmapClient/listInboxEmails.js"
const client = new Client("https://example.com/.well-known/jmap", { auth: token })
const res = await listInboxEmails(client, { mailboxId: inbox, limit: 25 })
for (const email of res.list) {
console.log(email.receivedAt, email.from?.[0].email, email.subject)
}The runtime comes with it — client.ts and types.ts are generated alongside
the requests — so the output has no dependencies. The only platform
requirement is fetch.
TypeScript expresses some things more precisely than Go. A nullable property is
a union rather than a pointer, so subject is string | null. A union of
shapes is written as one: a filter is FilterOperator | EmailFilterCondition | null, where Go has a struct with a field per shape. And the primitives that carry a format
rather than a shape are named aliases of string, so an Id and a
TimeZoneId cannot be swapped by accident.