1mod annotations;
2mod backend;
3mod config;
4mod measurement;
5mod protocol;
6mod replicates;
7mod status;
8mod streams;
9mod vocabulary;
10
11pub use annotations::{AnnotationMapping, AnnotationUpsert, NoteMapping, NoteUpsert};
12pub use backend::{
13 DeclinedChannel, SourceCandidate, SourceInventory, SourceWindow, StreamDescriptor,
14 StreamFetchRequest, StreamReadings, StreamStatusEvents,
15};
16pub use config::RunnerConfig;
17pub use measurement::MeasurementType;
18pub use protocol::{
19 CommandUpdateRequest, EnrollRequest, EnrollResponse, HeartbeatRequest, HeartbeatResponse,
20 PendingCommand, SyncEventCreate, SyncEventRef, SyncEventUpdate, SyncResult, SyncTrigger,
21};
22pub use replicates::{
23 ColumnAssignment, CurveMapping, GroupAudit, ReplicateSpec, SensorUpsert, StandardCurveUpsert,
24};
25pub use status::{CommandStatus, ServiceStatus, SyncEventStatus, SyncEventType};
26pub use streams::{DataStream, IngestReading, IngestStatusEvent, RegisterStreamRequest};
27pub use vocabulary::UnknownValue;
28
29#[cfg(test)]
30mod tests {
31 use super::*;
32
33 #[test]
37 fn the_shapes_that_refuse_an_unknown_field() {
38 fn refuses<D: serde::de::DeserializeOwned>(mut json: serde_json::Value) -> bool {
39 json["a_field_the_sender_gained"] = serde_json::json!(1);
40 serde_json::from_value::<D>(json).is_err()
41 }
42
43 assert!(refuses::<IngestReading>(serde_json::json!({
44 "time": "2026-01-15T10:00:00Z", "raw_value": 1.0
45 })));
46 assert!(refuses::<IngestStatusEvent>(serde_json::json!({
47 "time": "2026-01-15T10:00:00Z", "value": "unreachable"
48 })));
49 assert!(refuses::<SourceWindow>(serde_json::json!({
50 "from": "2026-01-01T00:00:00Z", "to": "2026-02-01T00:00:00Z", "source_rows_read": 1
51 })));
52 assert!(refuses::<GroupAudit>(serde_json::json!({
53 "time": "2026-01-15T10:00:00Z"
54 })));
55 assert!(refuses::<SensorUpsert>(serde_json::json!({
56 "source_key": "sensor_inventory:62", "name": "DOC corr", "is_lab_instrument": true
57 })));
58 assert!(refuses::<AnnotationUpsert>(serde_json::json!({
59 "source_key": "annotations:9",
60 "stream_id": "00000000-0000-0000-0000-000000000006",
61 "time": "2026-01-15T10:00:00Z", "category": "audit", "text": "x"
62 })));
63 assert!(refuses::<NoteUpsert>(serde_json::json!({
64 "source_key": "notes:1", "site_name": "FP1", "text": "x", "verified": true
65 })));
66
67 assert!(!refuses::<RegisterStreamRequest>(serde_json::json!({
68 "source_system": "cnet", "source_key": "FP1:DOC",
69 "source_name": null, "source_path": null, "metadata": {}
70 })));
71 assert!(!refuses::<StandardCurveUpsert>(serde_json::json!({
72 "source_key": "standard_curves:17", "instrument_label": "DOC corr",
73 "slope": 1.0, "intercept": 0.0
74 })));
75 assert!(!refuses::<ColumnAssignment>(serde_json::json!({
76 "column": "DOC_A", "index": 0
77 })));
78 }
79
80 #[test]
83 fn the_fields_the_api_accepts_travel() {
84 let instrument = SensorUpsert {
85 source_key: "sensor_inventory:62".to_string(),
86 name: "DOC corr".to_string(),
87 serial_number: None,
88 manufacturer: None,
89 model: None,
90 notes: None,
91 is_lab_instrument: true,
92 data_frequency: Some("low".to_string()),
93 metadata: None,
94 };
95 let json = serde_json::to_value(&instrument).unwrap();
96 assert_eq!(json["data_frequency"], "low");
97
98 let curve = StandardCurveUpsert {
99 source_key: "standard_curves:17".to_string(),
100 instrument_label: "DOC corr".to_string(),
101 slope: 1.0,
102 intercept: 0.0,
103 r_squared: None,
104 name: None,
105 fitted_on: None,
106 notes: Some("re-fitted after the lamp change".to_string()),
107 };
108 let json = serde_json::to_value(&curve).unwrap();
109 assert_eq!(json["notes"], "re-fitted after the lamp change");
110
111 let event = IngestStatusEvent {
112 time: chrono::Utc::now(),
113 value: "unreachable".to_string(),
114 sensor_id: Some(uuid::Uuid::nil()),
115 };
116 let json = serde_json::to_value(&event).unwrap();
117 assert_eq!(json["sensor_id"], uuid::Uuid::nil().to_string());
118 }
119
120 #[test]
123 fn an_undeclared_field_is_not_sent() {
124 let instrument = SensorUpsert {
125 source_key: "sensor_inventory:62".to_string(),
126 name: "DOC corr".to_string(),
127 serial_number: None,
128 manufacturer: None,
129 model: None,
130 notes: None,
131 is_lab_instrument: false,
132 data_frequency: None,
133 metadata: None,
134 };
135 let json = serde_json::to_value(&instrument).unwrap();
136 assert!(json.get("data_frequency").is_none());
137
138 let event = IngestStatusEvent {
139 time: chrono::Utc::now(),
140 value: "ok".to_string(),
141 sensor_id: None,
142 };
143 let json = serde_json::to_value(&event).unwrap();
144 assert!(json.get("sensor_id").is_none());
145 }
146}