Skip to main content

webots_proto/
lib.rs

1#[cfg(feature = "validation")]
2mod imports;
3
4#[cfg(feature = "validation")]
5use std::collections::HashSet;
6
7use thiserror::Error;
8
9pub use webots_proto_ast as ast;
10#[cfg(feature = "resolver")]
11pub use webots_proto_resolver as resolver;
12#[cfg(feature = "schema")]
13pub use webots_proto_schema as schema;
14#[cfg(feature = "schema")]
15pub use webots_proto_schema::types;
16
17pub use webots_proto_ast::{AstNode, FieldValue, Proto};
18#[cfg(feature = "resolver")]
19pub use webots_proto_resolver::{ProtoResolver, ResolveOptions};
20#[cfg(feature = "r2025a")]
21pub use webots_proto_schema::r2025a;
22#[cfg(feature = "schema")]
23pub use webots_proto_schema::{
24    Diagnostic, DiagnosticSet, Error as SchemaError, NodeSchema, Result as SchemaResult,
25    SchemaField, Severity, ValidationContext, ast_to_r2025a_node, r2025a_node_to_ast,
26};
27#[cfg(feature = "template")]
28pub use webots_proto_template::{
29    RenderContext, RenderOptions, RenderWebotsVersion, TemplateContext, TemplateError,
30    TemplateEvaluator, TemplateField, TemplateFieldBinding, TemplateWebotsVersion,
31};
32
33#[derive(Debug, Error)]
34pub enum Error {
35    #[error(transparent)]
36    Ast(#[from] webots_proto_ast::ProtoError),
37    #[cfg(feature = "template")]
38    #[error(transparent)]
39    Template(#[from] webots_proto_template::TemplateError),
40    #[cfg(feature = "resolver")]
41    #[error(transparent)]
42    Resolver(#[from] webots_proto_resolver::ProtoError),
43    #[error(transparent)]
44    Io(#[from] std::io::Error),
45}
46
47pub type Result<T> = std::result::Result<T, Error>;
48
49#[cfg(feature = "validation")]
50#[derive(Debug, Clone, Default)]
51pub struct ValidationOptions {
52    pub local_externproto_naming: bool,
53    pub runtime_semantics: bool,
54}
55
56#[cfg(feature = "validation")]
57impl ValidationOptions {
58    pub fn new() -> Self {
59        Self {
60            local_externproto_naming: true,
61            runtime_semantics: true,
62        }
63    }
64
65    pub fn with_local_externproto_naming(mut self, enabled: bool) -> Self {
66        self.local_externproto_naming = enabled;
67        self
68    }
69
70    pub fn with_runtime_semantics(mut self, enabled: bool) -> Self {
71        self.runtime_semantics = enabled;
72        self
73    }
74}
75
76#[cfg(any(feature = "template", feature = "validation"))]
77pub trait ProtoExt {
78    #[cfg(feature = "template")]
79    fn render(&self, options: &RenderOptions) -> Result<String>;
80    #[cfg(feature = "validation")]
81    fn validate(&self) -> Result<DiagnosticSet>;
82    #[cfg(feature = "validation")]
83    fn validate_with_options(&self, options: &ValidationOptions) -> Result<DiagnosticSet>;
84}
85
86#[cfg(any(feature = "template", feature = "validation"))]
87impl ProtoExt for Proto {
88    #[cfg(feature = "template")]
89    fn render(&self, options: &RenderOptions) -> Result<String> {
90        Ok(webots_proto_template::render(self, options)?)
91    }
92
93    #[cfg(feature = "validation")]
94    fn validate(&self) -> Result<DiagnosticSet> {
95        self.validate_with_options(&ValidationOptions::new())
96    }
97
98    #[cfg(feature = "validation")]
99    fn validate_with_options(&self, options: &ValidationOptions) -> Result<DiagnosticSet> {
100        validate_with_options(self, options)
101    }
102}
103
104#[cfg(feature = "validation")]
105pub fn validate(proto: &Proto) -> Result<DiagnosticSet> {
106    validate_with_options(proto, &ValidationOptions::new())
107}
108
109#[cfg(feature = "validation")]
110pub fn validate_with_options(proto: &Proto, options: &ValidationOptions) -> Result<DiagnosticSet> {
111    let mut diagnostics = schema::validate(proto);
112
113    if options.local_externproto_naming
114        && let Some(source_path) = &proto.source_path
115    {
116        diagnostics.extend(imports::validate_local_externproto_naming(
117            source_path,
118            proto,
119        )?);
120    }
121
122    if options.runtime_semantics
123        && let Some(source_path) = &proto.source_path
124    {
125        let content = proto
126            .source_content
127            .clone()
128            .map(Ok)
129            .unwrap_or_else(|| std::fs::read_to_string(source_path).map_err(Error::from))?;
130
131        if let Some(base_path) = source_path.parent() {
132            let expanded_root = ProtoResolver::new(ResolveOptions::new())
133                .to_root_node(&content, Some(base_path))?;
134            diagnostics.extend(schema::validate_runtime_semantics(&expanded_root));
135        }
136    }
137
138    Ok(dedupe_diagnostics(diagnostics))
139}
140
141#[cfg(feature = "validation")]
142fn dedupe_diagnostics(diagnostics: DiagnosticSet) -> DiagnosticSet {
143    let mut deduped = DiagnosticSet::new();
144    let mut seen = HashSet::new();
145    for diagnostic in diagnostics.iter() {
146        if seen.insert(diagnostic.clone()) {
147            deduped.add(diagnostic.clone());
148        }
149    }
150    deduped
151}