Skip to main content

printobserver_sdk/
contract.rs

1//! Generated by `contract-codegen` from the checked-in contract schemas.
2//!
3//! This file is the Rust client's request and response types.
4//!
5//! Do not edit by hand: change the contracts, regenerate with `just
6//! generate-clients`, and commit what that writes.
7
8use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11
12use crate::{Client, ClientError};
13
14/// The version of the server contract these types were generated from: the
15/// version of the workspace this crate is compiled from. A published crate
16/// reports the release version it was built at, and a crate compiled from a
17/// checkout reports that checkout's workspace version.
18pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
19
20/// Every operation this client exposes a method for, which is every operation
21/// the server declares and no other.
22pub const OPERATION_NAMES: [&str; 28] = [
23    "prints",
24    "status",
25    "context",
26    "image",
27    "history",
28    "events",
29    "manifest_get",
30    "look",
31    "manifest_set",
32    "spools",
33    "spool",
34    "printer_history",
35    "preflight",
36    "pause",
37    "resume",
38    "cancel",
39    "start_print",
40    "set_feedrate_factor",
41    "set_flowrate_factor",
42    "set_tool_target_c",
43    "set_bed_target_c",
44    "set_fan_percent",
45    "acknowledge_failure",
46    "register_spool",
47    "load_spool",
48    "unload_spool",
49    "adjust_spool",
50    "retire_spool",
51];
52
53/// A field that was sent, null included, as what was sent.
54fn present<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
55where
56    D: serde::Deserializer<'de>,
57    T: Deserialize<'de>,
58{
59    T::deserialize(deserializer).map(Some)
60}
61
62/// What an operator's acknowledgement of a failure event asks for next.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum AcknowledgementDisposition {
65    /// Carry on printing.
66    #[serde(rename = "continue")]
67    Continue,
68    /// Carry on printing, watched more closely.
69    #[serde(rename = "watch")]
70    Watch,
71    /// The print should stop. Recorded for whoever may cancel it: the
72    /// acknowledgement itself asks nothing of the machine.
73    #[serde(rename = "stop")]
74    Stop,
75}
76
77/// What one mutating request left behind it.
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct ActionAnswer {
80    /// The bounded intervention it opened, when it opened one.
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub intervention: Option<Intervention>,
83    /// What the printer said, when the request reached it and it refused.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub printer_refusal: Option<String>,
86    /// The record of the request and the decision taken on it. A rejected
87    /// request's rejection is here, carrying its reason, the value asked for
88    /// and the range allowed, so a caller can ask again inside the range.
89    pub record: ActionRecord,
90}
91
92/// An accepted action reached the printer.
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct ActionExecutedPayload {
95    /// The action's identifier.
96    pub action_id: ActionId,
97    /// The bounded intervention it opened, when it opened one.
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub intervention_id: Option<InterventionId>,
100}
101
102/// A lowercase hyphenated version 7 UUID identifying one requested action.
103pub type ActionId = String;
104
105/// One action of the closed vocabulary, named without its payload.
106///
107/// This is what a safety envelope grants and what a policy rejection names;
108/// the
109/// payload lives on `PrintAction` itself.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum ActionKind {
112    /// Pause the print.
113    #[serde(rename = "pause")]
114    Pause,
115    /// Resume the print.
116    #[serde(rename = "resume")]
117    Resume,
118    /// Cancel the print.
119    #[serde(rename = "cancel")]
120    Cancel,
121    /// Start a print of a named file.
122    #[serde(rename = "start_print")]
123    StartPrint,
124    /// Set the feedrate factor.
125    #[serde(rename = "set_feedrate_factor")]
126    SetFeedrateFactor,
127    /// Set the flowrate factor.
128    #[serde(rename = "set_flowrate_factor")]
129    SetFlowrateFactor,
130    /// Set a tool's target temperature.
131    #[serde(rename = "set_tool_target_c")]
132    SetToolTargetC,
133    /// Set the bed's target temperature.
134    #[serde(rename = "set_bed_target_c")]
135    SetBedTargetC,
136    /// Set the fan percentage.
137    #[serde(rename = "set_fan_percent")]
138    SetFanPercent,
139    /// Acknowledge a failure event.
140    #[serde(rename = "acknowledge_failure")]
141    AcknowledgeFailure,
142    /// Register a spool.
143    #[serde(rename = "register_spool")]
144    RegisterSpool,
145    /// Load a spool onto a tool.
146    #[serde(rename = "load_spool")]
147    LoadSpool,
148    /// Unload a spool from its tool.
149    #[serde(rename = "unload_spool")]
150    UnloadSpool,
151    /// Correct a spool's remaining figure.
152    #[serde(rename = "adjust_spool")]
153    AdjustSpool,
154    /// Retire a spool.
155    #[serde(rename = "retire_spool")]
156    RetireSpool,
157}
158
159/// One request, the decision taken on it, and what became of it.
160///
161/// `executed_at` and `outcome` are both optional and are absent together: a
162/// record is written when the decision is taken, which is before — and, for a
163/// rejected request, instead of — anything reaching the printer.
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub struct ActionRecord {
166    /// The decision policy took on it.
167    pub decision: PolicyDecision,
168    /// When it reached the printer, if it did.
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub executed_at: Option<Timestamp>,
171    /// This record's identifier, minted by the store.
172    pub id: ActionId,
173    /// What the printer made of it, if it reached the printer.
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub outcome: Option<ExecutionOutcome>,
176    /// The print this request was made against.
177    pub print_id: PrintId,
178    /// The request itself.
179    pub request: ActionRequest,
180}
181
182/// Policy refused an action.
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct ActionRejectedPayload {
185    /// The action's identifier.
186    pub action_id: ActionId,
187    /// The whole decision, carrying which rejection it was.
188    pub decision: PolicyDecision,
189}
190
191/// One request an actor made, at the instant it made it.
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct ActionRequest {
194    /// What was asked for.
195    pub action: PrintAction,
196    /// Who asked.
197    pub actor: Actor,
198    /// When they asked.
199    pub requested_at: Timestamp,
200}
201
202/// An actor asked for an action.
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct ActionRequestedPayload {
205    /// What was asked for.
206    pub action: PrintAction,
207    /// The action's identifier.
208    pub action_id: ActionId,
209    /// Who asked.
210    pub actor: Actor,
211}
212
213/// Who asked for something.
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215pub enum Actor {
216    /// The supervising agent, naming its session.
217    #[serde(rename = "agent")]
218    Agent {
219        /// The supervision session the agent is acting in.
220        session_name: String,
221    },
222    /// A person.
223    #[serde(rename = "operator")]
224    Operator,
225    /// The supervisor itself.
226    #[serde(rename = "system")]
227    System,
228}
229
230/// An actor class, which is what a safety envelope grants actions to.
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232pub enum ActorClass {
233    /// The supervising agent.
234    #[serde(rename = "agent")]
235    Agent,
236    /// A person.
237    #[serde(rename = "operator")]
238    Operator,
239    /// The supervisor itself.
240    #[serde(rename = "system")]
241    System,
242}
243
244/// One thing an adjustment may change.
245pub type Adjustable = String;
246
247/// The agent's written record of one supervision turn.
248///
249/// This is deliberately not how the agent acts: acting is a
250/// `PrintAction` that policy rules on.
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct AgentAssessment {
253    /// How sure the agent is.
254    pub confidence: Confidence,
255    /// What the agent did.
256    pub did: String,
257    /// Whether the agent is escalating to a person.
258    pub escalating: bool,
259    /// Whether the print should carry on.
260    pub should_continue: bool,
261    /// One line saying what is happening.
262    pub summary: String,
263    /// Why it did it.
264    pub why: String,
265}
266
267/// The agent wrote down what it made of a turn.
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct AgentAssessmentPayload {
270    /// What the agent answered with.
271    pub assessment: AgentAssessment,
272    /// The session the turn ran in.
273    pub session_name: String,
274}
275
276/// Somebody took a fresh look at the print.
277///
278/// Written down before the camera is asked for a frame, so that a frame the
279/// camera would not give is recorded against a look that is already in the
280/// history; the frame itself, when there is one, is this event's image.
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub struct CameraLookPayload {
283    /// The events for the print that arrived while it waited, and that it
284    /// handed to whoever looked, oldest first.
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub delivered: Option<Vec<EventId>>,
287    /// How long the look waited for something to happen before it was taken,
288    /// in whole seconds.
289    pub waited_s: i64,
290}
291
292/// The durable identity of one debit, so that a replay can never debit twice.
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294#[serde(tag = "key")]
295pub enum ChargeKey {
296    /// One segment of a print this system watched, on one tool.
297    #[serde(rename = "print")]
298    Print {
299        /// The print.
300        print_id: PrintId,
301        /// Which segment: zero from the print's start, and one more each time
302        /// a mid-print swap or a filament-change signal closes the current
303        /// one.
304        segment: i64,
305        /// The tool, in the printer's own numbering.
306        tool: i64,
307    },
308    /// One print this system never saw, on one tool.
309    #[serde(rename = "unobserved")]
310    Unobserved {
311        /// The file that was printed.
312        file_name: String,
313        /// The file's tally generation, which advances each time the
314        /// printer's
315        /// tally for the file is reset.
316        generation: i64,
317        /// Where the file lives, in the printer's own vocabulary.
318        origin: String,
319        /// The print's ordinal in the file's tally — completed and failed
320        /// prints counted up to and including this one — within that
321        /// generation.
322        print_number: i64,
323        /// The tool, in the printer's own numbering.
324        tool: i64,
325    },
326}
327
328/// How the length a charge debits was worked out.
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
330pub enum ChargeMethod {
331    /// A completed print: the length the file's analysis gives.
332    #[serde(rename = "slicer_estimate")]
333    SlicerEstimate,
334    /// The extrusion the file's own moves add up to, up to a position in it.
335    #[serde(rename = "gcode_extrusion")]
336    GcodeExtrusion,
337    /// The analysed length scaled by how far into the file the print got; the
338    /// fallback used only when the file itself could not be read.
339    #[serde(rename = "file_fraction")]
340    FileFraction,
341    /// A completed print this system never saw: the file's analysed length.
342    #[serde(rename = "unobserved_estimate")]
343    UnobservedEstimate,
344}
345
346/// Filament a tool fed after an unconfirmed filament change was held for the
347/// next spool loaded onto that tool, or confirmed on it.
348#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
349pub struct ChargePendedPayload {
350    /// What was held.
351    pub pending: PendingCharge,
352}
353
354/// One mark a spool command cleared, and the spool it was cleared from.
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
356pub struct ClearedMark {
357    /// The reason that no longer stands.
358    pub reason: UnverifiedReason,
359    /// The spool the mark was cleared from.
360    pub spool_id: SpoolId,
361}
362
363/// How sure the agent is.
364///
365/// A closed vocabulary rather than a number, because a number invites a
366/// precision the agent does not have.
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
368pub enum Confidence {
369    /// Not sure.
370    #[serde(rename = "low")]
371    Low,
372    /// Fairly sure.
373    #[serde(rename = "medium")]
374    Medium,
375    /// Sure.
376    #[serde(rename = "high")]
377    High,
378}
379
380/// What a context read answers.
381///
382/// The latest image is **materialized** here rather than left as the handle
383/// `PrintContext` carries: the one thing a supervision turn does with an
384/// image is look at it, and a handle is not something anything can open. The
385/// path is absolute on this server's own filesystem, exactly as
386/// `ImageAnswer`'s is, and is absent when there is no image or when the
387/// record is intact and the file is not.
388#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
389pub struct ContextAnswer {
390    /// Everything a supervision turn is given about the print.
391    pub context: PrintContext,
392    /// Where the latest image's bytes are, absolute on this host.
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub image_path: Option<String>,
395}
396
397/// The envelope intersected with the print's manifest.
398///
399/// This is what context reports, so that the agent can see its own limits
400/// before it asks. Computing one is the supervision core's; this crate
401/// declares
402/// the shape and nothing that produces it.
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
404pub struct EffectiveBounds {
405    /// The range each adjustable may be set to, inclusive.
406    pub allowed: BTreeMap<String, Range>,
407}
408
409/// What this server answers when it will not do what it was asked.
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
411pub struct ErrorAnswer {
412    /// One line saying why, in the words of whatever refused it.
413    pub error: String,
414}
415
416/// A lowercase hyphenated version 7 UUID identifying one event.
417pub type EventId = String;
418
419/// The name one kind of event is written down under: lowercase `snake_case`,
420/// declared by the domain that owns the event.
421pub type EventKind = String;
422
423/// One event, as the store holds it and the server serves it.
424///
425/// `raw` holds the bytes exactly as received for an externally sourced event
426/// and is absent for an internally raised one — it is what makes the history
427/// auditable when a normalization turns out to be wrong. `print_id` is
428/// optional, because an externally sourced event may name no print this
429/// system
430/// knows.
431#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
432pub struct EventRecord {
433    /// This event's identifier, minted by the store.
434    pub id: EventId,
435    /// The image it arrived with, when it arrived with one.
436    #[serde(default, skip_serializing_if = "Option::is_none")]
437    pub image: Option<ImageRef>,
438    /// Which event this is.
439    pub kind: EventKind,
440    /// What it carries, in the form its kind declares.
441    pub payload: serde_json::Value,
442    /// The print it belongs to, when it belongs to one.
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub print_id: Option<PrintId>,
445    /// The bytes exactly as received, for an externally sourced event.
446    #[serde(default, skip_serializing_if = "Option::is_none")]
447    pub raw: Option<RawBytes>,
448    /// When it was received.
449    pub received_at: Timestamp,
450    /// Where it came from.
451    pub source: EventSource,
452}
453
454/// Where an event came from, as the bare string the domain that raised it
455/// declares for itself.
456pub type EventSource = String;
457
458/// What an events read answers: the events stored after its cursor that pass
459/// its filters, oldest first, and where the next read picks up.
460#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
461pub struct EventsAnswer {
462    /// The last event this read examined, whether or not it passed the
463    /// filters, given as `after` to read on from it; null when the log held
464    /// nothing to start after. Always sent, as null when it is that.
465    pub cursor: Option<EventId>,
466    /// The events, oldest first in the order they were stored, each exactly
467    /// as
468    /// a history read serves it.
469    pub events: Vec<EventRecord>,
470    /// The watched print's end as stored, whatever the filters said, and null
471    /// while it is open. Absent from a read that names no print.
472    #[serde(
473        default,
474        skip_serializing_if = "Option::is_none",
475        deserialize_with = "present"
476    )]
477    pub print_ended: Option<Option<PrintEnded>>,
478}
479
480/// What happened when an accepted action reached the printer.
481#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
482pub enum ExecutionOutcome {
483    /// The printer took it.
484    #[serde(rename = "succeeded")]
485    Succeeded,
486    /// The printer did not, for this reason.
487    #[serde(rename = "failed")]
488    Failed {
489        /// Why it failed.
490        reason: String,
491    },
492}
493
494/// A filament change was signalled during a print.
495#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
496pub struct FilamentChangeSignalledPayload {
497    /// The position the spool was charged up to, when one was known.
498    #[serde(default, skip_serializing_if = "Option::is_none")]
499    pub position: Option<FilePosition>,
500    /// Whether that position was read when the signal was handled, rather
501    /// than
502    /// being the last one recorded; never with no position at all.
503    pub position_read_at_signal: bool,
504    /// The kind of the alert that carried the signal.
505    pub source_kind: EventKind,
506    /// The spool that was on it, when one was tracked.
507    #[serde(default, skip_serializing_if = "Option::is_none")]
508    pub spool_id: Option<SpoolId>,
509    /// The tool the change was signalled for, when it is known.
510    #[serde(default, skip_serializing_if = "Option::is_none")]
511    pub tool: Option<i64>,
512}
513
514/// Filament was charged against a spool, or recorded uncharged.
515#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
516pub struct FilamentConsumedPayload {
517    /// The file's analysed length for the tool, in millimetres, when it had
518    /// one.
519    #[serde(default, skip_serializing_if = "Option::is_none")]
520    pub analysed_length_mm: Option<f64>,
521    /// The durable identity of this debit.
522    pub charge_key: ChargeKey,
523    /// The density the grams were worked out with, in grams per cubic
524    /// centimetre.
525    #[serde(default, skip_serializing_if = "Option::is_none")]
526    pub density_g_cm3: Option<f64>,
527    /// The diameter the grams were worked out with, in millimetres.
528    #[serde(default, skip_serializing_if = "Option::is_none")]
529    pub diameter_mm: Option<f64>,
530    /// What that length weighs, in grams, when a spool gave its diameter and
531    /// density.
532    #[serde(default, skip_serializing_if = "Option::is_none")]
533    pub grams: Option<f64>,
534    /// How much was fed, in millimetres.
535    pub length_mm: f64,
536    /// How the length was worked out.
537    pub method: ChargeMethod,
538    /// Anything a reader needs to make sense of the figure.
539    #[serde(default, skip_serializing_if = "Option::is_none")]
540    pub note: Option<String>,
541    /// How far into the file the charge runs, when that was read.
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub position: Option<FilePosition>,
544    /// The spool's figure after it, in grams.
545    #[serde(default, skip_serializing_if = "Option::is_none")]
546    pub remaining_after_g: Option<f64>,
547    /// The spool's figure before the charge, in grams.
548    #[serde(default, skip_serializing_if = "Option::is_none")]
549    pub remaining_before_g: Option<f64>,
550    /// The spool charged; absent when nothing was tracked on the tool, and
551    /// the
552    /// filament was recorded uncharged.
553    #[serde(default, skip_serializing_if = "Option::is_none")]
554    pub spool_id: Option<SpoolId>,
555    /// The tool that fed it.
556    pub tool: i64,
557}
558
559/// A file name a printer's own file API can be asked for: no path separator,
560/// no NUL byte, no `.` or `..` segment, no drive prefix, and not empty.
561pub type FileName = String;
562
563/// How far into its file a print had got, and when that was read.
564#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
565pub struct FilePosition {
566    /// The byte the printer had read the file up to.
567    pub bytes: i64,
568    /// When the position was read.
569    pub observed_at: Timestamp,
570    /// The file's size in bytes, when it was known.
571    #[serde(default, skip_serializing_if = "Option::is_none")]
572    pub size_bytes: Option<i64>,
573}
574
575/// The printer's tally for a file went down — the file was deleted and
576/// uploaded again — so the file's tally generation advanced, and the new
577/// count
578/// is that generation's growth from zero.
579///
580/// The rebaseline itself marks no spool; only an `unverified` disposition
581/// does.
582#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
583pub struct FileTallyRebaselinedPayload {
584    /// What became of the prints the new generation counts.
585    pub disposition: RebaselineDisposition,
586    /// The file whose tally went down.
587    pub file_name: String,
588    /// The failed prints the new tally counts.
589    pub new_failures: i64,
590    /// The generation it is in now.
591    pub new_generation: i64,
592    /// The completed prints the new tally counts.
593    pub new_successes: i64,
594    /// The failed prints the old tally counted.
595    pub old_failures: i64,
596    /// The generation the tally was in.
597    pub old_generation: i64,
598    /// The completed prints the old tally counted.
599    pub old_successes: i64,
600    /// Where it lives, in the printer's own vocabulary.
601    pub origin: String,
602}
603
604/// One heater, as a source reported it.
605///
606/// Every field is optional, because a source that reports no heater at all
607/// reports none of these; each is a plausibility-ranged reported value in
608/// degrees Celsius.
609#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
610pub struct HeaterSnapshot {
611    /// The temperature the heater is at.
612    #[serde(default, skip_serializing_if = "Option::is_none")]
613    pub actual_c: Option<Reported>,
614    /// The offset applied to this heater's target.
615    #[serde(default, skip_serializing_if = "Option::is_none")]
616    pub offset_c: Option<Reported>,
617    /// The temperature the heater is driving towards.
618    #[serde(default, skip_serializing_if = "Option::is_none")]
619    pub target_c: Option<Reported>,
620}
621
622/// What a history read answers.
623#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
624pub struct HistoryAnswer {
625    /// The print's events, newest first.
626    pub events: Vec<EventRecord>,
627}
628
629/// What an image read answers.
630///
631/// The path is absolute on this server's own filesystem, and is the whole of
632/// the answer about the bytes: nothing here renders them, encodes them or
633/// serves them. It is absent when the record is intact and the file is not,
634/// which is a different answer from there being no such image.
635#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
636pub struct ImageAnswer {
637    /// Where its bytes are, absolute on this host.
638    #[serde(default, skip_serializing_if = "Option::is_none")]
639    pub path: Option<String>,
640    /// The record.
641    pub record: ImageRecord,
642}
643
644/// A lowercase hyphenated version 7 UUID identifying one image.
645pub type ImageId = String;
646
647/// One image, stored beside the event it arrived with.
648///
649/// `relative_path` is relative to the configured state directory, so that the
650/// store stays portable. Materializing an image resolves that to an absolute
651/// path; nothing in this system renders image bytes into JSON.
652#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
653pub struct ImageRecord {
654    /// How many bytes it is.
655    pub byte_len: i64,
656    /// The content type it was served as.
657    pub content_type: String,
658    /// The event it arrived with.
659    pub event_id: EventId,
660    /// When it was fetched.
661    pub fetched_at: Timestamp,
662    /// This image's identifier, minted by the store.
663    pub id: ImageId,
664    /// The print it belongs to.
665    pub print_id: PrintId,
666    /// Where it lives, relative to the configured state directory.
667    pub relative_path: String,
668    /// The SHA-256 of its bytes, lowercase hexadecimal.
669    pub sha256: String,
670    /// Where it was fetched from, when it was fetched from somewhere.
671    #[serde(default, skip_serializing_if = "Option::is_none")]
672    pub source_url: Option<String>,
673}
674
675/// The handle an image travels in context under.
676#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
677pub struct ImageRef {
678    /// The image's identifier.
679    pub id: ImageId,
680    /// The SHA-256 of its bytes, lowercase hexadecimal.
681    pub sha256: String,
682}
683
684/// One adjustment made for a bounded time.
685///
686/// `prior_value` is read from the printer snapshot taken before the change,
687/// and
688/// is absent when the printer reported none — in which case expiry restores
689/// nothing and the outcome says so rather than guessing a default.
690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
691pub struct Intervention {
692    /// The action that asked for it.
693    pub action_id: ActionId,
694    /// What it changed.
695    pub adjustable: Adjustable,
696    /// When it was applied.
697    pub applied_at: Timestamp,
698    /// What it was changed to.
699    pub applied_value: f64,
700    /// When it stops standing.
701    pub expires_at: Timestamp,
702    /// This intervention's identifier, minted by the store.
703    pub id: InterventionId,
704    /// What became of it.
705    pub outcome: InterventionOutcome,
706    /// The print it was made against.
707    pub print_id: PrintId,
708    /// What the printer reported before the change, if it reported anything.
709    #[serde(default, skip_serializing_if = "Option::is_none")]
710    pub prior_value: Option<f64>,
711    /// When the prior value was put back, if it was.
712    #[serde(default, skip_serializing_if = "Option::is_none")]
713    pub restored_at: Option<Timestamp>,
714}
715
716/// A bounded intervention expired.
717#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
718pub struct InterventionExpiredPayload {
719    /// What it had changed.
720    pub adjustable: Adjustable,
721    /// The intervention's identifier.
722    pub intervention_id: InterventionId,
723    /// What became of it.
724    pub outcome: InterventionOutcome,
725}
726
727/// A lowercase hyphenated version 7 UUID identifying one bounded
728/// intervention.
729pub type InterventionId = String;
730
731/// What became of a bounded change.
732#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
733pub enum InterventionOutcome {
734    /// It is still in force.
735    #[serde(rename = "still_active")]
736    StillActive,
737    /// The prior value was put back.
738    #[serde(rename = "restored")]
739    Restored,
740    /// There was no prior value to put back.
741    #[serde(rename = "restore_unavailable")]
742    RestoreUnavailable,
743    /// Putting the prior value back failed.
744    #[serde(rename = "restore_failed")]
745    RestoreFailed {
746        /// Why it failed.
747        reason: String,
748    },
749    /// Another intervention replaced it before it expired.
750    #[serde(rename = "superseded")]
751    Superseded {
752        /// The intervention that replaced it.
753        by: InterventionId,
754    },
755}
756
757/// What a sliced job declares about itself and about what may be adjusted.
758///
759/// An adjustable the manifest does not name takes the envelope's own range; a
760/// manifest range wider than the envelope's is narrowed to the envelope's and
761/// the narrowing is recorded on the print. A manifest may only narrow.
762#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
763pub struct JobManifest {
764    /// The range each named adjustable may take, inclusive.
765    pub allowed: BTreeMap<String, Range>,
766    /// The file this manifest is about, as the slicer named it.
767    pub file_name: String,
768    /// The material the job is sliced for.
769    pub material: String,
770    /// Whatever else the slicer recorded.
771    pub metadata: BTreeMap<String, String>,
772    /// The nozzle the job is sliced for, in millimetres.
773    pub nozzle_diameter_mm: f64,
774    /// The slicer profile the job was sliced with.
775    pub slicer_profile: String,
776}
777
778/// The job a printer reports it is running.
779///
780/// Every field but `state` is optional: an absent one means the source did
781/// not
782/// report it.
783#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
784pub struct JobSnapshot {
785    /// How far through the print is, as a fraction from zero to one.
786    ///
787    /// Normalized to a fraction here regardless of how the source expresses
788    /// it.
789    #[serde(default, skip_serializing_if = "Option::is_none")]
790    pub completion: Option<Reported>,
791    /// The error the source reports, when it reports one.
792    #[serde(default, skip_serializing_if = "Option::is_none")]
793    pub error: Option<String>,
794    /// The whole print's estimated duration, in whole seconds.
795    #[serde(default, skip_serializing_if = "Option::is_none")]
796    pub estimated_print_time_s: Option<i64>,
797    /// The filament the source's analysis of the file says each tool uses;
798    /// empty when the source reported none.
799    #[serde(default, skip_serializing_if = "Option::is_none")]
800    pub filament: Option<Vec<ToolFilament>>,
801    /// The name of the file being printed, as the source reported it.
802    #[serde(default, skip_serializing_if = "Option::is_none")]
803    pub file_name: Option<String>,
804    /// Where the file lives, in the source's own vocabulary.
805    #[serde(default, skip_serializing_if = "Option::is_none")]
806    pub file_origin: Option<String>,
807    /// How far into the file the printer has read, in bytes, when the source
808    /// reported it.
809    #[serde(default, skip_serializing_if = "Option::is_none")]
810    pub file_position_bytes: Option<i64>,
811    /// How long the print has left, in whole seconds.
812    #[serde(default, skip_serializing_if = "Option::is_none")]
813    pub print_time_left_s: Option<i64>,
814    /// How long the print has been running, in whole seconds.
815    #[serde(default, skip_serializing_if = "Option::is_none")]
816    pub print_time_s: Option<i64>,
817    /// The file's size in bytes.
818    #[serde(default, skip_serializing_if = "Option::is_none")]
819    pub size_bytes: Option<i64>,
820    /// The state the source reports the job to be in.
821    pub state: PrinterState,
822}
823
824/// One spool loaded on one tool, as a print started.
825#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
826pub struct LoadedSpool {
827    /// The spool loaded on it.
828    pub spool_id: SpoolId,
829    /// The tool, in the printer's own numbering.
830    pub tool: i64,
831}
832
833/// One fresh look at a print.
834///
835/// An answer rather than a record, so it admits fields beside its own the way
836/// every answer of the server does: a client that could not open the frame's
837/// path says so in a field of its own beside the rest of the answer.
838#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839pub struct Look {
840    /// The events for this print that arrived while the look waited, oldest
841    /// first; the look returned early when there were any.
842    #[serde(default, skip_serializing_if = "Option::is_none")]
843    pub arrived: Option<Vec<EventRecord>>,
844    /// Whether the print is paused and the pause is the detector's, which is
845    /// what an adjustment asked for now would be applied under.
846    pub detector_paused: bool,
847    /// The look itself, as it was written into the print's history, carrying
848    /// the frame as its image when there is one.
849    pub event: EventRecord,
850    /// The frame the camera gave, absent when no camera is configured or it
851    /// gave none — which the history records as a port failure.
852    #[serde(default, skip_serializing_if = "Option::is_none")]
853    pub frame: Option<ImageRef>,
854    /// The absolute path the frame's bytes are at, on the supervisor's host.
855    #[serde(default, skip_serializing_if = "Option::is_none")]
856    pub image_path: Option<String>,
857    /// The job the printer reports now, absent when it could not be read.
858    #[serde(default, skip_serializing_if = "Option::is_none")]
859    pub job: Option<JobSnapshot>,
860    /// The printer's state now, absent when it could not be read.
861    #[serde(default, skip_serializing_if = "Option::is_none")]
862    pub printer: Option<PrinterSnapshot>,
863}
864
865/// An external body arrived that could not be read.
866///
867/// This kind always carries its `raw` bytes, and it exists so that an alert
868/// this system cannot read is written down rather than dropped.
869#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
870pub struct MalformedExternalEventPayload {
871    /// One line saying why the body could not be read.
872    pub detail: String,
873}
874
875/// What a manifest read or write answers.
876#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
877pub struct ManifestAnswer {
878    /// The manifest, when the print has one.
879    #[serde(default, skip_serializing_if = "Option::is_none")]
880    pub manifest: Option<JobManifest>,
881    /// Every range this manifest asked wider than the envelope allows,
882    /// narrowed
883    /// to the envelope's — recorded on the print, so nobody has to wonder
884    /// later
885    /// which bound applied.
886    pub narrowings: Vec<ManifestNarrowing>,
887}
888
889/// One adjustable whose manifest range was wider than the envelope's.
890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
891pub struct ManifestNarrowing {
892    /// The adjustable that was narrowed.
893    pub adjustable: Adjustable,
894    /// The range that stands.
895    pub applied: Range,
896    /// The range the manifest asked for.
897    pub requested: Range,
898}
899
900/// Obico reported a print failure.
901///
902/// The two instants are optional because Obico's own field for each is a Unix
903/// timestamp number, an empty string, or absent, and the last two both mean
904/// the
905/// producer reported no instant. An absent field here is that, never an epoch
906/// date standing in for it.
907#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
908pub struct ObicoFailureAlertPayload {
909    /// When the print ended, when Obico reported an instant for it.
910    #[serde(default, skip_serializing_if = "Option::is_none")]
911    pub ended_at: Option<Timestamp>,
912    /// The file being printed, when Obico named one.
913    #[serde(default, skip_serializing_if = "Option::is_none")]
914    pub file_name: Option<String>,
915    /// Whether Obico called it a warning rather than a failure.
916    pub is_warning: bool,
917    /// Obico's own identifier for the print, when it named one.
918    #[serde(default, skip_serializing_if = "Option::is_none")]
919    pub obico_print_id: Option<i64>,
920    /// Whether Obico paused the print itself.
921    pub print_paused: bool,
922    /// When the print started, when Obico reported an instant for it.
923    #[serde(default, skip_serializing_if = "Option::is_none")]
924    pub started_at: Option<Timestamp>,
925}
926
927/// The kind of printer notification Obico sent, normalized.
928#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
929pub enum ObicoNotificationType {
930    /// A print started.
931    #[serde(rename = "started")]
932    Started,
933    /// A print finished.
934    #[serde(rename = "done")]
935    Done,
936    /// A print was cancelled.
937    #[serde(rename = "cancelled")]
938    Cancelled,
939    /// A print was paused.
940    #[serde(rename = "paused")]
941    Paused,
942    /// A print was resumed.
943    #[serde(rename = "resumed")]
944    Resumed,
945    /// The printer is waiting for a filament change.
946    #[serde(rename = "filament_change")]
947    FilamentChange,
948    /// A heater cooled down.
949    #[serde(rename = "heater_cooled")]
950    HeaterCooled,
951    /// A heater reached its target.
952    #[serde(rename = "heater_target")]
953    HeaterTarget,
954}
955
956/// Obico sent a printer notification.
957///
958/// The two instants are optional for the same reason
959/// `ObicoFailureAlertPayload`'s are, and are absent along with the rest of
960/// the print's fields when the notification is about no print at all.
961#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
962pub struct ObicoPrinterNotificationPayload {
963    /// When the print ended, when Obico reported an instant for it.
964    #[serde(default, skip_serializing_if = "Option::is_none")]
965    pub ended_at: Option<Timestamp>,
966    /// The file being printed, when the notification is about one.
967    #[serde(default, skip_serializing_if = "Option::is_none")]
968    pub file_name: Option<String>,
969    /// Which notification it is.
970    pub notification_type: ObicoNotificationType,
971    /// Obico's own identifier for the print, when the notification is about
972    /// one.
973    #[serde(default, skip_serializing_if = "Option::is_none")]
974    pub obico_print_id: Option<i64>,
975    /// When the print started, when Obico reported an instant for it.
976    #[serde(default, skip_serializing_if = "Option::is_none")]
977    pub started_at: Option<Timestamp>,
978}
979
980/// An operator acknowledged an event.
981#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
982pub struct OperatorAcknowledgementPayload {
983    /// The event being acknowledged.
984    pub acknowledged_event_id: EventId,
985    /// What the operator asked for next.
986    pub disposition: AcknowledgementDisposition,
987}
988
989/// Filament a tool fed after a filament change nobody has confirmed, held
990/// until
991/// somebody says which spool fed it.
992///
993/// A length rather than grams, because the grams follow the spool that takes
994/// it.
995#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
996pub struct PendingCharge {
997    /// The file's analysed length for the tool, in millimetres, when it had
998    /// one.
999    #[serde(default, skip_serializing_if = "Option::is_none")]
1000    pub analysed_length_mm: Option<f64>,
1001    /// The key of the print segment the filament was fed in.
1002    pub charge_key: ChargeKey,
1003    /// How much was fed, in millimetres.
1004    pub length_mm: f64,
1005    /// How that length was worked out.
1006    pub method: ChargeMethod,
1007    /// Anything a reader needs to make sense of the figure.
1008    #[serde(default, skip_serializing_if = "Option::is_none")]
1009    pub note: Option<String>,
1010    /// When it was held.
1011    pub pended_at: Timestamp,
1012    /// How far into the file the segment ran, when that was read.
1013    #[serde(default, skip_serializing_if = "Option::is_none")]
1014    pub position: Option<FilePosition>,
1015    /// The print it was fed in.
1016    pub print_id: PrintId,
1017    /// The tool that fed it.
1018    pub tool: i64,
1019}
1020
1021/// One pending charge a spool command took.
1022#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1023pub struct PendingTaken {
1024    /// The key it is charged under.
1025    pub charge_key: ChargeKey,
1026    /// The print it was fed in.
1027    pub print_id: PrintId,
1028}
1029
1030/// The supervisor looked in on an active print on its own schedule, with no
1031/// detector having said anything about it.
1032///
1033/// What a failure detector never alerts on — a progress that stopped moving,
1034/// a
1035/// heater whose target fell to nothing mid-print, a fan cut at a bridge, a
1036/// wall
1037/// going thin — is still on the machine and in the camera's frame, so every
1038/// active print is given one of these each interval and a turn on it. It
1039/// carries what the printer reported as it was taken; the frame the camera
1040/// gave
1041/// at the same moment, untagged by any detector, is this event's image.
1042#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1043pub struct PeriodicObservationPayload {
1044    /// How often these are taken, in whole seconds.
1045    pub interval_s: i64,
1046    /// The job the printer reported as the observation was taken, absent when
1047    /// it could not be read.
1048    #[serde(default, skip_serializing_if = "Option::is_none")]
1049    pub job: Option<JobSnapshot>,
1050    /// The printer's state as the observation was taken, absent when it could
1051    /// not be read — which the history records as a port failure.
1052    #[serde(default, skip_serializing_if = "Option::is_none")]
1053    pub printer: Option<PrinterSnapshot>,
1054}
1055
1056/// The decision policy took on one request.
1057#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1058pub enum PolicyDecision {
1059    /// The request may proceed.
1060    #[serde(rename = "accepted")]
1061    Accepted,
1062    /// The request may not, for this reason.
1063    #[serde(rename = "rejected")]
1064    Rejected(RejectionReason),
1065}
1066
1067/// A port failed while one event was being handled.
1068///
1069/// The event is named rather than implied, so that a reader holding an
1070/// event's
1071/// identifier reaches every failure recorded while that event was being
1072/// handled. A failure recorded here is one the handling survived: the event
1073/// is
1074/// already in the history by the time any of these sites is reached, and the
1075/// loop goes on to handle the next event.
1076#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1077pub struct PortFailurePayload {
1078    /// What the port said about it, in the port's own words.
1079    pub detail: String,
1080    /// The event whose handling reached the failing call.
1081    pub event_id: EventId,
1082    /// Where it failed.
1083    pub site: PortFailureSite,
1084}
1085
1086/// Where a port failed while an event was being handled.
1087///
1088/// A closed set of exactly the sites at which a failure has nowhere else to
1089/// be
1090/// recorded. The printer's action methods record theirs on the
1091/// `ActionRecord` the request minted, and a restoring
1092/// call records its own on the `Intervention` it was
1093/// expiring; those are not sites here, because a second record of them would
1094/// be
1095/// a second version of one fact.
1096#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1097pub enum PortFailureSite {
1098    /// Reading the printer's own state.
1099    #[serde(rename = "printer_snapshot")]
1100    PrinterSnapshot,
1101    /// Reading the job the printer reports it is running.
1102    #[serde(rename = "printer_job")]
1103    PrinterJob,
1104    /// Writing the image the event arrived with.
1105    #[serde(rename = "image_write")]
1106    ImageWrite,
1107    /// Running the supervision turn the event prompted.
1108    #[serde(rename = "supervision_turn")]
1109    SupervisionTurn,
1110    /// Taking a fresh frame from the camera, for a look at the print or for a
1111    /// periodic observation of it.
1112    #[serde(rename = "camera_look")]
1113    CameraLook,
1114    /// Telling the detector that paused the print its detection was handled.
1115    #[serde(rename = "detector_acknowledgement")]
1116    DetectorAcknowledgement,
1117}
1118
1119/// What a filament check found for one print.
1120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1121pub struct Preflight {
1122    /// When the check was made.
1123    pub checked_at: Timestamp,
1124    /// The file the print is of.
1125    pub file_name: String,
1126    /// What it found for each tool.
1127    pub tools: Vec<PreflightTool>,
1128    /// The worst of the tools' verdicts, in the order insufficient,
1129    /// unverified spool, no analysis, warned, overridden, unchecked, passed.
1130    pub verdict: PreflightVerdict,
1131}
1132
1133/// What a filament check answers.
1134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1135pub struct PreflightAnswer {
1136    /// What the check found, read now.
1137    pub preflight: Preflight,
1138}
1139
1140/// The filament check before a start was made.
1141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1142pub struct PreflightCheckedPayload {
1143    /// The start it was made for, when it was made for one.
1144    #[serde(default, skip_serializing_if = "Option::is_none")]
1145    pub action_id: Option<ActionId>,
1146    /// What the check found.
1147    pub preflight: Preflight,
1148}
1149
1150/// What a filament check found for one tool.
1151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1152pub struct PreflightTool {
1153    /// The margin asked for beyond that, in grams.
1154    #[serde(default, skip_serializing_if = "Option::is_none")]
1155    pub buffer_g: Option<f64>,
1156    /// The loaded spool's marks at the check, oldest first, whatever the
1157    /// verdict, so a spool both short and unverified says both.
1158    #[serde(default, skip_serializing_if = "Option::is_none")]
1159    pub marks: Option<Vec<UnverifiedMark>>,
1160    /// What the spool holds, in grams.
1161    #[serde(default, skip_serializing_if = "Option::is_none")]
1162    pub remaining_g: Option<f64>,
1163    /// What the print needs from it, in grams, when the file's analysis says.
1164    #[serde(default, skip_serializing_if = "Option::is_none")]
1165    pub required_g: Option<f64>,
1166    /// The spool loaded on it, when one is.
1167    #[serde(default, skip_serializing_if = "Option::is_none")]
1168    pub spool_id: Option<SpoolId>,
1169    /// That spool's name, beside its identifier, so a reader need not look it
1170    /// up.
1171    #[serde(default, skip_serializing_if = "Option::is_none")]
1172    pub spool_name: Option<String>,
1173    /// The tool, in the printer's own numbering.
1174    pub tool: i64,
1175    /// What the check found for this tool.
1176    pub verdict: PreflightVerdict,
1177}
1178
1179/// What a filament check found, for one tool or for a whole print.
1180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1181pub enum PreflightVerdict {
1182    /// The spool holds what the print needs and its buffer.
1183    #[serde(rename = "passed")]
1184    Passed,
1185    /// No tracked spool is loaded on the tool, so nothing was checked.
1186    #[serde(rename = "unchecked")]
1187    Unchecked,
1188    /// The spool holds less than the print needs and its buffer.
1189    #[serde(rename = "insufficient")]
1190    Insufficient,
1191    /// The spool's figure is in doubt.
1192    #[serde(rename = "unverified_spool")]
1193    UnverifiedSpool,
1194    /// `OctoPrint`'s analysis of the file had still not appeared when the
1195    /// start's wait for it elapsed, or has not appeared yet when the check is
1196    /// only read.
1197    #[serde(rename = "no_analysis")]
1198    NoAnalysis,
1199    /// The start went ahead despite the check, because the actor said so.
1200    #[serde(rename = "overridden")]
1201    Overridden,
1202    /// The check is set to warn, and let an insufficient, unverified or
1203    /// unanalysed start go ahead.
1204    #[serde(rename = "warned")]
1205    Warned,
1206}
1207
1208/// The whole vocabulary an actor may ask for, and there is no other.
1209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1210#[serde(tag = "action")]
1211pub enum PrintAction {
1212    /// Pause the print.
1213    #[serde(rename = "pause")]
1214    Pause {
1215        /// Who is asking.
1216        actor: Actor,
1217        /// Why the actor is asking for this.
1218        reason: String,
1219    },
1220    /// Resume the print.
1221    #[serde(rename = "resume")]
1222    Resume {
1223        /// Who is asking.
1224        actor: Actor,
1225        /// Why the actor is asking for this.
1226        reason: String,
1227    },
1228    /// Cancel the print.
1229    #[serde(rename = "cancel")]
1230    Cancel {
1231        /// Who is asking.
1232        actor: Actor,
1233        /// Why the actor is asking for this.
1234        reason: String,
1235    },
1236    /// Start a print of a named file, with a manifest.
1237    #[serde(rename = "start_print")]
1238    StartPrint {
1239        /// Who is asking.
1240        actor: Actor,
1241        /// The file to print, validated as a name a file API can be asked
1242        /// for.
1243        file_name: FileName,
1244        /// The manifest this print is bounded by.
1245        manifest: JobManifest,
1246        /// Start despite an insufficient or unverified spool, or a file
1247        /// `OctoPrint` has not analysed; the start's own reason is the
1248        /// override's reason.
1249        #[serde(default, skip_serializing_if = "Option::is_none")]
1250        override_filament_check: Option<bool>,
1251        /// Why the actor is asking for this.
1252        reason: String,
1253    },
1254    /// Set the feedrate factor, where one means one hundred percent.
1255    #[serde(rename = "set_feedrate_factor")]
1256    SetFeedrateFactor {
1257        /// Who is asking.
1258        actor: Actor,
1259        /// How long the change stands for, in whole seconds.
1260        #[serde(default, skip_serializing_if = "Option::is_none")]
1261        duration_s: Option<i64>,
1262        /// The multiplier asked for.
1263        factor: f64,
1264        /// Why the actor is asking for this.
1265        reason: String,
1266    },
1267    /// Set the flowrate factor, where one means one hundred percent.
1268    #[serde(rename = "set_flowrate_factor")]
1269    SetFlowrateFactor {
1270        /// Who is asking.
1271        actor: Actor,
1272        /// How long the change stands for, in whole seconds.
1273        #[serde(default, skip_serializing_if = "Option::is_none")]
1274        duration_s: Option<i64>,
1275        /// The multiplier asked for.
1276        factor: f64,
1277        /// Why the actor is asking for this.
1278        reason: String,
1279    },
1280    /// Set one tool's target temperature.
1281    #[serde(rename = "set_tool_target_c")]
1282    SetToolTargetC {
1283        /// Who is asking.
1284        actor: Actor,
1285        /// How long the change stands for, in whole seconds.
1286        #[serde(default, skip_serializing_if = "Option::is_none")]
1287        duration_s: Option<i64>,
1288        /// Why the actor is asking for this.
1289        reason: String,
1290        /// The target temperature asked for, in degrees Celsius.
1291        target_c: f64,
1292        /// The tool, in the printer's own numbering.
1293        tool: i64,
1294    },
1295    /// Set the bed's target temperature.
1296    #[serde(rename = "set_bed_target_c")]
1297    SetBedTargetC {
1298        /// Who is asking.
1299        actor: Actor,
1300        /// How long the change stands for, in whole seconds.
1301        #[serde(default, skip_serializing_if = "Option::is_none")]
1302        duration_s: Option<i64>,
1303        /// Why the actor is asking for this.
1304        reason: String,
1305        /// The target temperature asked for, in degrees Celsius.
1306        target_c: f64,
1307    },
1308    /// Set the part-cooling fan percentage.
1309    #[serde(rename = "set_fan_percent")]
1310    SetFanPercent {
1311        /// Who is asking.
1312        actor: Actor,
1313        /// How long the change stands for, in whole seconds.
1314        #[serde(default, skip_serializing_if = "Option::is_none")]
1315        duration_s: Option<i64>,
1316        /// The percentage asked for.
1317        percent: f64,
1318        /// Why the actor is asking for this.
1319        reason: String,
1320    },
1321    /// Acknowledge a failure event, with a disposition.
1322    #[serde(rename = "acknowledge_failure")]
1323    AcknowledgeFailure {
1324        /// Who is asking.
1325        actor: Actor,
1326        /// What to do next.
1327        disposition: AcknowledgementDisposition,
1328        /// The event being acknowledged.
1329        event_id: EventId,
1330        /// Why the actor is asking for this.
1331        reason: String,
1332    },
1333}
1334
1335/// Everything a supervision turn is given about one print.
1336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1337pub struct PrintContext {
1338    /// The limits in force, so that the agent can see them before it asks.
1339    pub bounds: EffectiveBounds,
1340    /// The interventions still active.
1341    pub interventions: Vec<Intervention>,
1342    /// The job, when a snapshot could be taken.
1343    #[serde(default, skip_serializing_if = "Option::is_none")]
1344    pub job: Option<JobSnapshot>,
1345    /// The most recent image, when there is one.
1346    #[serde(default, skip_serializing_if = "Option::is_none")]
1347    pub latest_image: Option<ImageRef>,
1348    /// The manifest, when the print has one.
1349    #[serde(default, skip_serializing_if = "Option::is_none")]
1350    pub manifest: Option<JobManifest>,
1351    /// The print itself.
1352    pub print: PrintRecord,
1353    /// The printer, when a snapshot could be taken.
1354    #[serde(default, skip_serializing_if = "Option::is_none")]
1355    pub printer: Option<PrinterSnapshot>,
1356    /// A bounded list of this print's events, newest first.
1357    pub recent_events: Vec<EventRecord>,
1358}
1359
1360/// One print's end, as its `print_ended` event stored it.
1361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1362pub struct PrintEnded {
1363    /// The `print_ended` event.
1364    pub event_id: EventId,
1365    /// How the print ended.
1366    pub outcome: PrintOutcome,
1367    /// When that event was received.
1368    pub received_at: Timestamp,
1369}
1370
1371/// A print ended.
1372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1373pub struct PrintEndedPayload {
1374    /// How far into its file it last read, when that was read.
1375    #[serde(default, skip_serializing_if = "Option::is_none")]
1376    pub last_position: Option<FilePosition>,
1377    /// How it ended.
1378    pub outcome: PrintOutcome,
1379    /// Why it ended, in the words the end was recorded with.
1380    pub reason: String,
1381    /// The state the printer was in as it ended.
1382    pub state: PrinterState,
1383}
1384
1385/// A lowercase hyphenated version 7 UUID identifying one print.
1386pub type PrintId = String;
1387
1388/// How a print ended.
1389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1390pub enum PrintOutcome {
1391    /// It ran to its end.
1392    #[serde(rename = "completed")]
1393    Completed,
1394    /// Somebody cancelled it.
1395    #[serde(rename = "cancelled")]
1396    Cancelled,
1397    /// It stopped on an error.
1398    #[serde(rename = "failed")]
1399    Failed,
1400}
1401
1402/// One print, and the record supervision keys from.
1403///
1404/// The provider's own print id is carried beside this record's identifier
1405/// rather than as it, because a print may be observed before the provider
1406/// that reports it has one.
1407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1408pub struct PrintRecord {
1409    /// Why it ended, if it has.
1410    #[serde(default, skip_serializing_if = "Option::is_none")]
1411    pub end_reason: Option<String>,
1412    /// When it ended, if it has.
1413    #[serde(default, skip_serializing_if = "Option::is_none")]
1414    pub ended_at: Option<Timestamp>,
1415    /// The file being printed, as the source reported it.
1416    #[serde(default, skip_serializing_if = "Option::is_none")]
1417    pub file_name: Option<String>,
1418    /// This print's identifier, minted by the store.
1419    pub id: PrintId,
1420    /// The longest running time, in whole seconds, the printer reported for
1421    /// this print's job at a read that found it printing rather than paused.
1422    ///
1423    /// The time a job has spent printing only grows, pause or no pause, so a
1424    /// job of this print's file reporting less — by more than
1425    /// `listing::JOB_IDENTITY_TOLERANCE_S` — is a later job. Absent until a
1426    /// read finds the job printing.
1427    #[serde(default, skip_serializing_if = "Option::is_none")]
1428    pub job_print_time_s: Option<i64>,
1429    /// When the printer's job this print records began, as the printer's own
1430    /// running time put it at the latest read that found the job running or
1431    /// paused: the instant of that read, less the running time it reported.
1432    ///
1433    /// `OctoPrint` counts a pause into the running time until the job resumes
1434    /// and then takes the pause back out, so a pause moves this later: a read
1435    /// putting it further than `listing::JOB_IDENTITY_TOLERANCE_S` from the
1436    /// one
1437    /// recorded resets it, rather than it being fixed once. Absent when no
1438    /// read has
1439    /// found this print's job reporting a running time — a print opened by an
1440    /// alert or a start before any read, and every print recorded before this
1441    /// was.
1442    #[serde(default, skip_serializing_if = "Option::is_none")]
1443    pub job_started_at: Option<Timestamp>,
1444    /// Every manifest range this print narrowed to the envelope's.
1445    pub narrowings: Vec<ManifestNarrowing>,
1446    /// When the print was opened.
1447    pub opened_at: Timestamp,
1448    /// The provider's own identifier for the print, whichever provider
1449    /// reported it, when one has.
1450    #[serde(default, skip_serializing_if = "Option::is_none")]
1451    pub provider_print_id: Option<i64>,
1452    /// The state the print is in.
1453    pub state: PrinterState,
1454}
1455
1456/// A print started.
1457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1458pub struct PrintStartedPayload {
1459    /// The file being printed, when it is known.
1460    #[serde(default, skip_serializing_if = "Option::is_none")]
1461    pub file_name: Option<String>,
1462    /// The spool loaded on each tool as it started.
1463    pub loaded: Vec<LoadedSpool>,
1464    /// The filament check made before it started, when one was.
1465    #[serde(default, skip_serializing_if = "Option::is_none")]
1466    pub preflight: Option<Preflight>,
1467    /// The action that started it; absent when the print was observed rather
1468    /// than started through this system.
1469    #[serde(default, skip_serializing_if = "Option::is_none")]
1470    pub started_by: Option<ActionId>,
1471}
1472
1473/// A print that was already open when spool tracking was installed: it is
1474/// never charged, and this says why it carries no charge.
1475#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1476pub struct PrintUntrackedPayload {
1477    /// Why the print carries no charge.
1478    pub reason: String,
1479}
1480
1481/// A printer, as a source reported it at one instant.
1482#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1483pub struct PrinterSnapshot {
1484    /// The bed heater, when the printer reports one.
1485    #[serde(default, skip_serializing_if = "Option::is_none")]
1486    pub bed: Option<HeaterSnapshot>,
1487    /// The chamber heater, when the printer reports one.
1488    #[serde(default, skip_serializing_if = "Option::is_none")]
1489    pub chamber: Option<HeaterSnapshot>,
1490    /// The state the printer is in.
1491    pub connection: PrinterState,
1492    /// The part-cooling fan, in percent.
1493    #[serde(default, skip_serializing_if = "Option::is_none")]
1494    pub fan_percent: Option<Reported>,
1495    /// The feedrate multiplier, where one means one hundred percent.
1496    #[serde(default, skip_serializing_if = "Option::is_none")]
1497    pub feedrate_factor: Option<Reported>,
1498    /// The flowrate multiplier, where one means one hundred percent.
1499    #[serde(default, skip_serializing_if = "Option::is_none")]
1500    pub flowrate_factor: Option<Reported>,
1501    /// The instant this observation was taken.
1502    pub observed_at: Timestamp,
1503    /// The tool heaters, indexed by tool number.
1504    pub tools: Vec<HeaterSnapshot>,
1505}
1506
1507/// The state a source reports a printer or a print to be in.
1508///
1509/// The `unknown` arm exists so that a state nobody anticipated is recorded
1510/// carrying the source's own word for it rather than lost.
1511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1512pub enum PrinterState {
1513    /// Connected and idle.
1514    #[serde(rename = "operational")]
1515    Operational,
1516    /// Printing, but paused.
1517    #[serde(rename = "paused")]
1518    Paused,
1519    /// Printing.
1520    #[serde(rename = "printing")]
1521    Printing,
1522    /// Cancelling a print.
1523    #[serde(rename = "cancelling")]
1524    Cancelling,
1525    /// In an error state.
1526    #[serde(rename = "error")]
1527    Error,
1528    /// Not reachable.
1529    #[serde(rename = "offline")]
1530    Offline,
1531    /// A state this vocabulary does not name, in the source's own word for
1532    /// it.
1533    #[serde(rename = "unknown")]
1534    Unknown(String),
1535}
1536
1537/// What the prints read answers: every print, and the one the printer is
1538/// running.
1539///
1540/// An open print's recorded `state` is the state it was opened in, which is
1541/// `printing` whether or not the machine is paused now; what the machine is
1542/// doing is the status read's answer.
1543#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1544pub struct PrintsAnswer {
1545    /// The print the printer's current job belongs to, present exactly when
1546    /// the
1547    /// printer reports a job it is printing or has paused. Absent when the
1548    /// printer could not be read.
1549    #[serde(default, skip_serializing_if = "Option::is_none")]
1550    pub active: Option<PrintId>,
1551    /// Every print this server holds, ended or not, most recently opened
1552    /// first.
1553    pub prints: Vec<PrintRecord>,
1554}
1555
1556/// An inclusive pair of 64-bit floats.
1557#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1558pub struct Range {
1559    /// The highest value the range admits, inclusive.
1560    pub max: f64,
1561    /// The lowest value the range admits, inclusive.
1562    pub min: f64,
1563}
1564
1565/// Bytes exactly as received, base64-encoded.
1566pub type RawBytes = String;
1567
1568/// What became of the prints a file's new tally generation counts.
1569#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1570pub enum RebaselineDisposition {
1571    /// They were accounted as prints this system never saw.
1572    #[serde(rename = "accounted")]
1573    Accounted,
1574    /// It could not be told which of them this system already settled, so the
1575    /// loaded spool was marked unverified rather than charged on a guess.
1576    #[serde(rename = "unverified")]
1577    Unverified,
1578}
1579
1580/// Why a request was refused.
1581///
1582/// Each rejection is a distinct variant, so a consumer distinguishes them by
1583/// matching rather than by reading a message.
1584#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1585pub enum RejectionReason {
1586    /// The value asked for is outside the range allowed for that adjustable.
1587    #[serde(rename = "out_of_bounds")]
1588    OutOfBounds {
1589        /// The adjustable that was asked for.
1590        adjustable: Adjustable,
1591        /// The range that was allowed.
1592        allowed: Range,
1593        /// The value that was asked for.
1594        requested: f64,
1595    },
1596    /// This actor class may not request this action at all.
1597    #[serde(rename = "actor_may_not_request")]
1598    ActorMayNotRequest {
1599        /// The action they asked for.
1600        action: ActionKind,
1601        /// The class of the actor that asked.
1602        actor_class: ActorClass,
1603    },
1604    /// There is no active print to act on.
1605    #[serde(rename = "no_active_print")]
1606    NoActivePrint,
1607    /// The printer is not in a state this action is valid from.
1608    #[serde(rename = "invalid_from_state")]
1609    InvalidFromState {
1610        /// The state the printer is in.
1611        state: PrinterState,
1612    },
1613    /// The agent's minimum interval has not elapsed.
1614    #[serde(rename = "min_interval_not_elapsed")]
1615    MinIntervalNotElapsed {
1616        /// The minimum interval, in whole seconds.
1617        interval_s: i64,
1618        /// How long it has been since the last agent action, in whole
1619        /// seconds.
1620        since_last_s: i64,
1621    },
1622    /// The adjustable is not one this printer has.
1623    #[serde(rename = "unsupported_adjustable")]
1624    UnsupportedAdjustable {
1625        /// The adjustable this printer cannot express.
1626        adjustable: Adjustable,
1627    },
1628    /// A start was refused because the spool on one tool holds less than the
1629    /// print needs and its buffer.
1630    #[serde(rename = "insufficient_filament")]
1631    InsufficientFilament {
1632        /// The margin asked for beyond that, in grams.
1633        buffer_g: f64,
1634        /// What the spool holds, in grams.
1635        remaining_g: f64,
1636        /// What the print needs from it, in grams.
1637        required_g: f64,
1638        /// The spool loaded on it.
1639        spool_id: SpoolId,
1640        /// That spool's name.
1641        spool_name: String,
1642        /// The tool whose spool is short.
1643        tool: i64,
1644        /// The spool's oldest mark, when the short spool is also unverified.
1645        #[serde(default, skip_serializing_if = "Option::is_none")]
1646        unverified: Option<UnverifiedReason>,
1647    },
1648    /// A start was refused because the spool on one tool is unverified.
1649    #[serde(rename = "unverified_spool")]
1650    UnverifiedSpool {
1651        /// The spool's oldest mark.
1652        reason: UnverifiedReason,
1653        /// The spool loaded on it.
1654        spool_id: SpoolId,
1655        /// That spool's name.
1656        spool_name: String,
1657        /// The tool whose spool is unverified.
1658        tool: i64,
1659    },
1660    /// A start was refused because `OctoPrint`'s analysis of the file had not
1661    /// appeared when the start's wait for it elapsed.
1662    #[serde(rename = "analysis_unavailable")]
1663    AnalysisUnavailable {
1664        /// The file that has no analysis.
1665        file_name: String,
1666        /// How long the start waited for one, in whole seconds.
1667        waited_s: i64,
1668    },
1669    /// A spool command could not be carried out.
1670    #[serde(rename = "spool_refused")]
1671    SpoolRefused {
1672        /// Why, in a sentence naming what to do instead.
1673        detail: String,
1674    },
1675}
1676
1677/// A value as a source reported it, flagged when it is outside the
1678/// plausibility range this crate declares for its field.
1679#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1680pub struct Reported {
1681    /// One shape of the contracts, which say nothing more about it.
1682    pub out_of_range: bool,
1683    /// One shape of the contracts, which say nothing more about it.
1684    pub value: f64,
1685}
1686
1687/// A spool's remaining figure was corrected.
1688#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1689pub struct SpoolAdjustedPayload {
1690    /// Who corrected it.
1691    pub actor: Actor,
1692    /// Every mark the correction cleared.
1693    #[serde(default, skip_serializing_if = "Option::is_none")]
1694    pub cleared_marks: Option<Vec<ClearedMark>>,
1695    /// The scale reading the figure was worked out from, when it was given as
1696    /// one.
1697    #[serde(default, skip_serializing_if = "Option::is_none")]
1698    pub gross_g: Option<f64>,
1699    /// Why.
1700    pub reason: String,
1701    /// Its figure after, in grams.
1702    pub remaining_after_g: f64,
1703    /// Its figure before, in grams, after any pending charge it took.
1704    pub remaining_before_g: f64,
1705    /// The spool corrected.
1706    pub spool_id: SpoolId,
1707    /// The pending charges the correction took first, each also its own
1708    /// `filament_consumed` event under its key.
1709    #[serde(default, skip_serializing_if = "Option::is_none")]
1710    pub took_pending: Option<Vec<PendingTaken>>,
1711}
1712
1713/// What one spool's read answers: the spool, and the newest of the printer's
1714/// own events that name it.
1715#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1716pub struct SpoolAnswer {
1717    /// The newest timeline events naming it, newest first.
1718    pub recent: Vec<EventRecord>,
1719    /// The spool, retired or not.
1720    pub spool: SpoolRecord,
1721}
1722
1723/// The whole vocabulary of the spool ledger, and there is no other.
1724#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1725#[serde(tag = "command")]
1726pub enum SpoolCommand {
1727    /// Register a spool.
1728    #[serde(rename = "register_spool")]
1729    RegisterSpool {
1730        /// Who is asking.
1731        actor: Actor,
1732        /// The filament's colour.
1733        #[serde(default, skip_serializing_if = "Option::is_none")]
1734        color: Option<String>,
1735        /// The material's density, in grams per cubic centimetre; the known
1736        /// density of the material when absent, and a material with none
1737        /// known
1738        /// is refused.
1739        #[serde(default, skip_serializing_if = "Option::is_none")]
1740        density_g_cm3: Option<f64>,
1741        /// The filament's diameter, in millimetres; 1.75 when absent.
1742        #[serde(default, skip_serializing_if = "Option::is_none")]
1743        diameter_mm: Option<f64>,
1744        /// The net filament on the spool when full, in grams.
1745        initial_g: f64,
1746        /// The material, such as `PLA`.
1747        material: String,
1748        /// The operator's own label for it.
1749        name: String,
1750        /// Why the actor is asking for this.
1751        reason: String,
1752        /// The filament on it now, in grams; the full figure when absent.
1753        #[serde(default, skip_serializing_if = "Option::is_none")]
1754        remaining_g: Option<f64>,
1755        /// The empty spool's own weight, in grams.
1756        #[serde(default, skip_serializing_if = "Option::is_none")]
1757        tare_g: Option<f64>,
1758    },
1759    /// Load a spool onto a tool.
1760    ///
1761    /// Onto an occupied tool it is a swap, and the spool there is unloaded in
1762    /// the same write. Naming the spool already on the tool confirms it was
1763    /// never swapped, which is accepted only while that tool holds a pending
1764    /// charge or that spool holds a filament-change mark.
1765    #[serde(rename = "load_spool")]
1766    LoadSpool {
1767        /// Who is asking.
1768        actor: Actor,
1769        /// Why the actor is asking for this.
1770        reason: String,
1771        /// The spool to load.
1772        spool_id: SpoolId,
1773        /// The tool, in the printer's own numbering.
1774        tool: i64,
1775    },
1776    /// Unload a spool from its tool.
1777    #[serde(rename = "unload_spool")]
1778    UnloadSpool {
1779        /// Who is asking.
1780        actor: Actor,
1781        /// Why the actor is asking for this.
1782        reason: String,
1783        /// The spool to unload.
1784        spool_id: SpoolId,
1785    },
1786    /// Correct a spool's remaining figure, from exactly one of the two; a
1787    /// command naming both or neither is refused as `spool_refused`.
1788    #[serde(rename = "adjust_spool")]
1789    AdjustSpool {
1790        /// Who is asking.
1791        actor: Actor,
1792        /// A scale reading of the spool, in grams, which needs its tare.
1793        #[serde(default, skip_serializing_if = "Option::is_none")]
1794        gross_g: Option<f64>,
1795        /// Why the actor is asking for this.
1796        reason: String,
1797        /// The filament left, in grams.
1798        #[serde(default, skip_serializing_if = "Option::is_none")]
1799        remaining_g: Option<f64>,
1800        /// The spool to correct.
1801        spool_id: SpoolId,
1802    },
1803    /// Retire a spool, after which it refuses every command.
1804    #[serde(rename = "retire_spool")]
1805    RetireSpool {
1806        /// Who is asking.
1807        actor: Actor,
1808        /// Why the actor is asking for this.
1809        reason: String,
1810        /// The spool to retire.
1811        spool_id: SpoolId,
1812    },
1813}
1814
1815/// What a spool command answers, accepted or refused.
1816///
1817/// A refusal is the same shape and deliberately so: the decision carries the
1818/// grant or the ledger's own reason, and the events are the record the
1819/// refusal
1820/// wrote.
1821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1822pub struct SpoolCommandAnswer {
1823    /// The decision taken on the command.
1824    pub decision: PolicyDecision,
1825    /// Every event it wrote, in the order they were written.
1826    pub events: Vec<EventRecord>,
1827    /// The spool it was about, as the command left it, when there is one.
1828    #[serde(default, skip_serializing_if = "Option::is_none")]
1829    pub spool: Option<SpoolRecord>,
1830}
1831
1832/// A spool command was refused.
1833#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1834pub struct SpoolCommandRejectedPayload {
1835    /// The command, as it was asked for.
1836    pub command: SpoolCommand,
1837    /// The whole decision, carrying which refusal it was.
1838    pub decision: PolicyDecision,
1839}
1840
1841/// A lowercase hyphenated version 7 UUID identifying one registered spool.
1842pub type SpoolId = String;
1843
1844/// A spool was loaded onto a tool, or confirmed as never having left it.
1845#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1846pub struct SpoolLoadedPayload {
1847    /// Who loaded it.
1848    pub actor: Actor,
1849    /// Every mark the load cleared, on either spool.
1850    #[serde(default, skip_serializing_if = "Option::is_none")]
1851    pub cleared_marks: Option<Vec<ClearedMark>>,
1852    /// Why.
1853    pub reason: String,
1854    /// The spool the load swapped off the tool, when it was occupied.
1855    #[serde(default, skip_serializing_if = "Option::is_none")]
1856    pub replaced: Option<SpoolId>,
1857    /// The spool loaded.
1858    pub spool_id: SpoolId,
1859    /// The pending charges the load took, each also its own
1860    /// `filament_consumed` event under its key.
1861    #[serde(default, skip_serializing_if = "Option::is_none")]
1862    pub took_pending: Option<Vec<PendingTaken>>,
1863    /// The tool, in the printer's own numbering.
1864    pub tool: i64,
1865}
1866
1867/// One spool the operator registered, and what is known of it now.
1868#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1869pub struct SpoolRecord {
1870    /// The filament's colour, when the operator gave one.
1871    #[serde(default, skip_serializing_if = "Option::is_none")]
1872    pub color: Option<String>,
1873    /// The material's density, in grams per cubic centimetre; above zero.
1874    pub density_g_cm3: f64,
1875    /// The filament's diameter, in millimetres; above zero.
1876    pub diameter_mm: f64,
1877    /// This spool's identifier, minted when it was registered.
1878    pub id: SpoolId,
1879    /// The net filament on the spool when it was full, in grams; above zero.
1880    pub initial_g: f64,
1881    /// The tool the spool is loaded on, when it is loaded.
1882    #[serde(default, skip_serializing_if = "Option::is_none")]
1883    pub loaded_on_tool: Option<i64>,
1884    /// The material, as given and trimmed, such as `PLA`; never empty.
1885    pub material: String,
1886    /// The operator's own label for the spool; never empty.
1887    pub name: String,
1888    /// When the spool was registered.
1889    pub registered_at: Timestamp,
1890    /// The filament left, in grams.
1891    ///
1892    /// Always the initial figure less every charge plus every correction. A
1893    /// charge is never refused or clamped, so this may go below zero.
1894    pub remaining_g: f64,
1895    /// When the spool was retired, if it has been.
1896    #[serde(default, skip_serializing_if = "Option::is_none")]
1897    pub retired_at: Option<Timestamp>,
1898    /// The empty spool's own weight, in grams, when the operator gave it.
1899    #[serde(default, skip_serializing_if = "Option::is_none")]
1900    pub tare_g: Option<f64>,
1901    /// How far the remaining figure can be trusted.
1902    pub trust: SpoolTrust,
1903    /// When anything about the spool last changed.
1904    pub updated_at: Timestamp,
1905}
1906
1907/// A spool was registered.
1908#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1909pub struct SpoolRegisteredPayload {
1910    /// Who registered it.
1911    pub actor: Actor,
1912    /// Why.
1913    pub reason: String,
1914    /// The spool, as it was registered.
1915    pub spool: SpoolRecord,
1916}
1917
1918/// A spool was retired.
1919#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1920pub struct SpoolRetiredPayload {
1921    /// Who retired it.
1922    pub actor: Actor,
1923    /// Why.
1924    pub reason: String,
1925    /// Its figure as it was retired, in grams.
1926    pub remaining_g: f64,
1927    /// The spool retired.
1928    pub spool_id: SpoolId,
1929}
1930
1931/// How far a spool's remaining figure can be trusted.
1932#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1933#[serde(tag = "state")]
1934pub enum SpoolTrust {
1935    /// Every charge against the spool was taken exactly.
1936    #[serde(rename = "verified")]
1937    Verified,
1938    /// At least one thing happened that could not be charged exactly.
1939    #[serde(rename = "unverified")]
1940    Unverified {
1941        /// Every reason the figure is in doubt, oldest first, at most one per
1942        /// distinct reason; never empty.
1943        marks: Vec<UnverifiedMark>,
1944    },
1945}
1946
1947/// A spool was unloaded from its tool.
1948#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1949pub struct SpoolUnloadedPayload {
1950    /// Who unloaded it.
1951    pub actor: Actor,
1952    /// Why.
1953    pub reason: String,
1954    /// The spool unloaded.
1955    pub spool_id: SpoolId,
1956    /// The tool it left.
1957    pub tool: i64,
1958}
1959
1960/// A spool was marked as one whose figure is in doubt.
1961#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1962pub struct SpoolUnverifiedPayload {
1963    /// Why its figure is in doubt.
1964    pub reason: UnverifiedReason,
1965    /// The spool marked.
1966    pub spool_id: SpoolId,
1967}
1968
1969/// What the spools read answers.
1970///
1971/// `at_a_glance` is one line per spool, in the order `spools` lists them,
1972/// composed here so that what is left on each spool is the first thing a
1973/// reader of either rendering meets rather than a figure inside a whole
1974/// record. Its name is what puts it first: both renderings walk an answer's
1975/// fields in name order, ahead of `pending` and `spools`.
1976#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1977pub struct SpoolsAnswer {
1978    /// One line per spool in `spools`, in the same order.
1979    pub at_a_glance: Vec<String>,
1980    /// Every charge held after a filament change nobody has confirmed, oldest
1981    /// first: what the next spool loaded on that tool is charged.
1982    pub pending: Vec<PendingCharge>,
1983    /// Every spool, retired ones only when asked for, most recently
1984    /// registered
1985    /// first.
1986    pub spools: Vec<SpoolRecord>,
1987}
1988
1989/// What one restart put back the way it found it.
1990///
1991/// A supervisor that has been restarted adopts whatever the store holds
1992/// rather
1993/// than starting empty, and each of these is one of those adoptions. They are
1994/// recorded rather than merely done, because a print that carried on across a
1995/// restart and one that was started again look identical afterwards unless
1996/// the
1997/// history says which happened.
1998#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1999pub enum StartupOutcome {
2000    /// A print left open was adopted as the print this supervisor is
2001    /// watching.
2002    #[serde(rename = "print_adopted")]
2003    PrintAdopted,
2004    /// A session left open was resumed rather than replaced.
2005    #[serde(rename = "session_resumed")]
2006    SessionResumed {
2007        /// The session's own name in the harness.
2008        session_name: String,
2009    },
2010    /// An intervention already past its expiry was expired on start.
2011    #[serde(rename = "intervention_expired")]
2012    InterventionExpired {
2013        /// What it had changed.
2014        adjustable: Adjustable,
2015        /// The intervention that had outlived its bound.
2016        intervention_id: InterventionId,
2017        /// What became of putting the prior value back.
2018        outcome: InterventionOutcome,
2019    },
2020}
2021
2022/// A supervisor reconciled one thing the store held when it started.
2023#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2024pub struct StartupReconciliationPayload {
2025    /// What was reconciled.
2026    pub outcome: StartupOutcome,
2027    /// The print it is about.
2028    pub print_id: PrintId,
2029}
2030
2031/// What a status read answers.
2032#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2033pub struct StatusAnswer {
2034    /// The bounded interventions still in force.
2035    pub interventions: Vec<Intervention>,
2036    /// The job, when a snapshot could be taken.
2037    #[serde(default, skip_serializing_if = "Option::is_none")]
2038    pub job: Option<JobSnapshot>,
2039    /// The print.
2040    pub print: PrintRecord,
2041    /// The printer, when a snapshot could be taken.
2042    #[serde(default, skip_serializing_if = "Option::is_none")]
2043    pub printer: Option<PrinterSnapshot>,
2044    /// The supervision session watching it, when one is.
2045    #[serde(default, skip_serializing_if = "Option::is_none")]
2046    pub session: Option<SupervisionSession>,
2047}
2048
2049/// The supervision session keyed to one print.
2050#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2051pub struct SupervisionSession {
2052    /// Why it was closed, if it was.
2053    #[serde(default, skip_serializing_if = "Option::is_none")]
2054    pub close_reason: Option<String>,
2055    /// When the session was closed, if it was.
2056    #[serde(default, skip_serializing_if = "Option::is_none")]
2057    pub closed_at: Option<Timestamp>,
2058    /// When the session was opened.
2059    pub created_at: Timestamp,
2060    /// The identity the harness ran the session under.
2061    pub harness_identity: String,
2062    /// When the last turn ran.
2063    pub last_turn_at: Timestamp,
2064    /// The print this session watches.
2065    pub print_id: PrintId,
2066    /// The session's own name in the harness.
2067    pub session_name: String,
2068}
2069
2070/// A supervision session was closed.
2071#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2072pub struct SupervisionSessionClosedPayload {
2073    /// Why it was closed.
2074    pub close_reason: String,
2075    /// The session's own name in the harness.
2076    pub session_name: String,
2077}
2078
2079/// A supervision session was opened.
2080#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2081pub struct SupervisionSessionOpenedPayload {
2082    /// The identity the harness ran it under.
2083    pub harness_identity: String,
2084    /// The session's own name in the harness.
2085    pub session_name: String,
2086}
2087
2088/// What a printer history read answers: one page of the printer's own events.
2089#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2090pub struct TimelineAnswer {
2091    /// The events, newest first.
2092    pub events: Vec<EventRecord>,
2093    /// The cursor for the following, older page, given as `before`; absent
2094    /// when there is none.
2095    #[serde(default, skip_serializing_if = "Option::is_none")]
2096    pub next: Option<EventId>,
2097}
2098
2099/// An instant in UTC, as an RFC 3339 string with a zero offset.
2100pub type Timestamp = String;
2101
2102/// The filament one tool feeds, as a length and, when it is known, a volume.
2103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2104pub struct ToolFilament {
2105    /// The length of filament fed, in millimetres.
2106    pub length_mm: f64,
2107    /// The tool, in the printer's own numbering.
2108    pub tool: i64,
2109    /// The volume of filament fed, in cubic centimetres, when the source
2110    /// reported one.
2111    #[serde(default, skip_serializing_if = "Option::is_none")]
2112    pub volume_cm3: Option<f64>,
2113}
2114
2115/// A print this system never saw was found in the printer's own tally.
2116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2117pub struct UnaccountedPrintPayload {
2118    /// Whether it was charged against a spool.
2119    pub charged: bool,
2120    /// The file that was printed.
2121    pub file_name: String,
2122    /// When it was printed, when the printer says.
2123    #[serde(default, skip_serializing_if = "Option::is_none")]
2124    pub printed_at: Option<Timestamp>,
2125    /// Whether it completed.
2126    pub success: bool,
2127}
2128
2129/// One reason a spool's figure is in doubt, and since when.
2130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2131pub struct UnverifiedMark {
2132    /// Why the figure is in doubt.
2133    pub reason: UnverifiedReason,
2134    /// When the spool was marked for it.
2135    pub since: Timestamp,
2136}
2137
2138/// Why a spool's figure is in doubt.
2139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2140#[serde(tag = "reason")]
2141pub enum UnverifiedReason {
2142    /// A print ran on the spool's tool and nothing could be charged, because
2143    /// the file had no analysis — an SD-card file, for one.
2144    #[serde(rename = "uncharged_print")]
2145    UnchargedPrint {
2146        /// The print nothing could be charged for.
2147        print_id: PrintId,
2148    },
2149    /// A print this system never saw ran and did not complete.
2150    #[serde(rename = "unaccounted_print")]
2151    UnaccountedPrint {
2152        /// The file that was printed.
2153        file_name: String,
2154        /// When it was printed.
2155        printed_at: Timestamp,
2156    },
2157    /// A filament change was signalled and nobody has said which spool is now
2158    /// loaded. The spool was charged up to a position read when the signal
2159    /// was
2160    /// handled.
2161    #[serde(rename = "filament_change_unconfirmed")]
2162    FilamentChangeUnconfirmed {
2163        /// The print the change was signalled during.
2164        print_id: PrintId,
2165    },
2166    /// A filament change was signalled, nobody has said which spool is now
2167    /// loaded, and no fresh position could be read at the signal: the spool
2168    /// was
2169    /// charged only up to the last recorded position, so its figure may be
2170    /// overstated.
2171    #[serde(rename = "filament_change_unconfirmed_position")]
2172    FilamentChangeUnconfirmedPosition {
2173        /// The print the change was signalled during.
2174        print_id: PrintId,
2175    },
2176    /// A file's print tally was reset, and it cannot be told which of the
2177    /// prints since the re-upload were already settled.
2178    #[serde(rename = "tally_reset")]
2179    TallyReset {
2180        /// The file whose tally was reset.
2181        file_name: String,
2182        /// Where the file lives, in the printer's own vocabulary.
2183        origin: String,
2184    },
2185}
2186
2187/// One payload type declared under a kind name, by the domain that owns it.
2188///
2189/// The table from kind name to payload type, as the server declares it: every
2190/// kind below has exactly one implementation here, and a kind this client
2191/// does not know flows through `EventRecord` untouched.
2192pub trait EventPayloadKind: serde::de::DeserializeOwned {
2193    /// The name this payload's events are written down under.
2194    const KIND: &'static str;
2195}
2196
2197impl EventPayloadKind for ActionExecutedPayload {
2198    const KIND: &'static str = "action_executed";
2199}
2200
2201impl EventPayloadKind for ActionRejectedPayload {
2202    const KIND: &'static str = "action_rejected";
2203}
2204
2205impl EventPayloadKind for ActionRequestedPayload {
2206    const KIND: &'static str = "action_requested";
2207}
2208
2209impl EventPayloadKind for AgentAssessmentPayload {
2210    const KIND: &'static str = "agent_assessment";
2211}
2212
2213impl EventPayloadKind for CameraLookPayload {
2214    const KIND: &'static str = "camera_look";
2215}
2216
2217impl EventPayloadKind for ChargePendedPayload {
2218    const KIND: &'static str = "charge_pended";
2219}
2220
2221impl EventPayloadKind for FilamentChangeSignalledPayload {
2222    const KIND: &'static str = "filament_change_signalled";
2223}
2224
2225impl EventPayloadKind for FilamentConsumedPayload {
2226    const KIND: &'static str = "filament_consumed";
2227}
2228
2229impl EventPayloadKind for FileTallyRebaselinedPayload {
2230    const KIND: &'static str = "file_tally_rebaselined";
2231}
2232
2233impl EventPayloadKind for InterventionExpiredPayload {
2234    const KIND: &'static str = "intervention_expired";
2235}
2236
2237impl EventPayloadKind for MalformedExternalEventPayload {
2238    const KIND: &'static str = "malformed_external_event";
2239}
2240
2241impl EventPayloadKind for ObicoFailureAlertPayload {
2242    const KIND: &'static str = "obico_failure_alert";
2243}
2244
2245impl EventPayloadKind for ObicoPrinterNotificationPayload {
2246    const KIND: &'static str = "obico_printer_notification";
2247}
2248
2249impl EventPayloadKind for OperatorAcknowledgementPayload {
2250    const KIND: &'static str = "operator_acknowledgement";
2251}
2252
2253impl EventPayloadKind for PeriodicObservationPayload {
2254    const KIND: &'static str = "periodic_observation";
2255}
2256
2257impl EventPayloadKind for PortFailurePayload {
2258    const KIND: &'static str = "port_failure";
2259}
2260
2261impl EventPayloadKind for PreflightCheckedPayload {
2262    const KIND: &'static str = "preflight_checked";
2263}
2264
2265impl EventPayloadKind for PrintEndedPayload {
2266    const KIND: &'static str = "print_ended";
2267}
2268
2269impl EventPayloadKind for PrintStartedPayload {
2270    const KIND: &'static str = "print_started";
2271}
2272
2273impl EventPayloadKind for PrintUntrackedPayload {
2274    const KIND: &'static str = "print_untracked";
2275}
2276
2277impl EventPayloadKind for SpoolAdjustedPayload {
2278    const KIND: &'static str = "spool_adjusted";
2279}
2280
2281impl EventPayloadKind for SpoolCommandRejectedPayload {
2282    const KIND: &'static str = "spool_command_rejected";
2283}
2284
2285impl EventPayloadKind for SpoolLoadedPayload {
2286    const KIND: &'static str = "spool_loaded";
2287}
2288
2289impl EventPayloadKind for SpoolRegisteredPayload {
2290    const KIND: &'static str = "spool_registered";
2291}
2292
2293impl EventPayloadKind for SpoolRetiredPayload {
2294    const KIND: &'static str = "spool_retired";
2295}
2296
2297impl EventPayloadKind for SpoolUnloadedPayload {
2298    const KIND: &'static str = "spool_unloaded";
2299}
2300
2301impl EventPayloadKind for SpoolUnverifiedPayload {
2302    const KIND: &'static str = "spool_unverified";
2303}
2304
2305impl EventPayloadKind for StartupReconciliationPayload {
2306    const KIND: &'static str = "startup_reconciliation";
2307}
2308
2309impl EventPayloadKind for SupervisionSessionClosedPayload {
2310    const KIND: &'static str = "supervision_session_closed";
2311}
2312
2313impl EventPayloadKind for SupervisionSessionOpenedPayload {
2314    const KIND: &'static str = "supervision_session_opened";
2315}
2316
2317impl EventPayloadKind for UnaccountedPrintPayload {
2318    const KIND: &'static str = "unaccounted_print";
2319}
2320
2321/// Every kind the server declares a payload type for, in name order.
2322pub const EVENT_KINDS: [&str; 31] = [
2323    "action_executed",
2324    "action_rejected",
2325    "action_requested",
2326    "agent_assessment",
2327    "camera_look",
2328    "charge_pended",
2329    "filament_change_signalled",
2330    "filament_consumed",
2331    "file_tally_rebaselined",
2332    "intervention_expired",
2333    "malformed_external_event",
2334    "obico_failure_alert",
2335    "obico_printer_notification",
2336    "operator_acknowledgement",
2337    "periodic_observation",
2338    "port_failure",
2339    "preflight_checked",
2340    "print_ended",
2341    "print_started",
2342    "print_untracked",
2343    "spool_adjusted",
2344    "spool_command_rejected",
2345    "spool_loaded",
2346    "spool_registered",
2347    "spool_retired",
2348    "spool_unloaded",
2349    "spool_unverified",
2350    "startup_reconciliation",
2351    "supervision_session_closed",
2352    "supervision_session_opened",
2353    "unaccounted_print",
2354];
2355
2356impl EventRecord {
2357    /// The payload as `P`, when this event is under the kind `P` declares.
2358    ///
2359    /// Answers `None` for an event of any other kind — one this client knows
2360    /// or one it does not — and the parse failure for an event under the
2361    /// right kind whose payload is not of the type.
2362    #[must_use]
2363    pub fn payload_as<P: EventPayloadKind>(&self) -> Option<Result<P, serde_json::Error>> {
2364        (self.kind == P::KIND).then(|| serde_json::from_value(self.payload.clone()))
2365    }
2366}
2367
2368impl Client {
2369    /// Call `prints` on the configured supervisor.
2370    ///
2371    /// # Errors
2372    ///
2373    /// Returns `ClientError` when the supervisor could not be reached, when
2374    /// it answered something this client cannot read.
2375    // llmlint: ignore[names_match_behavior] See suppressions.toml.
2376    pub fn prints(&self) -> Result<PrintsAnswer, ClientError> {
2377        let target = String::from("/v1/prints");
2378        let asked: Vec<(String, String)> = Vec::new();
2379        let sending: Option<serde_json::Value> = None;
2380        self.call("GET", &target, &asked, sending.as_ref())
2381    }
2382
2383    /// Call `status` on the configured supervisor.
2384    ///
2385    /// # Errors
2386    ///
2387    /// Returns `ClientError` when the supervisor could not be reached, when
2388    /// it answered something this client cannot read.
2389    pub fn status(&self, print_id: &str) -> Result<StatusAnswer, ClientError> {
2390        let target = format!("/v1/prints/{print_id}/status");
2391        let asked: Vec<(String, String)> = Vec::new();
2392        let sending: Option<serde_json::Value> = None;
2393        self.call("GET", &target, &asked, sending.as_ref())
2394    }
2395
2396    /// Call `context` on the configured supervisor.
2397    ///
2398    /// # Errors
2399    ///
2400    /// Returns `ClientError` when the supervisor could not be reached, when
2401    /// it answered something this client cannot read.
2402    pub fn context(&self, print_id: &str) -> Result<ContextAnswer, ClientError> {
2403        let target = format!("/v1/prints/{print_id}/context");
2404        let asked: Vec<(String, String)> = Vec::new();
2405        let sending: Option<serde_json::Value> = None;
2406        self.call("GET", &target, &asked, sending.as_ref())
2407    }
2408
2409    /// Call `image` on the configured supervisor.
2410    ///
2411    /// # Errors
2412    ///
2413    /// Returns `ClientError` when the supervisor could not be reached, when
2414    /// it answered something this client cannot read.
2415    pub fn image(&self, image_id: &str) -> Result<ImageAnswer, ClientError> {
2416        let target = format!("/v1/images/{image_id}");
2417        let asked: Vec<(String, String)> = Vec::new();
2418        let sending: Option<serde_json::Value> = None;
2419        self.call("GET", &target, &asked, sending.as_ref())
2420    }
2421
2422    /// Call `history` on the configured supervisor.
2423    ///
2424    /// # Errors
2425    ///
2426    /// Returns `ClientError` when the supervisor could not be reached, when
2427    /// it answered something this client cannot read.
2428    pub fn history(
2429        &self,
2430        print_id: &str,
2431        limit: Option<i64>,
2432    ) -> Result<HistoryAnswer, ClientError> {
2433        let target = format!("/v1/prints/{print_id}/history");
2434        let mut asked: Vec<(String, String)> = Vec::new();
2435        if let Some(value) = limit {
2436            asked.push(("limit".to_owned(), value.to_string()));
2437        }
2438        let sending: Option<serde_json::Value> = None;
2439        self.call("GET", &target, &asked, sending.as_ref())
2440    }
2441
2442    /// Call `events` on the configured supervisor.
2443    ///
2444    /// # Errors
2445    ///
2446    /// Returns `ClientError` when the supervisor could not be reached, when
2447    /// it answered something this client cannot read.
2448    pub fn events(
2449        &self,
2450        after: Option<String>,
2451        print_id: Option<String>,
2452        kinds: Option<Vec<String>>,
2453        exclude_kinds: Option<Vec<String>>,
2454        wait_s: Option<i64>,
2455        limit: Option<i64>,
2456    ) -> Result<EventsAnswer, ClientError> {
2457        let target = String::from("/v1/events");
2458        let mut asked: Vec<(String, String)> = Vec::new();
2459        if let Some(value) = after {
2460            asked.push(("after".to_owned(), value));
2461        }
2462        if let Some(value) = print_id {
2463            asked.push(("print_id".to_owned(), value));
2464        }
2465        for value in kinds.into_iter().flatten() {
2466            asked.push(("kinds".to_owned(), value));
2467        }
2468        for value in exclude_kinds.into_iter().flatten() {
2469            asked.push(("exclude_kinds".to_owned(), value));
2470        }
2471        if let Some(value) = wait_s {
2472            asked.push(("wait_s".to_owned(), value.to_string()));
2473        }
2474        if let Some(value) = limit {
2475            asked.push(("limit".to_owned(), value.to_string()));
2476        }
2477        let sending: Option<serde_json::Value> = None;
2478        self.call("GET", &target, &asked, sending.as_ref())
2479    }
2480
2481    /// Call `manifest_get` on the configured supervisor.
2482    ///
2483    /// # Errors
2484    ///
2485    /// Returns `ClientError` when the supervisor could not be reached, when
2486    /// it answered something this client cannot read.
2487    pub fn manifest_get(&self, print_id: &str) -> Result<ManifestAnswer, ClientError> {
2488        let target = format!("/v1/prints/{print_id}/manifest");
2489        let asked: Vec<(String, String)> = Vec::new();
2490        let sending: Option<serde_json::Value> = None;
2491        self.call("GET", &target, &asked, sending.as_ref())
2492    }
2493
2494    /// Call `look` on the configured supervisor.
2495    ///
2496    /// # Errors
2497    ///
2498    /// Returns `ClientError` when the supervisor could not be reached, when
2499    /// it answered something this client cannot read.
2500    pub fn look(&self, print_id: &str, wait_s: Option<i64>) -> Result<Look, ClientError> {
2501        let target = format!("/v1/prints/{print_id}/look");
2502        let mut asked: Vec<(String, String)> = Vec::new();
2503        if let Some(value) = wait_s {
2504            asked.push(("wait_s".to_owned(), value.to_string()));
2505        }
2506        let sending: Option<serde_json::Value> = None;
2507        self.call("GET", &target, &asked, sending.as_ref())
2508    }
2509
2510    /// Call `manifest_set` on the configured supervisor.
2511    ///
2512    /// # Errors
2513    ///
2514    /// Returns `ClientError` when the supervisor could not be reached, when
2515    /// it answered something this client cannot read, when this client
2516    /// refuses the call before making it because the reason is empty, and
2517    /// when the policy refused the action — which arrives as
2518    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2519    /// the range allowed.
2520    pub fn manifest_set(
2521        &self,
2522        print_id: &str,
2523        reason: &str,
2524        manifest: &JobManifest,
2525    ) -> Result<ManifestAnswer, ClientError> {
2526        crate::reason_given(reason)?;
2527        let target = format!("/v1/prints/{print_id}/manifest");
2528        let asked: Vec<(String, String)> = Vec::new();
2529        let mut sending = serde_json::Map::new();
2530        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2531        sending.insert("manifest".to_owned(), crate::as_value(&manifest)?);
2532        let sending = Some(serde_json::Value::Object(sending));
2533        self.call("PUT", &target, &asked, sending.as_ref())
2534    }
2535
2536    /// Call `spools` on the configured supervisor.
2537    ///
2538    /// # Errors
2539    ///
2540    /// Returns `ClientError` when the supervisor could not be reached, when
2541    /// it answered something this client cannot read.
2542    pub fn spools(&self, include_retired: Option<bool>) -> Result<SpoolsAnswer, ClientError> {
2543        let target = String::from("/v1/spools");
2544        let mut asked: Vec<(String, String)> = Vec::new();
2545        if let Some(value) = include_retired {
2546            asked.push(("include_retired".to_owned(), value.to_string()));
2547        }
2548        let sending: Option<serde_json::Value> = None;
2549        self.call("GET", &target, &asked, sending.as_ref())
2550    }
2551
2552    /// Call `spool` on the configured supervisor.
2553    ///
2554    /// # Errors
2555    ///
2556    /// Returns `ClientError` when the supervisor could not be reached, when
2557    /// it answered something this client cannot read.
2558    pub fn spool(&self, spool_id: &str) -> Result<SpoolAnswer, ClientError> {
2559        let target = format!("/v1/spools/{spool_id}");
2560        let asked: Vec<(String, String)> = Vec::new();
2561        let sending: Option<serde_json::Value> = None;
2562        self.call("GET", &target, &asked, sending.as_ref())
2563    }
2564
2565    /// Call `printer_history` on the configured supervisor.
2566    ///
2567    /// # Errors
2568    ///
2569    /// Returns `ClientError` when the supervisor could not be reached, when
2570    /// it answered something this client cannot read.
2571    pub fn printer_history(
2572        &self,
2573        kinds: Option<Vec<String>>,
2574        since: Option<String>,
2575        until: Option<String>,
2576        spool_id: Option<String>,
2577        before: Option<String>,
2578        limit: Option<i64>,
2579    ) -> Result<TimelineAnswer, ClientError> {
2580        let target = String::from("/v1/history");
2581        let mut asked: Vec<(String, String)> = Vec::new();
2582        for value in kinds.into_iter().flatten() {
2583            asked.push(("kinds".to_owned(), value));
2584        }
2585        if let Some(value) = since {
2586            asked.push(("since".to_owned(), value));
2587        }
2588        if let Some(value) = until {
2589            asked.push(("until".to_owned(), value));
2590        }
2591        if let Some(value) = spool_id {
2592            asked.push(("spool_id".to_owned(), value));
2593        }
2594        if let Some(value) = before {
2595            asked.push(("before".to_owned(), value));
2596        }
2597        if let Some(value) = limit {
2598            asked.push(("limit".to_owned(), value.to_string()));
2599        }
2600        let sending: Option<serde_json::Value> = None;
2601        self.call("GET", &target, &asked, sending.as_ref())
2602    }
2603
2604    /// Call `preflight` on the configured supervisor.
2605    ///
2606    /// # Errors
2607    ///
2608    /// Returns `ClientError` when the supervisor could not be reached, when
2609    /// it answered something this client cannot read.
2610    pub fn preflight(&self, file_name: &str) -> Result<PreflightAnswer, ClientError> {
2611        let target = String::from("/v1/preflight");
2612        let asked: Vec<(String, String)> = vec![("file_name".to_owned(), file_name.to_owned())];
2613        let sending: Option<serde_json::Value> = None;
2614        self.call("GET", &target, &asked, sending.as_ref())
2615    }
2616
2617    /// Call `pause` on the configured supervisor.
2618    ///
2619    /// # Errors
2620    ///
2621    /// Returns `ClientError` when the supervisor could not be reached, when
2622    /// it answered something this client cannot read, when this client
2623    /// refuses the call before making it because the reason is empty, and
2624    /// when the policy refused the action — which arrives as
2625    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2626    /// the range allowed.
2627    pub fn pause(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
2628        crate::reason_given(reason)?;
2629        let target = format!("/v1/prints/{print_id}/actions/pause");
2630        let asked: Vec<(String, String)> = Vec::new();
2631        let mut sending = serde_json::Map::new();
2632        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2633        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2634        let sending = Some(serde_json::Value::Object(sending));
2635        self.call("POST", &target, &asked, sending.as_ref())
2636    }
2637
2638    /// Call `resume` on the configured supervisor.
2639    ///
2640    /// # Errors
2641    ///
2642    /// Returns `ClientError` when the supervisor could not be reached, when
2643    /// it answered something this client cannot read, when this client
2644    /// refuses the call before making it because the reason is empty, and
2645    /// when the policy refused the action — which arrives as
2646    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2647    /// the range allowed.
2648    pub fn resume(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
2649        crate::reason_given(reason)?;
2650        let target = format!("/v1/prints/{print_id}/actions/resume");
2651        let asked: Vec<(String, String)> = Vec::new();
2652        let mut sending = serde_json::Map::new();
2653        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2654        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2655        let sending = Some(serde_json::Value::Object(sending));
2656        self.call("POST", &target, &asked, sending.as_ref())
2657    }
2658
2659    /// Call `cancel` on the configured supervisor.
2660    ///
2661    /// # Errors
2662    ///
2663    /// Returns `ClientError` when the supervisor could not be reached, when
2664    /// it answered something this client cannot read, when this client
2665    /// refuses the call before making it because the reason is empty, and
2666    /// when the policy refused the action — which arrives as
2667    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2668    /// the range allowed.
2669    pub fn cancel(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
2670        crate::reason_given(reason)?;
2671        let target = format!("/v1/prints/{print_id}/actions/cancel");
2672        let asked: Vec<(String, String)> = Vec::new();
2673        let mut sending = serde_json::Map::new();
2674        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2675        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2676        let sending = Some(serde_json::Value::Object(sending));
2677        self.call("POST", &target, &asked, sending.as_ref())
2678    }
2679
2680    /// Call `start_print` on the configured supervisor.
2681    ///
2682    /// # Errors
2683    ///
2684    /// Returns `ClientError` when the supervisor could not be reached, when
2685    /// it answered something this client cannot read, when this client
2686    /// refuses the call before making it because the reason is empty, and
2687    /// when the policy refused the action — which arrives as
2688    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2689    /// the range allowed.
2690    pub fn start_print(
2691        &self,
2692        print_id: &str,
2693        file_name: &FileName,
2694        manifest: &JobManifest,
2695        reason: &str,
2696        override_filament_check: Option<bool>,
2697    ) -> Result<ActionAnswer, ClientError> {
2698        crate::reason_given(reason)?;
2699        let target = format!("/v1/prints/{print_id}/actions/start_print");
2700        let asked: Vec<(String, String)> = Vec::new();
2701        let mut sending = serde_json::Map::new();
2702        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2703        sending.insert("file_name".to_owned(), crate::as_value(&file_name)?);
2704        sending.insert("manifest".to_owned(), crate::as_value(&manifest)?);
2705        if let Some(value) = override_filament_check {
2706            sending.insert(
2707                "override_filament_check".to_owned(),
2708                crate::as_value(&value)?,
2709            );
2710        }
2711        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2712        let sending = Some(serde_json::Value::Object(sending));
2713        self.call("POST", &target, &asked, sending.as_ref())
2714    }
2715
2716    /// Call `set_feedrate_factor` on the configured supervisor.
2717    ///
2718    /// # Errors
2719    ///
2720    /// Returns `ClientError` when the supervisor could not be reached, when
2721    /// it answered something this client cannot read, when this client
2722    /// refuses the call before making it because the reason is empty, and
2723    /// when the policy refused the action — which arrives as
2724    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2725    /// the range allowed.
2726    pub fn set_feedrate_factor(
2727        &self,
2728        print_id: &str,
2729        factor: f64,
2730        reason: &str,
2731        duration_s: Option<i64>,
2732    ) -> Result<ActionAnswer, ClientError> {
2733        crate::reason_given(reason)?;
2734        let target = format!("/v1/prints/{print_id}/actions/set_feedrate_factor");
2735        let asked: Vec<(String, String)> = Vec::new();
2736        let mut sending = serde_json::Map::new();
2737        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2738        if let Some(value) = duration_s {
2739            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
2740        }
2741        sending.insert("factor".to_owned(), crate::as_value(&factor)?);
2742        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2743        let sending = Some(serde_json::Value::Object(sending));
2744        self.call("POST", &target, &asked, sending.as_ref())
2745    }
2746
2747    /// Call `set_flowrate_factor` on the configured supervisor.
2748    ///
2749    /// # Errors
2750    ///
2751    /// Returns `ClientError` when the supervisor could not be reached, when
2752    /// it answered something this client cannot read, when this client
2753    /// refuses the call before making it because the reason is empty, and
2754    /// when the policy refused the action — which arrives as
2755    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2756    /// the range allowed.
2757    pub fn set_flowrate_factor(
2758        &self,
2759        print_id: &str,
2760        factor: f64,
2761        reason: &str,
2762        duration_s: Option<i64>,
2763    ) -> Result<ActionAnswer, ClientError> {
2764        crate::reason_given(reason)?;
2765        let target = format!("/v1/prints/{print_id}/actions/set_flowrate_factor");
2766        let asked: Vec<(String, String)> = Vec::new();
2767        let mut sending = serde_json::Map::new();
2768        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2769        if let Some(value) = duration_s {
2770            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
2771        }
2772        sending.insert("factor".to_owned(), crate::as_value(&factor)?);
2773        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2774        let sending = Some(serde_json::Value::Object(sending));
2775        self.call("POST", &target, &asked, sending.as_ref())
2776    }
2777
2778    /// Call `set_tool_target_c` on the configured supervisor.
2779    ///
2780    /// # Errors
2781    ///
2782    /// Returns `ClientError` when the supervisor could not be reached, when
2783    /// it answered something this client cannot read, when this client
2784    /// refuses the call before making it because the reason is empty, and
2785    /// when the policy refused the action — which arrives as
2786    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2787    /// the range allowed.
2788    pub fn set_tool_target_c(
2789        &self,
2790        print_id: &str,
2791        reason: &str,
2792        target_c: f64,
2793        tool: i64,
2794        duration_s: Option<i64>,
2795    ) -> Result<ActionAnswer, ClientError> {
2796        crate::reason_given(reason)?;
2797        let target = format!("/v1/prints/{print_id}/actions/set_tool_target_c");
2798        let asked: Vec<(String, String)> = Vec::new();
2799        let mut sending = serde_json::Map::new();
2800        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2801        if let Some(value) = duration_s {
2802            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
2803        }
2804        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2805        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
2806        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
2807        let sending = Some(serde_json::Value::Object(sending));
2808        self.call("POST", &target, &asked, sending.as_ref())
2809    }
2810
2811    /// Call `set_bed_target_c` on the configured supervisor.
2812    ///
2813    /// # Errors
2814    ///
2815    /// Returns `ClientError` when the supervisor could not be reached, when
2816    /// it answered something this client cannot read, when this client
2817    /// refuses the call before making it because the reason is empty, and
2818    /// when the policy refused the action — which arrives as
2819    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2820    /// the range allowed.
2821    pub fn set_bed_target_c(
2822        &self,
2823        print_id: &str,
2824        reason: &str,
2825        target_c: f64,
2826        duration_s: Option<i64>,
2827    ) -> Result<ActionAnswer, ClientError> {
2828        crate::reason_given(reason)?;
2829        let target = format!("/v1/prints/{print_id}/actions/set_bed_target_c");
2830        let asked: Vec<(String, String)> = Vec::new();
2831        let mut sending = serde_json::Map::new();
2832        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2833        if let Some(value) = duration_s {
2834            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
2835        }
2836        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2837        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
2838        let sending = Some(serde_json::Value::Object(sending));
2839        self.call("POST", &target, &asked, sending.as_ref())
2840    }
2841
2842    /// Call `set_fan_percent` on the configured supervisor.
2843    ///
2844    /// # Errors
2845    ///
2846    /// Returns `ClientError` when the supervisor could not be reached, when
2847    /// it answered something this client cannot read, when this client
2848    /// refuses the call before making it because the reason is empty, and
2849    /// when the policy refused the action — which arrives as
2850    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2851    /// the range allowed.
2852    pub fn set_fan_percent(
2853        &self,
2854        print_id: &str,
2855        percent: f64,
2856        reason: &str,
2857        duration_s: Option<i64>,
2858    ) -> Result<ActionAnswer, ClientError> {
2859        crate::reason_given(reason)?;
2860        let target = format!("/v1/prints/{print_id}/actions/set_fan_percent");
2861        let asked: Vec<(String, String)> = Vec::new();
2862        let mut sending = serde_json::Map::new();
2863        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2864        if let Some(value) = duration_s {
2865            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
2866        }
2867        sending.insert("percent".to_owned(), crate::as_value(&percent)?);
2868        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2869        let sending = Some(serde_json::Value::Object(sending));
2870        self.call("POST", &target, &asked, sending.as_ref())
2871    }
2872
2873    /// Call `acknowledge_failure` on the configured supervisor.
2874    ///
2875    /// # Errors
2876    ///
2877    /// Returns `ClientError` when the supervisor could not be reached, when
2878    /// it answered something this client cannot read, when this client
2879    /// refuses the call before making it because the reason is empty, and
2880    /// when the policy refused the action — which arrives as
2881    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2882    /// the range allowed.
2883    pub fn acknowledge_failure(
2884        &self,
2885        print_id: &str,
2886        disposition: &AcknowledgementDisposition,
2887        event_id: &EventId,
2888        reason: &str,
2889    ) -> Result<ActionAnswer, ClientError> {
2890        crate::reason_given(reason)?;
2891        let target = format!("/v1/prints/{print_id}/actions/acknowledge_failure");
2892        let asked: Vec<(String, String)> = Vec::new();
2893        let mut sending = serde_json::Map::new();
2894        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2895        sending.insert("disposition".to_owned(), crate::as_value(&disposition)?);
2896        sending.insert("event_id".to_owned(), crate::as_value(&event_id)?);
2897        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2898        let sending = Some(serde_json::Value::Object(sending));
2899        self.call("POST", &target, &asked, sending.as_ref())
2900    }
2901
2902    /// Call `register_spool` on the configured supervisor.
2903    ///
2904    /// # Errors
2905    ///
2906    /// Returns `ClientError` when the supervisor could not be reached, when
2907    /// it answered something this client cannot read, when this client
2908    /// refuses the call before making it because the reason is empty, and
2909    /// when the policy refused the action — which arrives as
2910    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2911    /// the range allowed.
2912    // One argument per value the operation declares, as every client takes them; suppressions.toml has the reason.
2913    #[expect(clippy::too_many_arguments)]
2914    pub fn register_spool(
2915        &self,
2916        initial_g: f64,
2917        material: &str,
2918        name: &str,
2919        reason: &str,
2920        color: Option<String>,
2921        density_g_cm3: Option<f64>,
2922        diameter_mm: Option<f64>,
2923        remaining_g: Option<f64>,
2924        tare_g: Option<f64>,
2925    ) -> Result<SpoolCommandAnswer, ClientError> {
2926        crate::reason_given(reason)?;
2927        let target = String::from("/v1/spools/actions/register_spool");
2928        let asked: Vec<(String, String)> = Vec::new();
2929        let mut sending = serde_json::Map::new();
2930        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2931        if let Some(value) = color {
2932            sending.insert("color".to_owned(), crate::as_value(&value)?);
2933        }
2934        if let Some(value) = density_g_cm3 {
2935            sending.insert("density_g_cm3".to_owned(), crate::as_value(&value)?);
2936        }
2937        if let Some(value) = diameter_mm {
2938            sending.insert("diameter_mm".to_owned(), crate::as_value(&value)?);
2939        }
2940        sending.insert("initial_g".to_owned(), crate::as_value(&initial_g)?);
2941        sending.insert("material".to_owned(), crate::as_value(&material)?);
2942        sending.insert("name".to_owned(), crate::as_value(&name)?);
2943        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2944        if let Some(value) = remaining_g {
2945            sending.insert("remaining_g".to_owned(), crate::as_value(&value)?);
2946        }
2947        if let Some(value) = tare_g {
2948            sending.insert("tare_g".to_owned(), crate::as_value(&value)?);
2949        }
2950        let sending = Some(serde_json::Value::Object(sending));
2951        self.call("POST", &target, &asked, sending.as_ref())
2952    }
2953
2954    /// Call `load_spool` on the configured supervisor.
2955    ///
2956    /// # Errors
2957    ///
2958    /// Returns `ClientError` when the supervisor could not be reached, when
2959    /// it answered something this client cannot read, when this client
2960    /// refuses the call before making it because the reason is empty, and
2961    /// when the policy refused the action — which arrives as
2962    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2963    /// the range allowed.
2964    pub fn load_spool(
2965        &self,
2966        spool_id: &str,
2967        reason: &str,
2968        tool: i64,
2969    ) -> Result<SpoolCommandAnswer, ClientError> {
2970        crate::reason_given(reason)?;
2971        let target = format!("/v1/spools/{spool_id}/actions/load_spool");
2972        let asked: Vec<(String, String)> = Vec::new();
2973        let mut sending = serde_json::Map::new();
2974        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
2975        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
2976        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
2977        let sending = Some(serde_json::Value::Object(sending));
2978        self.call("POST", &target, &asked, sending.as_ref())
2979    }
2980
2981    /// Call `unload_spool` on the configured supervisor.
2982    ///
2983    /// # Errors
2984    ///
2985    /// Returns `ClientError` when the supervisor could not be reached, when
2986    /// it answered something this client cannot read, when this client
2987    /// refuses the call before making it because the reason is empty, and
2988    /// when the policy refused the action — which arrives as
2989    /// `ClientError::Rejected`, carrying the reason, the value asked for and
2990    /// the range allowed.
2991    pub fn unload_spool(
2992        &self,
2993        spool_id: &str,
2994        reason: &str,
2995    ) -> Result<SpoolCommandAnswer, ClientError> {
2996        crate::reason_given(reason)?;
2997        let target = format!("/v1/spools/{spool_id}/actions/unload_spool");
2998        let asked: Vec<(String, String)> = Vec::new();
2999        let mut sending = serde_json::Map::new();
3000        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3001        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3002        let sending = Some(serde_json::Value::Object(sending));
3003        self.call("POST", &target, &asked, sending.as_ref())
3004    }
3005
3006    /// Call `adjust_spool` on the configured supervisor.
3007    ///
3008    /// # Errors
3009    ///
3010    /// Returns `ClientError` when the supervisor could not be reached, when
3011    /// it answered something this client cannot read, when this client
3012    /// refuses the call before making it because the reason is empty, and
3013    /// when the policy refused the action — which arrives as
3014    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3015    /// the range allowed.
3016    pub fn adjust_spool(
3017        &self,
3018        spool_id: &str,
3019        reason: &str,
3020        gross_g: Option<f64>,
3021        remaining_g: Option<f64>,
3022    ) -> Result<SpoolCommandAnswer, ClientError> {
3023        crate::reason_given(reason)?;
3024        let target = format!("/v1/spools/{spool_id}/actions/adjust_spool");
3025        let asked: Vec<(String, String)> = Vec::new();
3026        let mut sending = serde_json::Map::new();
3027        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3028        if let Some(value) = gross_g {
3029            sending.insert("gross_g".to_owned(), crate::as_value(&value)?);
3030        }
3031        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3032        if let Some(value) = remaining_g {
3033            sending.insert("remaining_g".to_owned(), crate::as_value(&value)?);
3034        }
3035        let sending = Some(serde_json::Value::Object(sending));
3036        self.call("POST", &target, &asked, sending.as_ref())
3037    }
3038
3039    /// Call `retire_spool` on the configured supervisor.
3040    ///
3041    /// # Errors
3042    ///
3043    /// Returns `ClientError` when the supervisor could not be reached, when
3044    /// it answered something this client cannot read, when this client
3045    /// refuses the call before making it because the reason is empty, and
3046    /// when the policy refused the action — which arrives as
3047    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3048    /// the range allowed.
3049    pub fn retire_spool(
3050        &self,
3051        spool_id: &str,
3052        reason: &str,
3053    ) -> Result<SpoolCommandAnswer, ClientError> {
3054        crate::reason_given(reason)?;
3055        let target = format!("/v1/spools/{spool_id}/actions/retire_spool");
3056        let asked: Vec<(String, String)> = Vec::new();
3057        let mut sending = serde_json::Map::new();
3058        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3059        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3060        let sending = Some(serde_json::Value::Object(sending));
3061        self.call("POST", &target, &asked, sending.as_ref())
3062    }
3063}