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