Skip to main content

dre_protocol/
schema.rs

1//! The protocol as data: a JSON Schema for every message, generated from [`crate::msg`], with the
2//! protocol version range, the capability names and the error kinds. It is published as
3//! `conformance/schema.json` beside the conformance cases, so a plugin written in any language
4//! can check its messages and constants against it; a test keeps the file in step with the code.
5
6use schemars::generate::SchemaSettings;
7use serde_json::{Value, json};
8
9use crate::msg::{ERROR_KINDS, Envelope, Request, Response};
10use crate::{CAPABILITIES, CORE_MIN_VERSION, MAX_VERSION, MIN_VERSION};
11
12/// The published schema file, as checked in.
13pub const PUBLISHED: &str = include_str!("../conformance/schema.json");
14
15/// The schema document: `$defs.Request` is any message core sends, `$defs.Response` any message
16/// a plugin sends, and `x-dre` the version range, capabilities and error kinds.
17#[must_use]
18pub fn document() -> Value {
19    let mut generator = SchemaSettings::draft2020_12().into_generator();
20    generator.subschema_for::<Envelope<Request>>();
21    generator.subschema_for::<Envelope<Response>>();
22    let defs: serde_json::Map<String, Value> = generator.take_definitions(true).into_iter().collect();
23    json!({
24        "$schema": "https://json-schema.org/draft/2020-12/schema",
25        "title": "DRE plugin protocol messages",
26        "description": "Every JSON frame of the DRE plugin protocol (docs/protocol.md). A frame core sends matches `$defs/Request`; a frame a plugin sends matches `$defs/Response`. Generated from dre-protocol's msg.rs; don't edit by hand.",
27        "anyOf": [{"$ref": "#/$defs/Request"}, {"$ref": "#/$defs/Response"}],
28        "x-dre": {
29            "protocol": {
30                "min_version": MIN_VERSION,
31                "max_version": MAX_VERSION,
32                "core_min_version": CORE_MIN_VERSION,
33            },
34            "capabilities": CAPABILITIES,
35            "error_kinds": ERROR_KINDS,
36        },
37        "$defs": defs,
38    })
39}
40
41/// The schema document as the published file spells it.
42///
43/// # Panics
44///
45/// Never in practice: a `serde_json::Value` always serialises.
46#[must_use]
47pub fn render() -> String {
48    let mut s = serde_json::to_string_pretty(&document()).expect("a schema serialises");
49    s.push('\n');
50    s
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    /// CI fails here when `msg.rs` changes without the published file. Regenerate it (and the
58    /// Go SDK's copies of the conformance files) with
59    /// `DRE_UPDATE_SCHEMA=1 cargo test -p dre-protocol --lib schema`.
60    #[test]
61    fn the_published_schema_matches_the_code() {
62        let fresh = render();
63        if std::env::var_os("DRE_UPDATE_SCHEMA").is_some() {
64            let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("conformance");
65            std::fs::write(dir.join("schema.json"), &fresh).unwrap();
66            // The Go SDK's plugintest embeds a copy of every published file.
67            let go = dir.join("../../../go/plugin/plugintest/conformance");
68            for name in ["schema.json", "protocol.json", "delivery.json"] {
69                std::fs::copy(dir.join(name), go.join(name)).unwrap();
70            }
71            return;
72        }
73        assert!(
74            fresh == PUBLISHED,
75            "conformance/schema.json is out of date with msg.rs; regenerate it with \
76             `DRE_UPDATE_SCHEMA=1 cargo test -p dre-protocol --lib schema`"
77        );
78    }
79
80    #[test]
81    fn every_message_names_its_type() {
82        let doc = document();
83        for side in ["Request", "Response"] {
84            let text = doc["$defs"][side].to_string();
85            assert!(text.contains("\"type\""), "{side}: {text}");
86        }
87        assert_eq!(doc["x-dre"]["protocol"]["max_version"], MAX_VERSION);
88    }
89}