lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

schema_validation.rs (23616B)


      1 use alloc::collections::{BTreeMap, BTreeSet};
      2 use alloc::string::String;
      3 use alloc::vec::Vec;
      4 
      5 use crate::{
      6     RADROOTS_MESH_AGENT_SCHEMA_ID, RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE,
      7     RadrootsMeshAgentProtoError,
      8 };
      9 
     10 struct RequiredField {
     11     owner: &'static str,
     12     name: &'static str,
     13     ordinal: u16,
     14     field_type: &'static str,
     15 }
     16 
     17 struct RequiredVariant {
     18     owner: &'static str,
     19     name: &'static str,
     20     ordinal: u16,
     21 }
     22 
     23 struct ForbiddenField {
     24     owner: &'static str,
     25     name: &'static str,
     26 }
     27 
     28 struct ForbiddenVariant {
     29     owner: &'static str,
     30     name: &'static str,
     31 }
     32 
     33 const REQUEST_FIELDS: &[RequiredField] = &[
     34     field("MeshAgentRequest", "requestId", 0, "Text"),
     35     field("MeshAgentRequest", "action", 1, "MeshAgentAction"),
     36     field("MeshAgentRequest", "frameCbor", 2, "Data"),
     37     field(
     38         "MeshAgentRequest",
     39         "statusRequest",
     40         3,
     41         "MeshAgentStatusRequest",
     42     ),
     43     field(
     44         "MeshAgentRequest",
     45         "publishRequest",
     46         4,
     47         "MeshAgentPublishRequest",
     48     ),
     49 ];
     50 
     51 const ACTION_VARIANTS: &[RequiredVariant] = &[
     52     variant("MeshAgentAction", "validateFrame", 0),
     53     variant("MeshAgentAction", "stageDelivery", 1),
     54     variant("MeshAgentAction", "observeEventHead", 2),
     55     variant("MeshAgentAction", "status", 3),
     56     variant("MeshAgentAction", "publish", 4),
     57 ];
     58 
     59 const RESPONSE_FIELDS: &[RequiredField] = &[
     60     field("MeshAgentResponse", "requestId", 0, "Text"),
     61     field("MeshAgentResponse", "status", 1, "MeshAgentResponseStatus"),
     62     field("MeshAgentResponse", "receipt", 2, "MeshAgentReceipt"),
     63     field("MeshAgentResponse", "errors", 3, "List(MeshAgentError)"),
     64     field(
     65         "MeshAgentResponse",
     66         "statusResponse",
     67         4,
     68         "MeshAgentStatusResponse",
     69     ),
     70     field(
     71         "MeshAgentResponse",
     72         "publishResponse",
     73         5,
     74         "MeshAgentPublishResponse",
     75     ),
     76 ];
     77 
     78 const RESPONSE_STATUS_VARIANTS: &[RequiredVariant] = &[
     79     variant("MeshAgentResponseStatus", "accepted", 0),
     80     variant("MeshAgentResponseStatus", "deferred", 1),
     81     variant("MeshAgentResponseStatus", "rejected", 2),
     82 ];
     83 
     84 const RECEIPT_FIELDS: &[RequiredField] = &[
     85     field("MeshAgentReceipt", "frameDigest", 0, "Text"),
     86     field("MeshAgentReceipt", "acceptedEventHeads", 1, "List(Text)"),
     87 ];
     88 
     89 const STATUS_FIELDS: &[RequiredField] = &[
     90     field("MeshAgentStatusRequest", "includeTransports", 0, "Bool"),
     91     field(
     92         "MeshAgentStatusResponse",
     93         "transports",
     94         0,
     95         "List(MeshAgentTransportStatus)",
     96     ),
     97     field(
     98         "MeshAgentTransportStatus",
     99         "transport",
    100         0,
    101         "MeshAgentTransportKind",
    102     ),
    103     field("MeshAgentTransportStatus", "profileId", 1, "Text"),
    104     field("MeshAgentTransportStatus", "endpointUri", 2, "Text"),
    105     field("MeshAgentTransportStatus", "configured", 3, "Bool"),
    106     field(
    107         "MeshAgentTransportStatus",
    108         "implementation",
    109         4,
    110         "MeshAgentImplementation",
    111     ),
    112     field("MeshAgentTransportStatus", "usableForDelivery", 5, "Bool"),
    113     field("MeshAgentTransportStatus", "message", 6, "Text"),
    114 ];
    115 
    116 const IMPLEMENTATION_VARIANTS: &[RequiredVariant] = &[
    117     variant("MeshAgentImplementation", "real", 0),
    118     variant("MeshAgentImplementation", "mock", 1),
    119     variant("MeshAgentImplementation", "previewUnavailable", 2),
    120 ];
    121 
    122 const TRANSPORT_KIND_VARIANTS: &[RequiredVariant] =
    123     &[variant("MeshAgentTransportKind", "reticulum", 0)];
    124 
    125 const FORBIDDEN_STATUS_FIELDS: &[ForbiddenField] = &[
    126     forbidden_field("MeshAgentStatusResponse", "readiness"),
    127     forbidden_field("MeshAgentStatusResponse", "implementationState"),
    128     forbidden_field("MeshAgentTransportStatus", "transportKind"),
    129     forbidden_field("MeshAgentTransportStatus", "readiness"),
    130     forbidden_field("MeshAgentTransportStatus", "implementationState"),
    131     forbidden_field("MeshAgentTransportStatus", "publishUsable"),
    132     forbidden_field("MeshAgentTransportStatus", "fetchUsable"),
    133     forbidden_field("MeshAgentTransportStatus", "redactedMessage"),
    134 ];
    135 
    136 const FORBIDDEN_STATUS_VARIANTS: &[ForbiddenVariant] =
    137     &[forbidden_variant("MeshAgentImplementation", "previewNoop")];
    138 
    139 const FORBIDDEN_STATUS_ENUMS: &[&str] =
    140     &["MeshAgentReadinessState", "MeshAgentImplementationState"];
    141 
    142 const TRANSPORT_OUTCOME_VARIANTS: &[RequiredVariant] = &[
    143     variant("MeshAgentTransportOutcome", "accepted", 0),
    144     variant("MeshAgentTransportOutcome", "delivered", 1),
    145     variant("MeshAgentTransportOutcome", "forwarded", 2),
    146     variant("MeshAgentTransportOutcome", "storedByGateway", 3),
    147     variant("MeshAgentTransportOutcome", "deferredUntilImplemented", 4),
    148     variant("MeshAgentTransportOutcome", "rejected", 5),
    149     variant("MeshAgentTransportOutcome", "routeUnavailable", 6),
    150     variant("MeshAgentTransportOutcome", "timeout", 7),
    151     variant("MeshAgentTransportOutcome", "transportUnavailable", 8),
    152 ];
    153 
    154 const PUBLISH_FIELDS: &[RequiredField] = &[
    155     field("MeshAgentPublishRequest", "publishRequestId", 0, "Text"),
    156     field("MeshAgentPublishRequest", "payloadCbor", 1, "Data"),
    157     field("MeshAgentPublishRequest", "eventId", 2, "Text"),
    158     field("MeshAgentPublishRequest", "targetFingerprint", 3, "Text"),
    159     field("MeshAgentPublishResponse", "publishRequestId", 0, "Text"),
    160     field(
    161         "MeshAgentPublishResponse",
    162         "status",
    163         1,
    164         "MeshAgentResponseStatus",
    165     ),
    166     field(
    167         "MeshAgentPublishResponse",
    168         "transportReceipts",
    169         2,
    170         "List(MeshAgentTransportReceipt)",
    171     ),
    172     field("MeshAgentPublishResponse", "eventId", 3, "Text"),
    173     field(
    174         "MeshAgentTransportReceipt",
    175         "transportKind",
    176         0,
    177         "MeshAgentTransportKind",
    178     ),
    179     field("MeshAgentTransportReceipt", "endpointUri", 1, "Text"),
    180     field(
    181         "MeshAgentTransportReceipt",
    182         "outcome",
    183         2,
    184         "MeshAgentTransportOutcome",
    185     ),
    186     field("MeshAgentTransportReceipt", "message", 3, "Text"),
    187 ];
    188 
    189 const FORBIDDEN_PUBLISH_FIELDS: &[ForbiddenField] = &[forbidden_field(
    190     "MeshAgentTransportReceipt",
    191     "redactedMessage",
    192 )];
    193 
    194 const ERROR_FIELDS: &[RequiredField] = &[
    195     field("MeshAgentError", "code", 0, "Text"),
    196     field("MeshAgentError", "message", 1, "Text"),
    197 ];
    198 
    199 #[cfg_attr(coverage_nightly, coverage(off))]
    200 const fn field(
    201     owner: &'static str,
    202     name: &'static str,
    203     ordinal: u16,
    204     field_type: &'static str,
    205 ) -> RequiredField {
    206     RequiredField {
    207         owner,
    208         name,
    209         ordinal,
    210         field_type,
    211     }
    212 }
    213 
    214 #[cfg_attr(coverage_nightly, coverage(off))]
    215 const fn variant(owner: &'static str, name: &'static str, ordinal: u16) -> RequiredVariant {
    216     RequiredVariant {
    217         owner,
    218         name,
    219         ordinal,
    220     }
    221 }
    222 
    223 #[cfg_attr(coverage_nightly, coverage(off))]
    224 const fn forbidden_field(owner: &'static str, name: &'static str) -> ForbiddenField {
    225     ForbiddenField { owner, name }
    226 }
    227 
    228 #[cfg_attr(coverage_nightly, coverage(off))]
    229 const fn forbidden_variant(owner: &'static str, name: &'static str) -> ForbiddenVariant {
    230     ForbiddenVariant { owner, name }
    231 }
    232 
    233 pub(crate) fn validate_schema_text(schema: &str) -> Result<(), RadrootsMeshAgentProtoError> {
    234     let parsed = parse_schema(schema)?;
    235     if parsed.schema_id.as_deref() != Some(RADROOTS_MESH_AGENT_SCHEMA_ID) {
    236         return Err(RadrootsMeshAgentProtoError::MissingSchemaId);
    237     }
    238     if parsed.namespace.as_deref() != Some(RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE) {
    239         return Err(RadrootsMeshAgentProtoError::MissingNamespace);
    240     }
    241     validate_fields(
    242         &parsed,
    243         REQUEST_FIELDS,
    244         RadrootsMeshAgentProtoError::MissingRequest,
    245     )?;
    246     validate_variants(
    247         &parsed,
    248         ACTION_VARIANTS,
    249         RadrootsMeshAgentProtoError::MissingAction,
    250     )?;
    251     validate_fields(
    252         &parsed,
    253         RESPONSE_FIELDS,
    254         RadrootsMeshAgentProtoError::MissingResponse,
    255     )?;
    256     validate_variants(
    257         &parsed,
    258         RESPONSE_STATUS_VARIANTS,
    259         RadrootsMeshAgentProtoError::MissingResponse,
    260     )?;
    261     validate_fields(
    262         &parsed,
    263         RECEIPT_FIELDS,
    264         RadrootsMeshAgentProtoError::MissingReceipt,
    265     )?;
    266     validate_fields(
    267         &parsed,
    268         STATUS_FIELDS,
    269         RadrootsMeshAgentProtoError::MissingStatusSurface,
    270     )?;
    271     validate_variants(
    272         &parsed,
    273         IMPLEMENTATION_VARIANTS,
    274         RadrootsMeshAgentProtoError::MissingStatusSurface,
    275     )?;
    276     validate_variants(
    277         &parsed,
    278         TRANSPORT_KIND_VARIANTS,
    279         RadrootsMeshAgentProtoError::MissingStatusSurface,
    280     )?;
    281     reject_fields(
    282         &parsed,
    283         FORBIDDEN_STATUS_FIELDS,
    284         RadrootsMeshAgentProtoError::MissingStatusSurface,
    285     )?;
    286     reject_variants(
    287         &parsed,
    288         FORBIDDEN_STATUS_VARIANTS,
    289         RadrootsMeshAgentProtoError::MissingStatusSurface,
    290     )?;
    291     reject_enums(
    292         &parsed,
    293         FORBIDDEN_STATUS_ENUMS,
    294         RadrootsMeshAgentProtoError::MissingStatusSurface,
    295     )?;
    296     validate_fields(
    297         &parsed,
    298         PUBLISH_FIELDS,
    299         RadrootsMeshAgentProtoError::MissingPublishSurface,
    300     )?;
    301     validate_variants(
    302         &parsed,
    303         TRANSPORT_OUTCOME_VARIANTS,
    304         RadrootsMeshAgentProtoError::MissingPublishSurface,
    305     )?;
    306     reject_fields(
    307         &parsed,
    308         FORBIDDEN_PUBLISH_FIELDS,
    309         RadrootsMeshAgentProtoError::MissingPublishSurface,
    310     )?;
    311     validate_fields(
    312         &parsed,
    313         ERROR_FIELDS,
    314         RadrootsMeshAgentProtoError::MissingError,
    315     )
    316 }
    317 
    318 #[derive(Clone, Debug, PartialEq, Eq)]
    319 enum SchemaToken {
    320     Ident(String),
    321     Number(String),
    322     StringLiteral(String),
    323     Symbol(char),
    324 }
    325 
    326 #[derive(Default)]
    327 struct SchemaAst {
    328     schema_id: Option<String>,
    329     namespace: Option<String>,
    330     structs: BTreeMap<String, StructDecl>,
    331     enums: BTreeMap<String, EnumDecl>,
    332 }
    333 
    334 struct StructDecl {
    335     fields: BTreeMap<String, FieldDecl>,
    336 }
    337 
    338 struct FieldDecl {
    339     ordinal: u16,
    340     field_type: String,
    341 }
    342 
    343 struct EnumDecl {
    344     variants: BTreeMap<String, u16>,
    345 }
    346 
    347 struct SchemaParser {
    348     tokens: Vec<SchemaToken>,
    349     index: usize,
    350 }
    351 
    352 fn parse_schema(schema: &str) -> Result<SchemaAst, RadrootsMeshAgentProtoError> {
    353     let tokens = lex_schema(schema)?;
    354     let mut parser = SchemaParser { tokens, index: 0 };
    355     parser.parse()
    356 }
    357 
    358 fn lex_schema(schema: &str) -> Result<Vec<SchemaToken>, RadrootsMeshAgentProtoError> {
    359     let chars: Vec<char> = schema.chars().collect();
    360     let mut tokens = Vec::new();
    361     let mut index = 0;
    362     while index < chars.len() {
    363         let ch = chars[index];
    364         if ch.is_whitespace() {
    365             index += 1;
    366             continue;
    367         }
    368         if ch == '#' {
    369             index = skip_line_comment(&chars, index + 1);
    370             continue;
    371         }
    372         if ch == '/' && chars.get(index + 1) == Some(&'/') {
    373             index = skip_line_comment(&chars, index + 2);
    374             continue;
    375         }
    376         if ch == '/' && chars.get(index + 1) == Some(&'*') {
    377             index = skip_block_comment(&chars, index + 2)?;
    378             continue;
    379         }
    380         if is_ident_start(ch) {
    381             let start = index;
    382             index += 1;
    383             while index < chars.len() && is_ident_continue(chars[index]) {
    384                 index += 1;
    385             }
    386             tokens.push(SchemaToken::Ident(chars[start..index].iter().collect()));
    387             continue;
    388         }
    389         if ch.is_ascii_digit() {
    390             let start = index;
    391             index += 1;
    392             while index < chars.len() && is_number_continue(chars[index]) {
    393                 index += 1;
    394             }
    395             tokens.push(SchemaToken::Number(chars[start..index].iter().collect()));
    396             continue;
    397         }
    398         if ch == '"' {
    399             let (literal, next_index) = read_string_literal(&chars, index + 1)?;
    400             tokens.push(SchemaToken::StringLiteral(literal));
    401             index = next_index;
    402             continue;
    403         }
    404         if is_schema_symbol(ch) {
    405             tokens.push(SchemaToken::Symbol(ch));
    406             index += 1;
    407             continue;
    408         }
    409         return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    410     }
    411     Ok(tokens)
    412 }
    413 
    414 fn skip_line_comment(chars: &[char], mut index: usize) -> usize {
    415     while index < chars.len() && chars[index] != '\n' {
    416         index += 1;
    417     }
    418     index
    419 }
    420 
    421 fn skip_block_comment(
    422     chars: &[char],
    423     mut index: usize,
    424 ) -> Result<usize, RadrootsMeshAgentProtoError> {
    425     while index + 1 < chars.len() {
    426         if chars[index] == '*' && chars[index + 1] == '/' {
    427             return Ok(index + 2);
    428         }
    429         index += 1;
    430     }
    431     Err(RadrootsMeshAgentProtoError::InvalidSchema)
    432 }
    433 
    434 fn read_string_literal(
    435     chars: &[char],
    436     mut index: usize,
    437 ) -> Result<(String, usize), RadrootsMeshAgentProtoError> {
    438     let mut literal = String::new();
    439     while index < chars.len() {
    440         match chars[index] {
    441             '"' => return Ok((literal, index + 1)),
    442             '\\' => {
    443                 index += 1;
    444                 if index >= chars.len() {
    445                     return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    446                 }
    447                 literal.push(chars[index]);
    448             }
    449             ch => literal.push(ch),
    450         }
    451         index += 1;
    452     }
    453     Err(RadrootsMeshAgentProtoError::InvalidSchema)
    454 }
    455 
    456 fn is_ident_start(ch: char) -> bool {
    457     ch.is_ascii_alphabetic() || ch == '_'
    458 }
    459 
    460 fn is_ident_continue(ch: char) -> bool {
    461     ch.is_ascii_alphanumeric() || ch == '_'
    462 }
    463 
    464 fn is_number_continue(ch: char) -> bool {
    465     ch.is_ascii_hexdigit() || ch == 'x' || ch == 'X'
    466 }
    467 
    468 fn is_schema_symbol(ch: char) -> bool {
    469     matches!(
    470         ch,
    471         '@' | '{' | '}' | ';' | ':' | '(' | ')' | '.' | '$' | ',' | '='
    472     )
    473 }
    474 
    475 impl SchemaParser {
    476     fn parse(&mut self) -> Result<SchemaAst, RadrootsMeshAgentProtoError> {
    477         let mut ast = SchemaAst::default();
    478         while !self.is_done() {
    479             match self.peek() {
    480                 Some(SchemaToken::Symbol('@')) => self.parse_schema_id(&mut ast)?,
    481                 Some(SchemaToken::Symbol('$')) => self.parse_annotation(&mut ast)?,
    482                 Some(SchemaToken::Ident(value)) if value == "using" => self.skip_statement()?,
    483                 Some(SchemaToken::Ident(value)) if value == "struct" => {
    484                     self.index += 1;
    485                     let (name, decl) = self.parse_struct()?;
    486                     if ast.structs.insert(name, decl).is_some() {
    487                         return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    488                     }
    489                 }
    490                 Some(SchemaToken::Ident(value)) if value == "enum" => {
    491                     self.index += 1;
    492                     let (name, decl) = self.parse_enum()?;
    493                     if ast.enums.insert(name, decl).is_some() {
    494                         return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    495                     }
    496                 }
    497                 Some(SchemaToken::Symbol(';')) => {
    498                     self.index += 1;
    499                 }
    500                 _ => return Err(RadrootsMeshAgentProtoError::InvalidSchema),
    501             }
    502         }
    503         Ok(ast)
    504     }
    505 
    506     fn parse_schema_id(&mut self, ast: &mut SchemaAst) -> Result<(), RadrootsMeshAgentProtoError> {
    507         self.expect_symbol('@')?;
    508         let schema_id = self.expect_number()?;
    509         self.expect_symbol(';')?;
    510         if ast.schema_id.replace(schema_id).is_some() {
    511             return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    512         }
    513         Ok(())
    514     }
    515 
    516     fn parse_annotation(&mut self, ast: &mut SchemaAst) -> Result<(), RadrootsMeshAgentProtoError> {
    517         self.expect_symbol('$')?;
    518         let is_namespace = self.consume_ident("Cxx")
    519             && self.consume_symbol('.')
    520             && self.consume_ident("namespace")
    521             && self.consume_symbol('(');
    522         if is_namespace {
    523             let namespace = self.expect_string_literal()?;
    524             self.expect_symbol(')')?;
    525             self.expect_symbol(';')?;
    526             if ast.namespace.replace(namespace).is_some() {
    527                 return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    528             }
    529             return Ok(());
    530         }
    531         self.skip_statement()
    532     }
    533 
    534     fn parse_struct(&mut self) -> Result<(String, StructDecl), RadrootsMeshAgentProtoError> {
    535         let name = self.expect_ident()?;
    536         self.expect_symbol('{')?;
    537         let mut fields = BTreeMap::new();
    538         let mut ordinals = BTreeSet::new();
    539         while !self.consume_symbol('}') {
    540             let field_name = self.expect_ident()?;
    541             self.expect_symbol('@')?;
    542             let ordinal = parse_ordinal(&self.expect_number()?)?;
    543             self.expect_symbol(':')?;
    544             let field_type = self.parse_type()?;
    545             if !ordinals.insert(ordinal)
    546                 || fields
    547                     .insert(
    548                         field_name,
    549                         FieldDecl {
    550                             ordinal,
    551                             field_type,
    552                         },
    553                     )
    554                     .is_some()
    555             {
    556                 return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    557             }
    558         }
    559         Ok((name, StructDecl { fields }))
    560     }
    561 
    562     fn parse_enum(&mut self) -> Result<(String, EnumDecl), RadrootsMeshAgentProtoError> {
    563         let name = self.expect_ident()?;
    564         self.expect_symbol('{')?;
    565         let mut variants = BTreeMap::new();
    566         let mut ordinals = BTreeSet::new();
    567         while !self.consume_symbol('}') {
    568             let variant_name = self.expect_ident()?;
    569             self.expect_symbol('@')?;
    570             let ordinal = parse_ordinal(&self.expect_number()?)?;
    571             self.expect_symbol(';')?;
    572             if !ordinals.insert(ordinal) || variants.insert(variant_name, ordinal).is_some() {
    573                 return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    574             }
    575         }
    576         Ok((name, EnumDecl { variants }))
    577     }
    578 
    579     fn parse_type(&mut self) -> Result<String, RadrootsMeshAgentProtoError> {
    580         let mut field_type = String::new();
    581         while let Some(token) = self.peek() {
    582             if token == &SchemaToken::Symbol(';') {
    583                 self.index += 1;
    584                 if field_type.is_empty() {
    585                     return Err(RadrootsMeshAgentProtoError::InvalidSchema);
    586                 }
    587                 return Ok(field_type);
    588             }
    589             append_token_text(&mut field_type, token)?;
    590             self.index += 1;
    591         }
    592         Err(RadrootsMeshAgentProtoError::InvalidSchema)
    593     }
    594 
    595     fn skip_statement(&mut self) -> Result<(), RadrootsMeshAgentProtoError> {
    596         while !self.is_done() {
    597             if self.consume_symbol(';') {
    598                 return Ok(());
    599             }
    600             self.index += 1;
    601         }
    602         Err(RadrootsMeshAgentProtoError::InvalidSchema)
    603     }
    604 
    605     fn peek(&self) -> Option<&SchemaToken> {
    606         self.tokens.get(self.index)
    607     }
    608 
    609     fn is_done(&self) -> bool {
    610         self.index >= self.tokens.len()
    611     }
    612 
    613     fn consume_symbol(&mut self, symbol: char) -> bool {
    614         if self.peek() == Some(&SchemaToken::Symbol(symbol)) {
    615             self.index += 1;
    616             true
    617         } else {
    618             false
    619         }
    620     }
    621 
    622     fn consume_ident(&mut self, expected: &str) -> bool {
    623         match self.peek() {
    624             Some(SchemaToken::Ident(value)) if value == expected => {
    625                 self.index += 1;
    626                 true
    627             }
    628             _ => false,
    629         }
    630     }
    631 
    632     fn expect_symbol(&mut self, expected: char) -> Result<(), RadrootsMeshAgentProtoError> {
    633         if self.consume_symbol(expected) {
    634             Ok(())
    635         } else {
    636             Err(RadrootsMeshAgentProtoError::InvalidSchema)
    637         }
    638     }
    639 
    640     fn expect_ident(&mut self) -> Result<String, RadrootsMeshAgentProtoError> {
    641         match self.peek().cloned() {
    642             Some(SchemaToken::Ident(value)) => {
    643                 self.index += 1;
    644                 Ok(value)
    645             }
    646             _ => Err(RadrootsMeshAgentProtoError::InvalidSchema),
    647         }
    648     }
    649 
    650     fn expect_number(&mut self) -> Result<String, RadrootsMeshAgentProtoError> {
    651         match self.peek().cloned() {
    652             Some(SchemaToken::Number(value)) => {
    653                 self.index += 1;
    654                 Ok(value)
    655             }
    656             _ => Err(RadrootsMeshAgentProtoError::InvalidSchema),
    657         }
    658     }
    659 
    660     fn expect_string_literal(&mut self) -> Result<String, RadrootsMeshAgentProtoError> {
    661         match self.peek().cloned() {
    662             Some(SchemaToken::StringLiteral(value)) => {
    663                 self.index += 1;
    664                 Ok(value)
    665             }
    666             _ => Err(RadrootsMeshAgentProtoError::InvalidSchema),
    667         }
    668     }
    669 }
    670 
    671 fn append_token_text(
    672     output: &mut String,
    673     token: &SchemaToken,
    674 ) -> Result<(), RadrootsMeshAgentProtoError> {
    675     match token {
    676         SchemaToken::Ident(value) | SchemaToken::Number(value) => {
    677             output.push_str(value.as_str());
    678             Ok(())
    679         }
    680         SchemaToken::Symbol(symbol) if matches!(symbol, '(' | ')' | '.') => {
    681             output.push(*symbol);
    682             Ok(())
    683         }
    684         _ => Err(RadrootsMeshAgentProtoError::InvalidSchema),
    685     }
    686 }
    687 
    688 fn parse_ordinal(value: &str) -> Result<u16, RadrootsMeshAgentProtoError> {
    689     value
    690         .parse::<u16>()
    691         .map_err(|_| RadrootsMeshAgentProtoError::InvalidSchema)
    692 }
    693 
    694 fn validate_fields(
    695     ast: &SchemaAst,
    696     fields: &[RequiredField],
    697     error: RadrootsMeshAgentProtoError,
    698 ) -> Result<(), RadrootsMeshAgentProtoError> {
    699     for required in fields {
    700         let decl = ast
    701             .structs
    702             .get(required.owner)
    703             .ok_or_else(|| error.clone())?;
    704         let field_matches = decl.fields.get(required.name).is_some_and(|field| {
    705             field.ordinal == required.ordinal && field.field_type == required.field_type
    706         });
    707         if !field_matches {
    708             return Err(error.clone());
    709         }
    710     }
    711     Ok(())
    712 }
    713 
    714 fn validate_variants(
    715     ast: &SchemaAst,
    716     variants: &[RequiredVariant],
    717     error: RadrootsMeshAgentProtoError,
    718 ) -> Result<(), RadrootsMeshAgentProtoError> {
    719     for required in variants {
    720         let decl = ast.enums.get(required.owner).ok_or_else(|| error.clone())?;
    721         let variant_matches = decl
    722             .variants
    723             .get(required.name)
    724             .is_some_and(|ordinal| *ordinal == required.ordinal);
    725         if !variant_matches {
    726             return Err(error.clone());
    727         }
    728     }
    729     Ok(())
    730 }
    731 
    732 fn reject_fields(
    733     ast: &SchemaAst,
    734     fields: &[ForbiddenField],
    735     error: RadrootsMeshAgentProtoError,
    736 ) -> Result<(), RadrootsMeshAgentProtoError> {
    737     for forbidden in fields {
    738         if ast
    739             .structs
    740             .get(forbidden.owner)
    741             .is_some_and(|decl| decl.fields.contains_key(forbidden.name))
    742         {
    743             return Err(error.clone());
    744         }
    745     }
    746     Ok(())
    747 }
    748 
    749 fn reject_variants(
    750     ast: &SchemaAst,
    751     variants: &[ForbiddenVariant],
    752     error: RadrootsMeshAgentProtoError,
    753 ) -> Result<(), RadrootsMeshAgentProtoError> {
    754     for forbidden in variants {
    755         if ast
    756             .enums
    757             .get(forbidden.owner)
    758             .is_some_and(|decl| decl.variants.contains_key(forbidden.name))
    759         {
    760             return Err(error.clone());
    761         }
    762     }
    763     Ok(())
    764 }
    765 
    766 fn reject_enums(
    767     ast: &SchemaAst,
    768     enums: &[&str],
    769     error: RadrootsMeshAgentProtoError,
    770 ) -> Result<(), RadrootsMeshAgentProtoError> {
    771     for forbidden in enums {
    772         if ast.enums.contains_key(*forbidden) {
    773             return Err(error.clone());
    774         }
    775     }
    776     Ok(())
    777 }