sdml_core/model/
annotations.rs

1/*!
2Provide the Rust types that implement *annotation*-related components of the SDML Grammar.
3*/
4use crate::load::ModuleLoader;
5use crate::model::values::{LanguageString, LanguageTag};
6use crate::model::{
7    check::Validate,
8    constraints::Constraint,
9    identifiers::{Identifier, IdentifierReference, QualifiedIdentifier},
10    modules::Module,
11    values::Value,
12    HasNameReference, Span,
13};
14use crate::model::{HasName, References};
15use crate::stdlib;
16use crate::store::ModuleStore;
17use std::{collections::HashSet, fmt::Debug};
18use tracing::trace;
19use url::Url;
20
21#[cfg(feature = "serde")]
22use serde::{Deserialize, Serialize};
23
24// ------------------------------------------------------------------------------------------------
25// Public Types ❱ Traits
26// ------------------------------------------------------------------------------------------------
27
28pub trait HasAnnotations {
29    fn has_annotations(&self) -> bool;
30
31    fn annotations_len(&self) -> usize;
32
33    fn annotations(&self) -> impl Iterator<Item = &Annotation>;
34
35    fn annotations_mut(&mut self) -> impl Iterator<Item = &mut Annotation>;
36
37    fn add_to_annotations<I>(&mut self, value: I)
38    where
39        I: Into<Annotation>;
40
41    fn extend_annotations<I>(&mut self, extension: I)
42    where
43        I: IntoIterator<Item = Annotation>;
44
45    fn has_annotation_properties(&self) -> bool {
46        self.annotations().any(|a| a.is_annotation_property())
47    }
48
49    fn annotation_properties(&self) -> impl Iterator<Item = &AnnotationProperty> {
50        self.annotations()
51            .filter_map(|a| a.as_annotation_property())
52    }
53
54    fn has_rdf_type(&self, type_id: &IdentifierReference) -> bool {
55        self.rdf_types().any(|id| id == type_id)
56    }
57
58    fn rdf_types(&self) -> impl Iterator<Item = &IdentifierReference> {
59        self.annotation_properties()
60            .filter(|ann| ann.name_reference() == "rdf:type")
61            .filter_map(|ann| ann.value().as_reference())
62    }
63
64    fn preferred_label(&self) -> impl Iterator<Item = &LanguageString> {
65        self.annotation_properties()
66            .filter(|ann| ann.name_reference() == "skos:prefLabel")
67            .filter_map(|ann| ann.value().as_string())
68    }
69
70    fn alternate_labels(&self) -> impl Iterator<Item = &LanguageString> {
71        self.annotation_properties()
72            .filter(|ann| ann.name_reference() == "skos:altLabel")
73            .filter_map(|ann| ann.value().as_string())
74    }
75
76    fn descriptions(&self) -> impl Iterator<Item = &LanguageString> {
77        self.annotation_properties()
78            .filter(|ann| ann.name_reference() == "dc:description")
79            .filter_map(|ann| ann.value().as_string())
80    }
81
82    fn definitions(&self) -> impl Iterator<Item = &LanguageString> {
83        self.annotation_properties()
84            .filter(|ann| ann.name_reference() == "skos:definition")
85            .filter_map(|ann| ann.value().as_string())
86    }
87
88    fn has_constraints(&self) -> bool {
89        self.annotations().any(|a| a.is_constraint())
90    }
91
92    fn annotation_constraints(&self) -> impl Iterator<Item = &Constraint> {
93        self.annotations().filter_map(|a| a.as_constraint())
94    }
95}
96
97pub trait AnnotationBuilder {
98    fn with_predicate<I, V>(self, predicate: I, value: V) -> Self
99    where
100        Self: Sized,
101        I: Into<IdentifierReference>,
102        V: Into<Value>;
103
104    fn with_type<I>(self, name: I) -> Self
105    where
106        Self: Sized,
107        I: Into<IdentifierReference>,
108    {
109        self.with_predicate(
110            QualifiedIdentifier::new(
111                Identifier::new_unchecked(stdlib::rdf::MODULE_NAME),
112                Identifier::new_unchecked(stdlib::rdf::TYPE),
113            ),
114            Value::from(name.into()),
115        )
116    }
117
118    fn with_super_class<I>(self, name: I) -> Self
119    where
120        Self: Sized,
121        I: Into<IdentifierReference>,
122    {
123        self.with_predicate(
124            QualifiedIdentifier::new(
125                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
126                Identifier::new_unchecked(stdlib::rdfs::SUB_CLASS_OF),
127            ),
128            Value::from(name.into()),
129        )
130    }
131
132    fn with_equivalent_class<I>(self, name: I) -> Self
133    where
134        Self: Sized,
135        I: Into<IdentifierReference>,
136    {
137        self.with_predicate(
138            QualifiedIdentifier::new(
139                Identifier::new_unchecked(stdlib::owl::MODULE_NAME),
140                Identifier::new_unchecked(stdlib::owl::EQUIVALENT_CLASS),
141            ),
142            Value::from(name.into()),
143        )
144    }
145
146    fn with_super_property<I>(self, name: I) -> Self
147    where
148        Self: Sized,
149        I: Into<IdentifierReference>,
150    {
151        self.with_predicate(
152            QualifiedIdentifier::new(
153                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
154                Identifier::new_unchecked(stdlib::rdfs::SUB_PROPERTY_OF),
155            ),
156            Value::from(name.into()),
157        )
158    }
159
160    fn with_domain<I>(self, name: I) -> Self
161    where
162        Self: Sized,
163        I: Into<IdentifierReference>,
164    {
165        self.with_predicate(
166            QualifiedIdentifier::new(
167                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
168                Identifier::new_unchecked(stdlib::rdfs::DOMAIN),
169            ),
170            Value::from(name.into()),
171        )
172    }
173
174    fn with_comment<S>(self, comment: S) -> Self
175    where
176        Self: Sized,
177        S: Into<LanguageString>,
178    {
179        self.with_predicate(
180            QualifiedIdentifier::new(
181                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
182                Identifier::new_unchecked(stdlib::rdfs::COMMENT),
183            ),
184            comment.into(),
185        )
186    }
187
188    fn with_label<S>(self, label: S) -> Self
189    where
190        Self: Sized,
191        S: Into<LanguageString>,
192    {
193        self.with_predicate(
194            QualifiedIdentifier::new(
195                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
196                Identifier::new_unchecked(stdlib::rdfs::LABEL),
197            ),
198            Value::from(label.into()),
199        )
200    }
201
202    fn with_see_also_str(self, resource: &str) -> Self
203    where
204        Self: Sized,
205    {
206        self.with_predicate(
207            QualifiedIdentifier::new(
208                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
209                Identifier::new_unchecked(stdlib::rdfs::SEE_ALSO),
210            ),
211            Value::from(Url::parse(resource).unwrap()),
212        )
213    }
214
215    fn with_see_also(self, resource: Url) -> Self
216    where
217        Self: Sized,
218    {
219        self.with_predicate(
220            QualifiedIdentifier::new(
221                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
222                Identifier::new_unchecked(stdlib::rdfs::SEE_ALSO),
223            ),
224            Value::from(resource),
225        )
226    }
227
228    fn with_see_also_ref<I>(self, resource: I) -> Self
229    where
230        Self: Sized,
231        I: Into<IdentifierReference>,
232    {
233        self.with_predicate(
234            QualifiedIdentifier::new(
235                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
236                Identifier::new_unchecked(stdlib::rdfs::SEE_ALSO),
237            ),
238            Value::from(resource.into()),
239        )
240    }
241
242    fn with_is_defined_by(self, resource: Url) -> Self
243    where
244        Self: Sized,
245    {
246        self.with_predicate(
247            QualifiedIdentifier::new(
248                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
249                Identifier::new_unchecked(stdlib::rdfs::IS_DEFINED_BY),
250            ),
251            Value::from(resource),
252        )
253    }
254
255    fn with_is_defined_by_str(self, resource: &str) -> Self
256    where
257        Self: Sized,
258    {
259        self.with_predicate(
260            QualifiedIdentifier::new(
261                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
262                Identifier::new_unchecked(stdlib::rdfs::IS_DEFINED_BY),
263            ),
264            Value::from(Url::parse(resource).unwrap()),
265        )
266    }
267
268    fn with_is_defined_by_ref<I>(self, resource: I) -> Self
269    where
270        Self: Sized,
271        I: Into<IdentifierReference>,
272    {
273        self.with_predicate(
274            QualifiedIdentifier::new(
275                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
276                Identifier::new_unchecked(stdlib::rdfs::IS_DEFINED_BY),
277            ),
278            Value::from(resource.into()),
279        )
280    }
281
282    fn with_range<I>(self, name: I) -> Self
283    where
284        Self: Sized,
285        I: Into<IdentifierReference>,
286    {
287        self.with_predicate(
288            QualifiedIdentifier::new(
289                Identifier::new_unchecked(stdlib::rdfs::MODULE_NAME),
290                Identifier::new_unchecked(stdlib::rdfs::RANGE),
291            ),
292            Value::from(name.into()),
293        )
294    }
295}
296
297impl<A: HasAnnotations> AnnotationBuilder for A {
298    fn with_predicate<I, V>(self, predicate: I, value: V) -> Self
299    where
300        Self: Sized,
301        I: Into<IdentifierReference>,
302        V: Into<Value>,
303    {
304        let mut self_mut = self;
305        self_mut.add_to_annotations(AnnotationProperty::new(predicate.into(), value.into()));
306        self_mut
307    }
308}
309
310// ------------------------------------------------------------------------------------------------
311// Public Types ❱ Concrete
312// ------------------------------------------------------------------------------------------------
313
314/// Corresponds to the grammar rule `annotation`.
315#[derive(Clone, Debug)]
316#[allow(clippy::large_enum_variant)] // TODO: why is this reported as an issue?
317#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
318pub enum Annotation {
319    Property(AnnotationProperty),
320    Constraint(Constraint),
321}
322
323/// Corresponds to the grammar rule `annotation_property`.
324#[derive(Clone, Debug)]
325#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
326pub struct AnnotationProperty {
327    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
328    span: Option<Box<Span>>,
329    name_reference: IdentifierReference,
330    value: Value,
331}
332
333/// Corresponds to the grammar rule `annotation_only_body`.
334#[derive(Clone, Debug, Default)]
335#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
336pub struct AnnotationOnlyBody {
337    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
338    span: Option<Box<Span>>,
339    annotations: Vec<Annotation>, // assert!(!annotations.is_empty());
340}
341
342// ------------------------------------------------------------------------------------------------
343// Public Functions
344// ------------------------------------------------------------------------------------------------
345
346pub fn preferred_type_label<T: HasAnnotations + HasName>(
347    element: T,
348    _for_language: Option<LanguageTag>,
349) -> String {
350    let labels: Vec<&LanguageString> = element.preferred_label().collect();
351
352    // TODO: match by language
353
354    if labels.is_empty() {
355        element.name().to_type_label()
356    } else {
357        element.name().to_string()
358    }
359}
360
361// ------------------------------------------------------------------------------------------------
362// Implementations ❱ Annotations
363// ------------------------------------------------------------------------------------------------
364
365impl_has_source_span_for!(Annotation => variants Property, Constraint);
366
367impl From<AnnotationProperty> for Annotation {
368    fn from(value: AnnotationProperty) -> Self {
369        Self::Property(value)
370    }
371}
372
373impl From<Constraint> for Annotation {
374    fn from(value: Constraint) -> Self {
375        Self::Constraint(value)
376    }
377}
378
379impl References for Annotation {}
380
381impl Validate for Annotation {
382    fn validate(
383        &self,
384        top: &Module,
385        cache: &impl ModuleStore,
386        loader: &impl ModuleLoader,
387        check_constraints: bool,
388    ) {
389        trace!("Annotation::is_valid");
390        match (self, check_constraints) {
391            (Annotation::Property(v), _) => v.validate(top, cache, loader, check_constraints),
392            (Annotation::Constraint(v), true) => v.validate(top, cache, loader, check_constraints),
393            _ => {}
394        };
395    }
396}
397
398impl Annotation {
399    // --------------------------------------------------------------------------------------------
400    // Annotation :: Variants
401    // --------------------------------------------------------------------------------------------
402
403    is_as_variant!(Property (AnnotationProperty) => is_annotation_property, as_annotation_property);
404
405    is_as_variant!(Constraint (Constraint) => is_constraint, as_constraint);
406}
407
408// ------------------------------------------------------------------------------------------------
409// Implementations ❱ Annotations ❱ Annotation Properties
410// ------------------------------------------------------------------------------------------------
411
412impl_has_source_span_for!(AnnotationProperty);
413
414impl_has_name_reference_for!(AnnotationProperty);
415
416impl Validate for AnnotationProperty {
417    fn validate(&self, _top: &Module, _cache: &impl ModuleStore, _: &impl ModuleLoader, _: bool) {
418        trace!("AnnotationProperty::is_valid -- missing type/value conformance");
419        // TODO: check value/type conformance
420        // 1. Lookup property
421        // 2. Get property range
422        // 3. check::validate_value(self.value, range, ...)
423    }
424}
425
426impl AnnotationProperty {
427    // --------------------------------------------------------------------------------------------
428    // AnnotationProperty :: Constructors
429    // --------------------------------------------------------------------------------------------
430
431    pub fn new(name_reference: IdentifierReference, value: Value) -> Self {
432        Self {
433            span: None,
434            name_reference,
435            value,
436        }
437    }
438
439    // --------------------------------------------------------------------------------------------
440    // AnnotationProperty :: Fields
441    // --------------------------------------------------------------------------------------------
442
443    get_and_set!(pub value, set_value => Value);
444
445    // --------------------------------------------------------------------------------------------
446    // AnnotationProperty :: Helpers
447    // --------------------------------------------------------------------------------------------
448
449    #[inline(always)]
450    pub fn is_stdlib_property(&self) -> bool {
451        if let IdentifierReference::QualifiedIdentifier(name) = self.name_reference() {
452            stdlib::is_library_module(name.module())
453        } else {
454            false
455        }
456    }
457
458    #[inline(always)]
459    pub fn is_datatype_facet(&self) -> bool {
460        if let IdentifierReference::QualifiedIdentifier(name) = self.name_reference() {
461            name.module().as_ref() == stdlib::xsd::MODULE_NAME
462                && stdlib::xsd::is_constraining_facet(name.member())
463        } else {
464            false
465        }
466    }
467}
468
469// ------------------------------------------------------------------------------------------------
470
471impl_has_source_span_for!(AnnotationOnlyBody);
472
473impl_has_annotations_for!(AnnotationOnlyBody);
474
475impl From<Vec<Annotation>> for AnnotationOnlyBody {
476    fn from(annotations: Vec<Annotation>) -> Self {
477        Self {
478            span: Default::default(),
479            annotations,
480        }
481    }
482}
483
484impl From<AnnotationOnlyBody> for Vec<Annotation> {
485    fn from(value: AnnotationOnlyBody) -> Self {
486        value.annotations
487    }
488}
489
490impl References for AnnotationOnlyBody {
491    fn referenced_annotations<'a>(&'a self, names: &mut HashSet<&'a IdentifierReference>) {
492        names.extend(self.annotation_properties().map(|ann| ann.name_reference()));
493    }
494}
495
496impl Validate for AnnotationOnlyBody {
497    fn validate(
498        &self,
499        top: &Module,
500        cache: &impl ModuleStore,
501        loader: &impl ModuleLoader,
502        check_constraints: bool,
503    ) {
504        trace!("AnnotationOnlyBody::is_valid");
505        self.annotations()
506            .for_each(|ann| ann.validate(top, cache, loader, check_constraints));
507    }
508}