1pub mod dump;
13pub mod error;
14pub mod types;
15mod validate;
16
17pub use error::HirError;
18pub use types::{
19 Annotation, AnnotationArg, Declaration, DictEntry, DirectiveRecord, DirectiveValue, Event,
20 Expr, Generator, OptimizationState, Position, PreprocessingSnapshot, PreprocessingState,
21 Program, Protocol, Rule, RuleEntry, Settings, SettingsListElement, SettingsNode, SourceFile,
22 Span, Stmt, SwitchArm, TranslationState, default_var_index,
23};
24
25use serde_json::Value;
26
27pub fn parse_str(input: &str) -> Result<Program, HirError> {
33 let value: Value = serde_json::from_str(input)?;
34 parse_value(value)
35}
36
37pub fn parse_value(value: Value) -> Result<Program, HirError> {
39 validate::check_envelope(&value)?;
40 validate::check_unknown_kinds(&value)?;
41 let program: Program = serde_json::from_value(value)?;
42 program.validate()?;
43 Ok(program)
44}
45
46impl Program {
47 pub fn validate(&self) -> Result<(), HirError> {
51 validate::validate_program(self)
52 }
53
54 pub fn dump(&self) -> String {
57 dump::dump(self)
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use serde_json::json;
64
65 use super::parse_value;
66 use super::validate::check_envelope;
67
68 #[test]
69 fn v2_envelope_is_accepted_and_v1_is_rejected_before_body_inspection() {
70 let v2 = json!({
71 "protocol": { "name": "wright/opy-hir", "version": "2.0.0" }
72 });
73 assert!(check_envelope(&v2).is_ok());
74
75 let v1 = json!({
76 "protocol": { "name": "wright/opy-hir", "version": "1.1.0" },
77 "rules": [{ "kind": "malformed-v1-body" }]
78 });
79 let error = check_envelope(&v1).expect_err("v1 payload must not enter the v2 parser");
80 assert_eq!(error.code(), "incompatible-protocol");
81 }
82
83 #[test]
84 fn unknown_conditional_condition_kind_preserves_unsupported_node_span() {
85 let error = parse_value(json!({
86 "protocol": { "name": "wright/opy-hir", "version": "2.0.0" },
87 "rules": [{
88 "event": { "args": [] },
89 "conditions": [],
90 "actions": [{
91 "kind": "expr",
92 "expr": {
93 "kind": "conditional",
94 "thenValue": { "kind": "number", "value": 1 },
95 "condition": {
96 "kind": "future-expression",
97 "span": {
98 "file": 0,
99 "start": { "line": 4, "col": 12 },
100 "end": { "line": 4, "col": 20 }
101 }
102 },
103 "elseValue": { "kind": "number", "value": 0 }
104 }
105 }]
106 }]
107 }))
108 .expect_err("unknown kinds nested in conditional conditions must be rejected");
109
110 assert_eq!(error.code(), "unsupported-node");
111 assert_eq!(error.message(), "unsupported node kind 'future-expression'");
112 assert_eq!(error.span().unwrap().start.line, 4);
113 assert_eq!(error.span().unwrap().start.col, 12);
114 }
115}