Skip to main content

printobserver_sdk/
lib.rs

1//! `printobserver-sdk`.
2//!
3//! Owns: the Rust client of the printobserver server's HTTP surface: a typed,
4//! blocking client with one method per public operation, and the request and
5//! response types those methods take and answer.
6//!
7//! May depend on: `serde` and `serde_json`. Never `printobserver-core`, never
8//! the server, and never an implementation crate — a client that could reach
9//! them would be a second copy of the server. It does not depend on
10//! `printobserver-types` either: its types are **generated** from the same
11//! checked-in schemas that crate generates, so the two agree because they come
12//! from one source rather than because one imports the other.
13//!
14//! # The types are generated, and the gate says so
15//!
16//! [`contract`] is written by `contract-codegen` from `schemas/`, and `just
17//! check-generated` refuses a tree in which it differs from what that
18//! generator writes. Everything else in this crate is the transport and the
19//! error vocabulary that module's methods are written against.
20//!
21//! # A refused action is an answer, not a failure of transport
22//!
23//! The policy's own refusal arrives as [`ClientError::Rejected`], carrying
24//! [`Rejection`] — the reason, the value asked for and the range allowed — so
25//! a caller can ask again for something inside the range without reading a
26//! message.
27//!
28//! # Images are a path, never bytes
29//!
30//! No method of this client answers image bytes or an encoding of them. The
31//! server answers an absolute path on **its own** filesystem, and this client
32//! hands that path back exactly as it was answered.
33//!
34//! # The reason is not something a caller can leave out
35//!
36//! Every mutating method takes it as an argument, so a call that omits it does
37//! not compile. That is the whole of the enforcement in this client: there is
38//! no run-time refusal for an omission, because there is no way to make one.
39//!
40//! ```compile_fail
41//! use printobserver_sdk::{Actor, Client};
42//!
43//! let client = Client::new("http://127.0.0.1:8420", Actor::Operator);
44//! // No reason: this does not compile, which is the point.
45//! let _ = client.pause("0198f0a1-2b3c-7d4e-8f90-123456789abc");
46//! ```
47//!
48//! ```no_run
49//! use printobserver_sdk::{Actor, Client};
50//!
51//! let client = Client::new("http://127.0.0.1:8420", Actor::Operator);
52//! let status = client.status("0198f0a1-2b3c-7d4e-8f90-123456789abc")?;
53//! println!("{:?}", status.print.state);
54//! # Ok::<(), printobserver_sdk::ClientError>(())
55//! ```
56
57mod client;
58pub mod contract;
59mod error;
60mod transport;
61
62pub use client::Client;
63pub use contract::*;
64pub use error::{ClientError, RecoveryRejected, RejectedAnswer, Rejection};
65pub use transport::Answered;
66
67/// The reason a mutating call carries, refused here when it carries none.
68///
69/// The refusal happens in this client, before a connection is opened: a change
70/// nobody gave a reason for is one the history cannot account for afterwards,
71/// and asking the server to say so would be a request this client already
72/// knows the answer to.
73///
74/// # Errors
75///
76/// Returns [`ClientError::NoReason`] when the reason is empty or is nothing
77/// but whitespace.
78pub fn reason_given(reason: &str) -> Result<(), ClientError> {
79    if reason.trim().is_empty() {
80        return Err(ClientError::NoReason);
81    }
82    Ok(())
83}
84
85/// One value a request carries, as the JSON it travels as.
86///
87/// # Errors
88///
89/// Returns [`ClientError::Unsendable`] when the value cannot be rendered as
90/// JSON, which for the generated types is a value carrying a float that is not
91/// a number.
92pub fn as_value<T: serde::Serialize>(value: &T) -> Result<serde_json::Value, ClientError> {
93    serde_json::to_value(value).map_err(|error| ClientError::Unsendable {
94        detail: error.to_string(),
95    })
96}