printobserver_types/obico.rs
1//! The wire shapes the self-hosted Obico webhook notification plugin sends.
2//!
3//! These are held here as this repository's committed claim about an external
4//! producer, beside the three samples that model what it sends. What
5//! reconciles that claim against the real producer is the scheduled Obico tier,
6//! which drives a live self-hosted Obico and fails naming any field that has
7//! moved.
8//!
9//! The producer sends three shapes: the failure alert, a printer notification
10//! about a print, which carries `print` and `img_url`, and a printer
11//! notification not about a print, which carries neither. One sample per shape
12//! is committed under this crate's own `samples/obico/`, and the third is not
13//! an incomplete copy of the second: it is what the producer sends when there
14//! is no print, and its two absences are the whole reason it is committed.
15
16use std::borrow::Cow;
17
18use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
19use serde::de::{Error as _, Unexpected};
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21
22/// The one `type` a failure alert's event object carries.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24pub enum ObicoFailureEventType {
25 /// Obico's own spelling of a print failure.
26 #[serde(rename = "PrintFailure")]
27 PrintFailure,
28}
29
30/// The `type` a printer notification's event object carries.
31///
32/// These are the producer's own spellings; the normalized vocabulary is
33/// [`ObicoNotificationType`](crate::ObicoNotificationType).
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
35pub enum ObicoEventType {
36 /// A print started.
37 PrintStarted,
38 /// A print finished.
39 PrintDone,
40 /// A print was cancelled.
41 PrintCancelled,
42 /// A print was paused.
43 PrintPaused,
44 /// A print was resumed.
45 PrintResumed,
46 /// The printer is waiting for a filament change.
47 FilamentChange,
48 /// A heater cooled down.
49 HeaterCooledDown,
50 /// A heater reached its target.
51 HeaterTargetReached,
52}
53
54/// A Unix timestamp number, or the empty string the producer sends instead.
55#[derive(Debug, Clone, Copy, PartialEq)]
56pub enum ObicoTimestamp {
57 /// Seconds since the Unix epoch.
58 Seconds(f64),
59 /// The producer sent the empty string, meaning it has no instant to send.
60 NotReported,
61}
62
63impl Serialize for ObicoTimestamp {
64 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
65 match self {
66 Self::Seconds(seconds) => serializer.serialize_f64(*seconds),
67 Self::NotReported => serializer.serialize_str(""),
68 }
69 }
70}
71
72/// The two forms the producer sends, before the empty string is given meaning.
73#[derive(Deserialize)]
74#[serde(untagged)]
75enum ObicoTimestampWire {
76 /// A JSON number.
77 Seconds(f64),
78 /// A JSON string, which the producer sends only as the empty one.
79 Text(String),
80}
81
82impl<'de> Deserialize<'de> for ObicoTimestamp {
83 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
84 match ObicoTimestampWire::deserialize(deserializer)? {
85 ObicoTimestampWire::Seconds(seconds) => Ok(Self::Seconds(seconds)),
86 ObicoTimestampWire::Text(text) if text.is_empty() => Ok(Self::NotReported),
87 ObicoTimestampWire::Text(text) => Err(D::Error::invalid_value(
88 Unexpected::Str(&text),
89 &"a Unix timestamp number or an empty string",
90 )),
91 }
92 }
93}
94
95impl JsonSchema for ObicoTimestamp {
96 fn schema_name() -> Cow<'static, str> {
97 Cow::Borrowed("ObicoTimestamp")
98 }
99
100 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
101 json_schema!({
102 "title": "ObicoTimestamp",
103 "description": "A Unix timestamp number, or the empty string the producer sends \
104 when it has no instant to send.",
105 "anyOf": [
106 { "type": "number" },
107 { "type": "string", "maxLength": 0 }
108 ]
109 })
110 }
111}
112
113/// The `printer` object both shapes carry.
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
115pub struct ObicoPrinterInfo {
116 /// Obico's own identifier for the printer.
117 pub id: i64,
118 /// The name Obico shows the printer under.
119 pub name: String,
120}
121
122/// The `print` object, which the producer sends when there is a print.
123///
124/// Each instant has **three** input states rather than two, and the option and
125/// the enum carry one each: a number is [`ObicoTimestamp::Seconds`], the empty
126/// string the producer sends for a print that has not started or ended is
127/// [`ObicoTimestamp::NotReported`], and a field the producer omits altogether
128/// is `None`. The last two both mean the producer reported no instant, and
129/// neither is refused and neither is an epoch date; they are held apart here
130/// only so that a body round-trips back into exactly the form it arrived in.
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
132pub struct ObicoPrintInfo {
133 /// Obico's own identifier for the print.
134 pub id: i64,
135 /// The file being printed.
136 pub filename: String,
137 /// When the print started, absent when the producer omits the field.
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub started_at: Option<ObicoTimestamp>,
140 /// When the print ended, absent when the producer omits the field.
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub ended_at: Option<ObicoTimestamp>,
143}
144
145/// The `event` object a failure alert carries.
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
147pub struct ObicoFailureEvent {
148 /// Always the producer's own `PrintFailure`.
149 #[serde(rename = "type")]
150 pub event_type: ObicoFailureEventType,
151 /// Whether the producer called it a warning rather than a failure.
152 pub is_warning: bool,
153 /// Whether the producer paused the print itself.
154 pub print_paused: bool,
155}
156
157/// The `event` object a printer notification carries.
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
159pub struct ObicoNotificationEvent {
160 /// Which notification it is, in the producer's own spelling.
161 #[serde(rename = "type")]
162 pub event_type: ObicoEventType,
163 /// Whether the producer called it a warning.
164 pub is_warning: bool,
165 /// Whether the producer paused the print itself.
166 pub print_paused: bool,
167}
168
169/// The whole body the producer sends for a print failure.
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
171pub struct ObicoFailureAlert {
172 /// What happened.
173 pub event: ObicoFailureEvent,
174 /// Which printer it happened on.
175 pub printer: ObicoPrinterInfo,
176 /// Which print it happened to.
177 pub print: ObicoPrintInfo,
178 /// Where the snapshot that shows it can be fetched.
179 pub img_url: String,
180}
181
182/// The whole body the producer sends for a printer notification.
183///
184/// `print` and `img_url` are present together when the notification is about a
185/// print and absent together when it is not; the absent form is a shape the
186/// producer sends rather than an incomplete copy of the present one.
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
188pub struct ObicoPrinterNotification {
189 /// What happened.
190 pub event: ObicoNotificationEvent,
191 /// Which printer it happened on.
192 pub printer: ObicoPrinterInfo,
193 /// Which print it is about, when it is about one.
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub print: Option<ObicoPrintInfo>,
196 /// Where the snapshot can be fetched, when the notification carries one.
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub img_url: Option<String>,
199}