use serde::Serialize;
use crate::__compat::wire::{DescribeWire, WireSchema};
use crate::model::connection::ConnectionKind;
use crate::participant::metadata::{ParticipantContract, ParticipantKind, ParticipantMetadata};
use crate::version::FrameworkVersion;
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ParticipantContractRecord<'a> {
pub framework: FrameworkVersion,
pub id: &'a str,
pub kind: ParticipantKind,
pub connection: Option<ConnectionKind>,
pub config_schema: serde_json::Value,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(tag = "schema")]
pub enum ParticipantMetadataRecord<'a> {
#[serde(rename = "phoxal/participant-metadata/v0")]
V0 {
#[serde(flatten)]
contract: ParticipantContractRecord<'a>,
},
}
impl DescribeWire for ParticipantContractRecord<'_> {
fn wire_schema() -> WireSchema {
ParticipantContract::wire_schema()
}
}
impl DescribeWire for ParticipantMetadataRecord<'_> {
fn wire_schema() -> WireSchema {
ParticipantMetadata::wire_schema()
}
}
#[doc(hidden)]
#[must_use]
pub const fn connection_json(connection: Option<ConnectionKind>) -> &'static str {
match connection {
None => "null",
Some(ConnectionKind::Can) => "\"can\"",
Some(ConnectionKind::I2c) => "\"i2c\"",
Some(ConnectionKind::Spi) => "\"spi\"",
Some(ConnectionKind::Serial) => "\"serial\"",
Some(ConnectionKind::Uart) => "\"uart\"",
Some(ConnectionKind::Usb) => "\"usb\"",
Some(ConnectionKind::Gpio) => "\"gpio\"",
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! participant_metadata_json {
(
framework = $framework:expr,
id = $id:expr,
kind = $kind:expr,
connection = $connection:expr,
config_schema = $config_schema:expr $(,)?
) => {{
const __PHOXAL_FRAMEWORK: &str = $framework;
const __PHOXAL_KIND: &str = $kind.as_str();
const __PHOXAL_CONNECTION: &str = $crate::__private::connection_json($connection);
$crate::__private::meta::concatcp!(
"{\"schema\":\"phoxal/participant-metadata/v0\",\"framework\":\"",
__PHOXAL_FRAMEWORK,
"\",\"id\":\"",
$id,
"\",\"kind\":\"",
__PHOXAL_KIND,
"\",\"connection\":",
__PHOXAL_CONNECTION,
",\"config_schema\":",
$config_schema,
"}"
)
}};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::participant::metadata::ParticipantMetadata;
const CONFIG_SCHEMA: &str = r#"{"type":"null"}"#;
const EMBEDDED: &str = participant_metadata_json!(
framework = FrameworkVersion::CURRENT_SPELLING,
id = "drive",
kind = ParticipantKind::Service,
connection = None,
config_schema = CONFIG_SCHEMA,
);
const EMBEDDED_DECLARED: &str = participant_metadata_json!(
framework = FrameworkVersion::CURRENT_SPELLING,
id = "ddsm115",
kind = ParticipantKind::Driver,
connection = Some(ConnectionKind::Serial),
config_schema = CONFIG_SCHEMA,
);
fn typed_record() -> ParticipantMetadataRecord<'static> {
ParticipantMetadataRecord::V0 {
contract: ParticipantContractRecord {
framework: FrameworkVersion::CURRENT,
id: "drive",
kind: ParticipantKind::Service,
connection: None,
config_schema: serde_json::json!({"type": "null"}),
},
}
}
fn typed_declared_record() -> ParticipantMetadataRecord<'static> {
ParticipantMetadataRecord::V0 {
contract: ParticipantContractRecord {
framework: FrameworkVersion::CURRENT,
id: "ddsm115",
kind: ParticipantKind::Driver,
connection: Some(ConnectionKind::Serial),
config_schema: serde_json::json!({"type": "null"}),
},
}
}
#[test]
fn the_const_writer_emits_exactly_what_the_typed_record_serializes() {
for (const_written, typed) in [
(EMBEDDED, typed_record()),
(EMBEDDED_DECLARED, typed_declared_record()),
] {
let const_written: serde_json::Value = serde_json::from_str(const_written)
.expect("the const writer emits a JSON document");
let typed = serde_json::to_value(typed).expect("the typed record serializes");
assert_eq!(const_written, typed);
}
}
#[test]
fn the_connection_fragment_is_the_quoted_wire_token() {
assert_eq!(connection_json(None), "null");
for kind in ConnectionKind::ALL {
assert_eq!(
connection_json(Some(kind)),
format!("\"{}\"", kind.as_str())
);
}
}
#[test]
fn the_const_written_bytes_have_the_declared_document_shape() {
for embedded in [EMBEDDED, EMBEDDED_DECLARED] {
let const_written: serde_json::Value =
serde_json::from_str(embedded).expect("the const writer emits a JSON document");
assert_eq!(
ParticipantMetadataRecord::wire_schema().conforms(&const_written),
Ok(())
);
}
}
#[test]
fn an_emitted_record_parses_back_into_the_typed_contract() {
let metadata = ParticipantMetadata::from_bytes(EMBEDDED.as_bytes())
.expect("the writer's own output must satisfy the parser");
let contract = metadata.contract();
assert_eq!(contract.framework, FrameworkVersion::CURRENT);
assert_eq!(contract.id.as_str(), "drive");
assert_eq!(contract.kind, ParticipantKind::Service);
assert_eq!(contract.connection, None);
assert_eq!(contract.config_schema, serde_json::json!({"type": "null"}));
let declared = ParticipantMetadata::from_bytes(EMBEDDED_DECLARED.as_bytes())
.expect("the writer's own output must satisfy the parser");
assert_eq!(declared.contract().connection, Some(ConnectionKind::Serial));
}
#[test]
fn the_const_written_document_is_self_contained() {
for embedded in [EMBEDDED, EMBEDDED_DECLARED] {
assert!(
embedded.starts_with('{') && embedded.ends_with('}'),
"{embedded}"
);
assert_eq!(embedded.len(), embedded.trim().len());
}
}
}