caretly
Rust client for the Caretly API: render ZPL labels to vector PDF or PNG. It has an async client for tokio and a blocking client, sharing the same options, result and error types.
Source: https://github.com/jaapieaapie1/zpl (folder clients/rust).
Install
Or in Cargo.toml:
[]
= "0.1"
Features
| Feature | Default | What it adds |
|---|---|---|
async |
yes | caretly::Client, the async client for tokio. |
rustls-tls |
yes | HTTPS through rustls. |
blocking |
no | caretly::blocking::Client, no async runtime needed. |
native-tls |
no | HTTPS through the platform's TLS library instead of rustls. |
gzip |
no | The compress option: gzip the request body. |
Blocking only, without tokio in your code:
= { = "0.1", = false, = ["blocking", "rustls-tls"] }
Keep one of rustls-tls or native-tls on to reach https:// URLs; without them only
http:// (for example a self-hosted server on localhost) works.
Minimum supported Rust version: 1.85. On toolchains older than the newest dependency
releases need, let Cargo pick compatible versions with resolver = "3" (or
CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback).
Quickstart
Create an API key in the console. Test keys
(zpl_test_…) are free and watermark every label; live keys (zpl_live_…) are billed
per label. The client reads the key from CARETLY_API_KEY unless you pass one.
Async
use ;
async
Blocking
use Client;
Do not call the blocking client from inside an async runtime; use the async client there.
Several files
Each file starts from a fresh printer state; pages follow the order of the files.
# async
File contents can be &str, String, &[u8] or Vec<u8>.
Client settings
use Duration;
let client = builder
.api_key // default: CARETLY_API_KEY
.base_url // default: https://caretly.dev
.timeout // per attempt, default: 30 s
.max_retries // default: 2
.build?;
# Ok::
An empty API key sends no Authorization header, for self-hosted servers without keys.
Render options
| Method | Query / header | Meaning |
|---|---|---|
format(Format::Pdf | Format::Png) |
format |
Output format. Default PDF (every page). |
page(n) |
page |
For PNG: the page to render, from 1. |
dpmm(Dpmm::D6 | D8 | D12 | D24) |
dpmm |
Resolution in dots/mm. Default 8. |
timings(true) |
timings=1 |
Ask for a Server-Timing header. |
idempotency_key("order-1234") |
Idempotency-Key |
A retry with the same key and content is billed once. Max 255 characters. |
compress(true) (feature gzip) |
Content-Encoding: gzip |
Gzip the request body. |
Unset options are left out of the request, so the server defaults apply. page(0), an
empty or too long idempotency key, and an empty file list fail before anything is sent
with Error::InvalidArgument. Dpmm::try_from(n) checks a numeric resolution.
Result
RenderResult holds:
| Field | From | Meaning |
|---|---|---|
bytes |
body | The PDF or PNG. save(path) writes it to a file. |
content_type |
Content-Type |
application/pdf or image/png. |
page_count |
X-Page-Count |
Total pages. |
file_page_counts |
X-File-Page-Counts |
Pages per file (multipart only, else empty). |
labels_billed |
X-Labels-Billed |
Labels billed; 0 for a test key; None without keys. |
rate_limit |
RateLimit-Limit, RateLimit-Remaining |
Burst size and what is left. |
server_timing |
Server-Timing |
Raw header, with timings(true). |
Errors
Every call returns Result<_, caretly::Error>:
| Variant | Cause |
|---|---|
BadRequest |
400: ZPL parse error, invalid parameter, page out of range. |
Authentication |
401: key missing, invalid, expired or revoked. |
PaymentRequired |
402: billing inactive, live keys paused. |
PayloadTooLarge |
413: body over 100 MiB decompressed. |
UnsupportedEncoding |
415: unsupported Content-Encoding. |
RateLimited |
429: too many requests (retried). |
Overloaded |
503: server busy, not billed (retried). |
Api |
Any other status, including other 5xx. |
Connection |
Network failure (retried). |
Timeout |
The timeout elapsed (not retried). |
InvalidArgument |
Client-side validation failed; nothing was sent. |
The API variants carry an ApiError with status, message (the response body) and
retry_after. Error::status(), Error::retry_after() and Error::api_error() reach
them without matching.
# async
Retries
A 429, a 503 or a connection error is retried up to max_retries times (default 2).
The client waits Retry-After seconds when the server sends it, and otherwise backs
off 0.5 s, 1 s, 2 s, 4 s, then 8 s. Other statuses and timeouts are not retried.
When retries are on and you did not set an idempotency key, the client generates one
(a UUID v4) per call and sends it on every attempt, so a retried request is never
billed twice. Set max_retries(0) to turn retries off.
Examples
CARETLY_API_KEY=zpl_test_...
CARETLY_API_KEY=zpl_test_...
CARETLY_API_KEY=zpl_test_...
Development
The tests run against a local mock server; they need no network or API key.
Publishing
This crate lives in clients/rust and is not part of the repository's root workspace
(its Cargo.toml has its own [workspace] table). From clients/rust:
- Update
versioninCargo.tomland add an entry toCHANGELOG.md. - Run the checks above, then
cargo package --listto review the packaged files. cargo publish --dry-run- Log in once with
cargo login <token>(a token from https://crates.io/settings/tokens with thepublish-new/publish-updatescope). cargo publish- Tag the release:
git tag clients/rust/v0.1.0 && git push origin clients/rust/v0.1.0.
docs.rs builds the documentation with all features after publishing.
License
MIT, see LICENSE.