Skip to main content

phoxal_runtime_contract/
metadata.rs

1use serde::Deserialize;
2
3/// Exact linker-section metadata schema written by participant macros.
4pub const PARTICIPANT_METADATA_SCHEMA: &str = "phoxal/participant-metadata/v0";
5
6#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
7#[serde(rename_all = "snake_case")]
8pub enum ParticipantKind {
9    Service,
10    Driver,
11    Simulator,
12}
13
14#[derive(Clone, Debug, Deserialize, PartialEq)]
15#[serde(deny_unknown_fields)]
16pub struct ParticipantMetadata {
17    pub schema: String,
18    pub id: String,
19    pub kind: ParticipantKind,
20    pub config_schema: serde_json::Value,
21}
22
23/// Strictly parse and validate an embedded metadata record.
24pub fn parse_participant_metadata(bytes: &[u8]) -> Result<ParticipantMetadata, MetadataError> {
25    let metadata: ParticipantMetadata = serde_json::from_slice(bytes)?;
26    if metadata.schema != PARTICIPANT_METADATA_SCHEMA {
27        return Err(MetadataError::Schema(metadata.schema));
28    }
29    Ok(metadata)
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum MetadataError {
34    #[error("participant metadata JSON is invalid: {0}")]
35    Json(#[from] serde_json::Error),
36    #[error("unsupported participant metadata schema '{0}'")]
37    Schema(String),
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn strict_metadata_shape_round_trips() {
46        let bytes = br#"{"schema":"phoxal/participant-metadata/v0","id":"drive","kind":"service","config_schema":{"type":"null"}}"#;
47        let metadata = parse_participant_metadata(bytes).unwrap();
48        assert_eq!(metadata.kind, ParticipantKind::Service);
49        assert_eq!(metadata.id, "drive");
50    }
51
52    #[test]
53    fn unknown_schema_and_fields_are_rejected() {
54        assert!(matches!(
55            parse_participant_metadata(
56                br#"{"schema":"v1","id":"drive","kind":"service","config_schema":null}"#
57            ),
58            Err(MetadataError::Schema(_))
59        ));
60        assert!(parse_participant_metadata(br#"{"schema":"phoxal/participant-metadata/v0","id":"drive","kind":"service","config_schema":null,"extra":true}"#).is_err());
61        // `class` was the tool/checked discriminator and went with the tool
62        // concept (#978); a binary still emitting it is stale, not compatible.
63        assert!(parse_participant_metadata(br#"{"schema":"phoxal/participant-metadata/v0","id":"drive","kind":"service","class":"checked","config_schema":null}"#).is_err());
64    }
65}