lib

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

schema.rs (18467B)


      1 use radroots_mesh_agent_proto::{
      2     RADROOTS_MESH_AGENT_SCHEMA, RADROOTS_MESH_AGENT_SCHEMA_ID,
      3     RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, RadrootsMeshAgentProtoError, schema_sha256_hex,
      4     validate_schema, validate_schema_text,
      5 };
      6 
      7 #[test]
      8 fn schema_declares_mesh_agent_v1_surface() {
      9     validate_schema().expect("schema validates");
     10 
     11     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains(RADROOTS_MESH_AGENT_SCHEMA_ID));
     12     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains(RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE));
     13     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("struct MeshAgentRequest"));
     14     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("struct MeshAgentResponse"));
     15     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("struct MeshAgentStatusRequest"));
     16     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("struct MeshAgentStatusResponse"));
     17     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("struct MeshAgentPublishRequest"));
     18     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("struct MeshAgentPublishResponse"));
     19     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("enum MeshAgentTransportKind"));
     20     assert!(RADROOTS_MESH_AGENT_SCHEMA.contains("enum MeshAgentTransportOutcome"));
     21 }
     22 
     23 #[test]
     24 fn schema_is_canonical_transport_v1_authority() {
     25     validate_schema().expect("schema validates");
     26 
     27     assert_eq!(RADROOTS_MESH_AGENT_SCHEMA_ID, "0xb83e0c4f71838d9a");
     28     assert_ne!(RADROOTS_MESH_AGENT_SCHEMA_ID, "0xdecafbaddecaf001");
     29     assert_eq!(
     30         validate_schema_text(
     31             RADROOTS_MESH_AGENT_SCHEMA
     32                 .replace(RADROOTS_MESH_AGENT_SCHEMA_ID, "0xdecafbaddecaf001")
     33                 .as_str(),
     34         ),
     35         Err(RadrootsMeshAgentProtoError::MissingSchemaId)
     36     );
     37 }
     38 
     39 #[test]
     40 fn schema_covers_transport_v1_status_and_publish_intent() {
     41     for required in [
     42         "requestId @0 :Text;",
     43         "statusRequest @3 :MeshAgentStatusRequest;",
     44         "publishRequest @4 :MeshAgentPublishRequest;",
     45         "statusResponse @4 :MeshAgentStatusResponse;",
     46         "publishResponse @5 :MeshAgentPublishResponse;",
     47         "includeTransports @0 :Bool;",
     48         "transports @0 :List(MeshAgentTransportStatus);",
     49         "transport @0 :MeshAgentTransportKind;",
     50         "profileId @1 :Text;",
     51         "configured @3 :Bool;",
     52         "implementation @4 :MeshAgentImplementation;",
     53         "usableForDelivery @5 :Bool;",
     54         "message @6 :Text;",
     55         "real @0;",
     56         "mock @1;",
     57         "previewUnavailable @2;",
     58         "reticulum @0;",
     59         "publishRequestId @0 :Text;",
     60         "payloadCbor @1 :Data;",
     61         "eventId @2 :Text;",
     62         "targetFingerprint @3 :Text;",
     63         "transportReceipts @2 :List(MeshAgentTransportReceipt);",
     64         "eventId @3 :Text;",
     65         "outcome @2 :MeshAgentTransportOutcome;",
     66         "deferredUntilImplemented @4;",
     67         "transportUnavailable @8;",
     68         "errors @3 :List(MeshAgentError);",
     69     ] {
     70         assert!(RADROOTS_MESH_AGENT_SCHEMA.contains(required), "{required}");
     71     }
     72 }
     73 
     74 #[test]
     75 fn schema_excludes_retired_transport_status_vocabulary() {
     76     validate_schema().expect("schema validates");
     77 
     78     for forbidden in [
     79         "readiness",
     80         "implementationState",
     81         "publishUsable",
     82         "fetchUsable",
     83         "redactedMessage",
     84         "previewNoop",
     85     ] {
     86         assert!(
     87             !RADROOTS_MESH_AGENT_SCHEMA.contains(forbidden),
     88             "{forbidden}"
     89         );
     90     }
     91 }
     92 
     93 #[test]
     94 fn schema_hash_is_deterministic() {
     95     let first = schema_sha256_hex();
     96     let second = schema_sha256_hex();
     97 
     98     assert_eq!(first, second);
     99     assert_eq!(
    100         first,
    101         "712aaa11dfec25abf44edb3b0be447f0596442271d46a8b1d9fedb7c3df00bb2"
    102     );
    103 }
    104 
    105 #[test]
    106 fn schema_validator_rejects_missing_required_surface() {
    107     assert_eq!(
    108         validate_schema_text("@0xb83e0c4f71838d9a;")
    109             .expect_err("schema missing namespace and structs"),
    110         RadrootsMeshAgentProtoError::MissingNamespace
    111     );
    112 }
    113 
    114 #[test]
    115 fn schema_validator_reports_each_missing_required_surface() {
    116     let valid = RADROOTS_MESH_AGENT_SCHEMA;
    117     let cases = [
    118         (
    119             valid.replace(RADROOTS_MESH_AGENT_SCHEMA_ID, "0x0000000000000000"),
    120             RadrootsMeshAgentProtoError::MissingSchemaId,
    121         ),
    122         (
    123             valid.replace(RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, "radroots::missing"),
    124             RadrootsMeshAgentProtoError::MissingNamespace,
    125         ),
    126         (
    127             valid.replace("struct MeshAgentRequest", "struct MissingMeshAgentRequest"),
    128             RadrootsMeshAgentProtoError::MissingRequest,
    129         ),
    130         (
    131             valid.replace("frameCbor @2 :Data;", "frameBytes @2 :Data;"),
    132             RadrootsMeshAgentProtoError::MissingRequest,
    133         ),
    134         (
    135             valid.replace("enum MeshAgentAction", "enum MissingMeshAgentAction"),
    136             RadrootsMeshAgentProtoError::MissingAction,
    137         ),
    138         (
    139             valid.replace("validateFrame @0;", "validateSomethingElse @0;"),
    140             RadrootsMeshAgentProtoError::MissingAction,
    141         ),
    142         (
    143             valid.replace("stageDelivery @1;", "stageSomethingElse @1;"),
    144             RadrootsMeshAgentProtoError::MissingAction,
    145         ),
    146         (
    147             valid.replace("observeEventHead @2;", "observeSomethingElse @2;"),
    148             RadrootsMeshAgentProtoError::MissingAction,
    149         ),
    150         (
    151             valid.replace("status @3;", "agentStatus @3;"),
    152             RadrootsMeshAgentProtoError::MissingAction,
    153         ),
    154         (
    155             valid.replace("publish @4;", "publishSomethingElse @4;"),
    156             RadrootsMeshAgentProtoError::MissingAction,
    157         ),
    158         (
    159             valid.replace(
    160                 "struct MeshAgentResponse",
    161                 "struct MissingMeshAgentResponse",
    162             ),
    163             RadrootsMeshAgentProtoError::MissingResponse,
    164         ),
    165         (
    166             valid.replace(
    167                 "enum MeshAgentResponseStatus",
    168                 "enum MissingMeshAgentResponseStatus",
    169             ),
    170             RadrootsMeshAgentProtoError::MissingResponse,
    171         ),
    172         (
    173             valid.replace("struct MeshAgentReceipt", "struct MissingMeshAgentReceipt"),
    174             RadrootsMeshAgentProtoError::MissingReceipt,
    175         ),
    176         (
    177             valid.replace(
    178                 "acceptedEventHeads @1 :List(Text);",
    179                 "acceptedEventIds @1 :List(Text);",
    180             ),
    181             RadrootsMeshAgentProtoError::MissingReceipt,
    182         ),
    183         (
    184             valid.replace(
    185                 "struct MeshAgentStatusRequest",
    186                 "struct MissingMeshAgentStatusRequest",
    187             ),
    188             RadrootsMeshAgentProtoError::MissingStatusSurface,
    189         ),
    190         (
    191             valid.replace(
    192                 "transports @0 :List(MeshAgentTransportStatus);",
    193                 "transportStatuses @0 :List(MeshAgentTransportStatus);",
    194             ),
    195             RadrootsMeshAgentProtoError::MissingStatusSurface,
    196         ),
    197         (
    198             valid.replace(
    199                 "transport @0 :MeshAgentTransportKind;",
    200                 "transportKind @0 :MeshAgentTransportKind;",
    201             ),
    202             RadrootsMeshAgentProtoError::MissingStatusSurface,
    203         ),
    204         (
    205             valid.replace("profileId @1 :Text;", "transportProfileId @1 :Text;"),
    206             RadrootsMeshAgentProtoError::MissingStatusSurface,
    207         ),
    208         (
    209             valid.replace("configured @3 :Bool;", "configured @3 :Text;"),
    210             RadrootsMeshAgentProtoError::MissingStatusSurface,
    211         ),
    212         (
    213             valid.replace(
    214                 "implementation @4 :MeshAgentImplementation;",
    215                 "implementation @4 :Text;",
    216             ),
    217             RadrootsMeshAgentProtoError::MissingStatusSurface,
    218         ),
    219         (
    220             valid.replace("usableForDelivery @5 :Bool;", "publishUsable @5 :Bool;"),
    221             RadrootsMeshAgentProtoError::MissingStatusSurface,
    222         ),
    223         (
    224             valid.replace("message @6 :Text;", "redactedMessage @6 :Text;"),
    225             RadrootsMeshAgentProtoError::MissingStatusSurface,
    226         ),
    227         (
    228             valid.replace("reticulum @0;", "reticulumPreview @0;"),
    229             RadrootsMeshAgentProtoError::MissingStatusSurface,
    230         ),
    231         (
    232             valid.replace("previewUnavailable @2;", "previewUnavailable @3;"),
    233             RadrootsMeshAgentProtoError::MissingStatusSurface,
    234         ),
    235         (
    236             valid.replace(
    237                 "struct MeshAgentPublishRequest",
    238                 "struct MissingMeshAgentPublishRequest",
    239             ),
    240             RadrootsMeshAgentProtoError::MissingPublishSurface,
    241         ),
    242         (
    243             valid.replace("publishRequestId @0 :Text;", "operationId @0 :Text;"),
    244             RadrootsMeshAgentProtoError::MissingPublishSurface,
    245         ),
    246         (
    247             valid.replace("eventId @2 :Text;", "eventIdentifier @2 :Text;"),
    248             RadrootsMeshAgentProtoError::MissingPublishSurface,
    249         ),
    250         (
    251             valid.replace("targetFingerprint @3 :Text;", "transportTarget @3 :Text;"),
    252             RadrootsMeshAgentProtoError::MissingPublishSurface,
    253         ),
    254         (
    255             valid.replace(
    256                 "transportReceipts @2 :List(MeshAgentTransportReceipt);",
    257                 "transportResults @2 :List(MeshAgentTransportReceipt);",
    258             ),
    259             RadrootsMeshAgentProtoError::MissingPublishSurface,
    260         ),
    261         (
    262             valid.replace(
    263                 "outcome @2 :MeshAgentTransportOutcome;",
    264                 "outcome @2 :Text;",
    265             ),
    266             RadrootsMeshAgentProtoError::MissingPublishSurface,
    267         ),
    268         (
    269             valid.replace("message @3 :Text;", "redactedMessage @3 :Text;"),
    270             RadrootsMeshAgentProtoError::MissingPublishSurface,
    271         ),
    272         (
    273             valid.replace(
    274                 "transportUnavailable @8;",
    275                 "transportUnavailablePreview @8;",
    276             ),
    277             RadrootsMeshAgentProtoError::MissingPublishSurface,
    278         ),
    279         (
    280             valid.replace("struct MeshAgentError", "struct MissingMeshAgentError"),
    281             RadrootsMeshAgentProtoError::MissingError,
    282         ),
    283     ];
    284 
    285     for (schema, error) in cases {
    286         assert_eq!(validate_schema_text(schema.as_str()), Err(error));
    287     }
    288 }
    289 
    290 #[test]
    291 fn schema_validator_rejects_reintroduced_retired_status_vocabulary() {
    292     let valid = RADROOTS_MESH_AGENT_SCHEMA;
    293     let cases = [
    294         (
    295             valid.replace(
    296                 "  transports @0 :List(MeshAgentTransportStatus);",
    297                 "  transports @0 :List(MeshAgentTransportStatus);\n  readiness @1 :MeshAgentReadinessState;",
    298             ),
    299             RadrootsMeshAgentProtoError::MissingStatusSurface,
    300         ),
    301         (
    302             valid.replace(
    303                 "  transports @0 :List(MeshAgentTransportStatus);",
    304                 "  transports @0 :List(MeshAgentTransportStatus);\n  implementationState @1 :MeshAgentImplementationState;",
    305             ),
    306             RadrootsMeshAgentProtoError::MissingStatusSurface,
    307         ),
    308         (
    309             valid.replace(
    310                 "  message @6 :Text;",
    311                 "  message @6 :Text;\n  publishUsable @7 :Bool;",
    312             ),
    313             RadrootsMeshAgentProtoError::MissingStatusSurface,
    314         ),
    315         (
    316             valid.replace(
    317                 "  message @6 :Text;",
    318                 "  message @6 :Text;\n  fetchUsable @7 :Bool;",
    319             ),
    320             RadrootsMeshAgentProtoError::MissingStatusSurface,
    321         ),
    322         (
    323             valid.replace(
    324                 "  message @6 :Text;",
    325                 "  message @6 :Text;\n  redactedMessage @7 :Text;",
    326             ),
    327             RadrootsMeshAgentProtoError::MissingStatusSurface,
    328         ),
    329         (
    330             valid.replace(
    331                 "  previewUnavailable @2;",
    332                 "  previewUnavailable @2;\n  previewNoop @3;",
    333             ),
    334             RadrootsMeshAgentProtoError::MissingStatusSurface,
    335         ),
    336         (
    337             valid.replace(
    338                 "enum MeshAgentTransportKind",
    339                 "enum MeshAgentReadinessState {\n  ready @0;\n}\n\nenum MeshAgentTransportKind",
    340             ),
    341             RadrootsMeshAgentProtoError::MissingStatusSurface,
    342         ),
    343         (
    344             valid.replace(
    345                 "  message @3 :Text;",
    346                 "  message @3 :Text;\n  redactedMessage @4 :Text;",
    347             ),
    348             RadrootsMeshAgentProtoError::MissingPublishSurface,
    349         ),
    350     ];
    351 
    352     for (schema, error) in cases {
    353         assert_eq!(validate_schema_text(schema.as_str()), Err(error));
    354     }
    355 }
    356 
    357 #[test]
    358 fn schema_validator_rejects_commented_required_declarations() {
    359     let schema =
    360         RADROOTS_MESH_AGENT_SCHEMA.replace("  frameCbor @2 :Data;", "  # frameCbor @2 :Data;");
    361 
    362     assert_eq!(
    363         validate_schema_text(schema.as_str()),
    364         Err(RadrootsMeshAgentProtoError::MissingRequest)
    365     );
    366 }
    367 
    368 #[test]
    369 fn schema_validator_rejects_misplaced_required_declarations() {
    370     let schema = RADROOTS_MESH_AGENT_SCHEMA
    371         .replace(
    372             "  acceptedEventHeads @1 :List(Text);",
    373             "  acceptedEventIds @1 :List(Text);",
    374         )
    375         .replace(
    376             "  message @1 :Text;",
    377             "  message @1 :Text;\n  acceptedEventHeads @2 :List(Text);",
    378         );
    379 
    380     assert_eq!(
    381         validate_schema_text(schema.as_str()),
    382         Err(RadrootsMeshAgentProtoError::MissingReceipt)
    383     );
    384 }
    385 
    386 #[test]
    387 fn schema_validator_rejects_duplicate_incompatible_declarations() {
    388     let duplicate_ordinal = RADROOTS_MESH_AGENT_SCHEMA.replace(
    389         "  frameCbor @2 :Data;",
    390         "  frameCbor @2 :Data;\n  frameBytes @2 :Data;",
    391     );
    392     let duplicate_name = RADROOTS_MESH_AGENT_SCHEMA.replace(
    393         "  frameCbor @2 :Data;",
    394         "  frameCbor @2 :Data;\n  frameCbor @9 :Text;",
    395     );
    396 
    397     assert_eq!(
    398         validate_schema_text(duplicate_ordinal.as_str()),
    399         Err(RadrootsMeshAgentProtoError::InvalidSchema)
    400     );
    401     assert_eq!(
    402         validate_schema_text(duplicate_name.as_str()),
    403         Err(RadrootsMeshAgentProtoError::InvalidSchema)
    404     );
    405 }
    406 
    407 #[test]
    408 fn schema_validator_rejects_type_drift() {
    409     let request_type_drift =
    410         RADROOTS_MESH_AGENT_SCHEMA.replace("  frameCbor @2 :Data;", "  frameCbor @2 :Text;");
    411     let request_ordinal_drift =
    412         RADROOTS_MESH_AGENT_SCHEMA.replace("  frameCbor @2 :Data;", "  frameCbor @9 :Data;");
    413     let request_numeric_type_drift =
    414         RADROOTS_MESH_AGENT_SCHEMA.replace("  frameCbor @2 :Data;", "  frameCbor @2 :123;");
    415     let status_type_drift = RADROOTS_MESH_AGENT_SCHEMA.replace(
    416         "  includeTransports @0 :Bool;",
    417         "  includeTransports @0 :Text;",
    418     );
    419 
    420     assert_eq!(
    421         validate_schema_text(request_type_drift.as_str()),
    422         Err(RadrootsMeshAgentProtoError::MissingRequest)
    423     );
    424     assert_eq!(
    425         validate_schema_text(request_ordinal_drift.as_str()),
    426         Err(RadrootsMeshAgentProtoError::MissingRequest)
    427     );
    428     assert_eq!(
    429         validate_schema_text(request_numeric_type_drift.as_str()),
    430         Err(RadrootsMeshAgentProtoError::MissingRequest)
    431     );
    432     assert_eq!(
    433         validate_schema_text(status_type_drift.as_str()),
    434         Err(RadrootsMeshAgentProtoError::MissingStatusSurface)
    435     );
    436 }
    437 
    438 #[test]
    439 fn schema_validator_accepts_lexical_trivia_and_ignored_statements() {
    440     let decorated = format!(
    441         "# hash comment\n// slash comment\n/* *x*/\nusing Escaped = import \"schema\\\\\\\"name\";\n$Other.annotation(\"ignored\");\n;\n{RADROOTS_MESH_AGENT_SCHEMA}"
    442     );
    443 
    444     assert_eq!(validate_schema_text(decorated.as_str()), Ok(()));
    445 }
    446 
    447 #[test]
    448 fn schema_validator_rejects_malformed_lexical_and_parser_edges() {
    449     let malformed = [
    450         "!",
    451         "/",
    452         "/* unterminated",
    453         "\"unterminated",
    454         "\"trailing\\",
    455         "using Alias",
    456         "@;",
    457         "@1",
    458         "$Cxx.namespace(value);",
    459         "struct",
    460         "struct A",
    461         "struct A { field @x :Text; }",
    462         "struct A { field @0 Text; }",
    463         "struct A { field @0 :; }",
    464         "struct A { field @0 :Text",
    465         "struct A { field @0 :\"Text\"; }",
    466         "struct A { field @0 :@; }",
    467         "enum A { value @0 }",
    468         "enum A { value @65536; }",
    469         "bogus",
    470         "@1; @2;",
    471         "$Cxx.namespace(\"a\"); $Cxx.namespace(\"b\");",
    472         "struct A {} struct A {}",
    473         "enum A {} enum A {}",
    474         "enum A { first @0; second @0; }",
    475         "enum A { first @0; first @1; }",
    476     ];
    477 
    478     for schema in malformed {
    479         assert_eq!(
    480             validate_schema_text(schema),
    481             Err(RadrootsMeshAgentProtoError::InvalidSchema),
    482             "{schema}"
    483         );
    484     }
    485 }
    486 
    487 #[test]
    488 fn schema_validator_ignores_non_namespace_annotations() {
    489     for schema in ["#", "$Other;", "$Cxx;", "$Cxx.other;", "$Cxx.namespace;"] {
    490         assert_eq!(
    491             validate_schema_text(schema),
    492             Err(RadrootsMeshAgentProtoError::MissingSchemaId),
    493             "{schema}"
    494         );
    495     }
    496 }
    497 
    498 #[test]
    499 fn mesh_agent_proto_errors_have_stable_display_strings() {
    500     let cases = [
    501         (
    502             RadrootsMeshAgentProtoError::InvalidSchema,
    503             "mesh agent schema is invalid",
    504         ),
    505         (
    506             RadrootsMeshAgentProtoError::MissingSchemaId,
    507             "mesh agent schema id is missing",
    508         ),
    509         (
    510             RadrootsMeshAgentProtoError::MissingNamespace,
    511             "mesh agent schema namespace is missing",
    512         ),
    513         (
    514             RadrootsMeshAgentProtoError::MissingRequest,
    515             "mesh agent request schema is missing",
    516         ),
    517         (
    518             RadrootsMeshAgentProtoError::MissingAction,
    519             "mesh agent action schema is missing",
    520         ),
    521         (
    522             RadrootsMeshAgentProtoError::MissingResponse,
    523             "mesh agent response schema is missing",
    524         ),
    525         (
    526             RadrootsMeshAgentProtoError::MissingReceipt,
    527             "mesh agent receipt schema is missing",
    528         ),
    529         (
    530             RadrootsMeshAgentProtoError::MissingStatusSurface,
    531             "mesh agent status schema surface is missing",
    532         ),
    533         (
    534             RadrootsMeshAgentProtoError::MissingPublishSurface,
    535             "mesh agent publish schema surface is missing",
    536         ),
    537         (
    538             RadrootsMeshAgentProtoError::MissingError,
    539             "mesh agent error schema is missing",
    540         ),
    541     ];
    542 
    543     for (error, message) in cases {
    544         assert_eq!(error.to_string(), message);
    545     }
    546 }