Skip to content

Commit d359949

Browse files
authored
feat(plc.json-validation): Add validation for build description file (#994)
* initial commit * add schema, optional fields, validation method, tests, diagnostics adjust integration tests * remove unnecessary serde attr, move schema to file * add clang error locations to serde errors/diagnostics
1 parent bc1c805 commit d359949

21 files changed

+921
-109
lines changed

Cargo.lock

+431-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/plc_diagnostics/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ edition = "2021"
99
codespan-reporting = "0.11.1"
1010
plc_ast = { path = "../plc_ast" }
1111
plc_source = { path = "../plc_source" }
12+
serde_json = "1"

compiler/plc_diagnostics/src/diagnostics.rs

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use std::{error::Error, fmt::Display, ops::Range};
22

33
use plc_ast::ast::{AstNode, DataTypeDeclaration, DiagnosticInfo, PouType};
4-
use plc_source::source_location::SourceLocation;
4+
5+
use plc_source::{
6+
source_location::{SourceLocation, SourceLocationFactory},
7+
BuildDescriptionSource,
8+
};
59

610
use crate::errno::ErrNo;
711

@@ -16,6 +20,13 @@ pub enum Diagnostic {
1620
CombinedDiagnostic { message: String, inner_diagnostics: Vec<Diagnostic>, err_no: ErrNo },
1721
}
1822

23+
#[derive(Debug)]
24+
pub struct SerdeError {
25+
message: String,
26+
line: usize,
27+
column: usize,
28+
}
29+
1930
impl Display for Diagnostic {
2031
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2132
write!(f, "{}: {}", self.get_type(), self.get_message())?;
@@ -784,6 +795,39 @@ impl Diagnostic {
784795
err_no: ErrNo::cfc__unnamed_control,
785796
}
786797
}
798+
799+
pub fn invalid_build_description_file(message: String, location: Option<SourceLocation>) -> Diagnostic {
800+
let range = if let Some(range) = location { vec![range] } else { vec![SourceLocation::internal()] };
801+
Diagnostic::SemanticError { message, range, err_no: ErrNo::plc_json__invalid }
802+
}
803+
}
804+
805+
// Necessary in-between step to convert serde error to diagnostics, since there is
806+
// a conflicting `From<T: Error>` impl for `Diagnostic`
807+
impl From<serde_json::Error> for SerdeError {
808+
fn from(value: serde_json::Error) -> Self {
809+
let line = value.line();
810+
let column = value.column();
811+
812+
// remove line, column from message
813+
let message = value.to_string();
814+
let message = if let Some(pos) = message.find("at line") {
815+
message.chars().take(pos).collect()
816+
} else {
817+
message
818+
};
819+
820+
SerdeError { message, line, column }
821+
}
822+
}
823+
824+
impl SerdeError {
825+
pub fn into_diagnostic(self, src: &BuildDescriptionSource) -> Diagnostic {
826+
let factory = SourceLocationFactory::for_source(src);
827+
let range = factory.create_range_to_end_of_line(self.line, self.column);
828+
829+
Diagnostic::invalid_build_description_file(self.message, Some(range))
830+
}
787831
}
788832

789833
#[cfg(test)]

compiler/plc_diagnostics/src/errno.rs

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ pub enum ErrNo {
9696
cfc__cyclic_connection,
9797
cfc__no_associated_connector,
9898
cfc__unnamed_control,
99+
100+
// Project description file
101+
plc_json__invalid,
99102
}
100103

101104
impl Display for ErrNo {

compiler/plc_project/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ source_code = { path = "../plc_source/", package = "plc_source" }
1212
serde = { version = "1.0", features = ["derive"] }
1313
serde_json = "1"
1414
regex = "1"
15+
jsonschema = "0.17"
1516
encoding_rs.workspace = true
1617
encoding_rs_io.workspace = true
1718
glob = "*"
19+
20+
[dev-dependencies]
21+
insta = "1.31.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"title": "Schema for plc.json",
4+
"type": "object",
5+
"properties": {
6+
"name": {
7+
"type": "string"
8+
},
9+
"version": {
10+
"type": "string"
11+
},
12+
"format_version": {
13+
"type": "string"
14+
},
15+
"files": {
16+
"type": "array",
17+
"items": {
18+
"type": "string"
19+
},
20+
"minItems": 1
21+
},
22+
"compile_type": {
23+
"type": "string"
24+
},
25+
"output": {
26+
"type": "string"
27+
},
28+
"libraries": {
29+
"type": "array",
30+
"items": {
31+
"type": "object",
32+
"properties": {
33+
"name": {
34+
"type": "string"
35+
},
36+
"path": {
37+
"type": "string"
38+
},
39+
"package": {
40+
"type": "string"
41+
},
42+
"include_path": {
43+
"type": "array",
44+
"items": {
45+
"type": "string"
46+
}
47+
},
48+
"architectures": {
49+
"type": "array",
50+
"items": {
51+
"type": "object"
52+
}
53+
}
54+
},
55+
"additionalProperties": false,
56+
"required": [
57+
"name",
58+
"path",
59+
"package",
60+
"include_path"
61+
]
62+
}
63+
},
64+
"package_commands": {
65+
"type": "array",
66+
"items": {
67+
"type": "string"
68+
}
69+
}
70+
},
71+
"additionalProperties": false,
72+
"required": [
73+
"name",
74+
"files",
75+
"compile_type"
76+
]
77+
}

0 commit comments

Comments
 (0)