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    Tool,
13}
14
15#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
16#[serde(rename_all = "snake_case")]
17pub enum ParticipantClass {
18    Checked,
19    Privileged,
20}
21
22#[derive(Clone, Debug, Deserialize, PartialEq)]
23#[serde(deny_unknown_fields)]
24pub struct ParticipantMetadata {
25    pub schema: String,
26    pub id: String,
27    pub kind: ParticipantKind,
28    pub class: ParticipantClass,
29    pub config_schema: serde_json::Value,
30}
31
32/// Strictly parse and validate an embedded metadata record.
33pub fn parse_participant_metadata(bytes: &[u8]) -> Result<ParticipantMetadata, MetadataError> {
34    let metadata: ParticipantMetadata = serde_json::from_slice(bytes)?;
35    if metadata.schema != PARTICIPANT_METADATA_SCHEMA {
36        return Err(MetadataError::Schema(metadata.schema));
37    }
38    Ok(metadata)
39}
40
41#[derive(Debug, thiserror::Error)]
42pub enum MetadataError {
43    #[error("participant metadata JSON is invalid: {0}")]
44    Json(#[from] serde_json::Error),
45    #[error("unsupported participant metadata schema '{0}'")]
46    Schema(String),
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn strict_metadata_shape_round_trips() {
55        let bytes = br#"{"schema":"phoxal/participant-metadata/v0","id":"drive","kind":"service","class":"checked","config_schema":{"type":"null"}}"#;
56        let metadata = parse_participant_metadata(bytes).unwrap();
57        assert_eq!(metadata.kind, ParticipantKind::Service);
58        assert_eq!(metadata.class, ParticipantClass::Checked);
59    }
60
61    #[test]
62    fn unknown_schema_and_fields_are_rejected() {
63        assert!(matches!(
64            parse_participant_metadata(br#"{"schema":"v1","id":"drive","kind":"service","class":"checked","config_schema":null}"#),
65            Err(MetadataError::Schema(_))
66        ));
67        assert!(parse_participant_metadata(br#"{"schema":"phoxal/participant-metadata/v0","id":"drive","kind":"service","class":"checked","config_schema":null,"extra":true}"#).is_err());
68    }
69}