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 }