1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
use crate::{
lint::{
common_error_literals::NO_ROOT_PREFIX,
helpers::{
catch_single_or_multiple_annotations_which_must_exist, fail_if_has_language_tag,
},
lint_failure, lint_success, Lint, LintResult,
},
Linter, MultiReaderRdfGraph,
};
use field33_rdftk_iri_temporary_fork::IRI as RDFTK_IRI;
use plow_ontology::constants::REGISTRY_CATEGORY;
use plow_package_management::metadata::get_root_prefix;
use std::str::FromStr;
use std::{any::Any, collections::HashSet};
const RELATED_FIELD: &str = "`registry:category`";
/// Maximum allowed categories.
const MAX_CATEGORIES: usize = 5;
/// Available categories to choose from.
pub const CATEGORY_ALLOW_LIST: [&str; 36] = [
"Benchmark",
"Design",
"Enterprise",
"Framework",
"Innovation",
"Meta Model",
"Methodology",
"Metric",
"Organization",
"People",
"Portfolio",
"Process",
"Product",
"Project Management",
"Software Architecture",
"Software Development",
"Software Implementation",
"Software Infrastructure",
"Strategy",
"Transformation",
"Upper Ontology",
"Customer Ontology",
"Scrum",
"Relation",
"Communication",
"Core",
"Graph Visualization",
"Change Tracking",
"Graph Style",
"Interoperability",
"APQC",
"Business",
"Payment",
"Purchase",
"Federal Administration",
"Connector"
];
/// Ensures that a value for `registry:category` is specified as annotation on the ontology.
#[derive(Debug, Default)]
pub struct HasRegistryCategory;
impl Lint for HasRegistryCategory {
fn as_any(&self) -> &dyn Any {
self
}
fn short_description(&self) -> &str {
"Check that the field is annotated with a value for `registry:category`"
}
/// Lints for the existence of `registry:category` and its validity.
/// Available categories are defined by plow.
/// Maximum 5 categories are allowed.
/// Categories shouldn't contain language tags.
///
/// Here is a list:
/// ```rust
/// const CATEGORY_ALLOW_LIST: [&str; 30] = [
/// "Benchmark",
/// "Design",
/// "Enterprise",
/// "Framework",
/// "Innovation",
/// "Meta Model",
/// "Methodology",
/// "Metric",
/// "Organization",
/// "People",
/// "Portfolio",
/// "Process",
/// "Product",
/// "Project Management",
/// "Software Architecture",
/// "Software Development",
/// "Software Implementation",
/// "Software Infrastructure",
/// "Strategy",
/// "Transformation",
/// "Upper Ontology",
/// "Customer Ontology",
/// "Scrum",
/// "Relation",
/// "Communication",
/// "Core",
/// "Graph Visualization",
/// "Change Tracking",
/// "Graph Style",
/// "Interoperability",
/// ];
/// ```
fn run(
&self,
Linter {
document,
graph: MultiReaderRdfGraph { inner: rdf_graph },
..
}: &Linter,
) -> LintResult {
let rdf_factory = field33_rdftk_core_temporary_fork::simple::statement::statement_factory();
if let Some(root_prefix) = get_root_prefix(document) {
let graph_ref = rdf_graph;
let graph = graph_ref.borrow();
// We explicitly pass valid data, unwrap is safe here.
#[allow(clippy::unwrap_used)]
let annotations = graph
.statements()
.filter(|statement| {
statement.subject()
== &rdf_factory
.named_subject(RDFTK_IRI::from_str(root_prefix).unwrap().into())
&& statement.predicate()
== &RDFTK_IRI::from_str(REGISTRY_CATEGORY).unwrap().into()
})
.collect::<HashSet<_>>();
if let Some(failure) =
catch_single_or_multiple_annotations_which_must_exist(&annotations, RELATED_FIELD)
{
return failure;
}
let lint_prefix = format!("The value of {RELATED_FIELD},");
if annotations.len() > MAX_CATEGORIES {
return lint_failure!(format!(
"{lint_prefix} can not contain more than {MAX_CATEGORIES} categories. Please reduce the amount of categories.",
));
}
// Checking for duplicate categories are actually unnecessary. Because they're checked on parsing level.
// But I'm keeping it here if harriet decides to allow for duplicate annotations later.
let mut checked_literals: Vec<&str> = vec![];
let lint_results = annotations
.iter()
.map(|annotation| {
annotation.object().as_literal().map_or_else(
|| lint_failure!(format!("{lint_prefix} is not a literal.")),
|literal| {
if let Some(failure) = fail_if_has_language_tag(literal, RELATED_FIELD) {
return failure;
}
let category_raw = literal.lexical_form().trim();
if checked_literals.contains(&category_raw) {
return lint_failure!(format!(
"Each {RELATED_FIELD} value can only be used only once. There could be no duplicates."
));
}
checked_literals.push(category_raw);
if CATEGORY_ALLOW_LIST.contains(&category_raw) {
return lint_success!(format!("{lint_prefix} is valid."));
}
lint_failure!(format!(
"{lint_prefix} does contain a category ({category_raw}) which is not available."
))
},
)
})
.collect::<Vec<LintResult>>();
checked_literals.clear();
for result in lint_results {
if let LintResult::Failure(messages) = result {
return lint_failure!(format!(
"Some {RELATED_FIELD} annotations are invalid. More info: {}",
messages.join(", ")
));
}
}
lint_success!(format!("All {RELATED_FIELD} annotations are valid."))
} else {
lint_failure!(NO_ROOT_PREFIX)
}
}
}