Skip to main content

morphir_core/ir/v4/
annotation.rs

1//! Annotations on specifications (definitions-0020 to 0023).
2//!
3//! An annotation names a value in the public face of a type, a value or a module. The compact
4//! spelling is the string `pkg:mod#local` or `pkg:mod#local:free text`, where the separator is the
5//! first colon after the local-name hash; the structured spelling is `{ "name", "arguments" }`,
6//! whose arguments are positional value expressions or named `{ "name", "value" }` pairs.
7//! Definitions never carry annotations.
8
9use serde::ser::{SerializeMap, Serializer};
10use serde::{Deserialize, Deserializer, Serialize};
11use std::ops::Deref;
12
13use super::linked_metadata::MetadataScope;
14use super::linked_metadata_scan::StandaloneMetadata;
15use super::value::Value;
16use crate::naming::{FQName, Name};
17use crate::node_address::NodeUri;
18
19/// A specification's annotations and optional independent fact scope.
20///
21/// The old array spelling is retained when the metadata scope is empty. The
22/// 4.1.0 envelope writes `entries` beside `@context` and `facts`.
23#[derive(Debug, Clone, Default, PartialEq)]
24pub struct Annotations {
25    /// Existing named entries and their arguments.
26    pub entries: Vec<Annotation>,
27    /// Scoped authored facts on the enclosing specification.
28    pub metadata: Option<Box<MetadataScope>>,
29}
30
31impl Annotations {
32    /// Retain an authored envelope until the containing document's context is known.
33    /// Callers must validate the completed IR file before exposing it.
34    pub fn parse_unresolved(value: &serde_json::Value) -> Result<Self, String> {
35        super::serde_document::decode_annotations_value(value, "annotations")
36            .map_err(|error| error.message)
37    }
38
39    /// Make an ordinary annotation array with no linked facts.
40    pub fn new(entries: Vec<Annotation>) -> Self {
41        Self {
42            entries,
43            metadata: None,
44        }
45    }
46
47    /// Whether neither entries nor metadata were authored.
48    pub fn is_empty(&self) -> bool {
49        self.entries.is_empty() && self.metadata.is_none()
50    }
51}
52
53impl Deref for Annotations {
54    type Target = [Annotation];
55
56    fn deref(&self) -> &Self::Target {
57        &self.entries
58    }
59}
60
61impl From<Vec<Annotation>> for Annotations {
62    fn from(entries: Vec<Annotation>) -> Self {
63        Self::new(entries)
64    }
65}
66
67impl PartialEq<Vec<Annotation>> for Annotations {
68    fn eq(&self, other: &Vec<Annotation>) -> bool {
69        self.metadata.is_none() && self.entries == *other
70    }
71}
72
73impl Serialize for Annotations {
74    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
75        if let Some(metadata) = &self.metadata {
76            let mut map = serializer.serialize_map(None)?;
77            if let Some(context) = &metadata.context {
78                map.serialize_entry("@context", context)?;
79            }
80            if !self.entries.is_empty() {
81                map.serialize_entry("entries", &self.entries)?;
82            }
83            if !metadata.facts.is_empty() {
84                map.serialize_entry("facts", &metadata.facts)?;
85            }
86            map.end()
87        } else {
88            self.entries.serialize(serializer)
89        }
90    }
91}
92
93impl<'de> Deserialize<'de> for Annotations {
94    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95        let value = serde_json::Value::deserialize(deserializer)?;
96        let mut decoded = super::serde_document::decode_annotations_value(&value, "")
97            .map_err(super::serde_tagged::carry)?;
98        decoded
99            .validate_standalone()
100            .map_err(serde::de::Error::custom)?;
101        Ok(decoded)
102    }
103}
104
105/// An annotation on a type, value or module specification.
106#[derive(Debug, Clone, PartialEq)]
107pub enum Annotation {
108    /// The compact spelling: a name, and optionally the free text after it.
109    Compact { name: FQName, text: Option<String> },
110    /// The structured spelling: a name and its arguments, written only when it has some.
111    Structured {
112        name: FQName,
113        args: Vec<AnnotationArgument>,
114    },
115    /// A 4.1.0 compact entry resolved through `annotations.@context`.
116    LinkedCompact {
117        /// The authored alias or compact IRI.
118        authored_name: String,
119        /// Expanded declaration identity.
120        declaration: NodeUri,
121    },
122    /// A 4.1.0 structured entry with preserved positional and named arguments.
123    LinkedStructured {
124        /// The authored alias or compact IRI.
125        authored_name: String,
126        /// Expanded declaration identity.
127        declaration: NodeUri,
128        /// The existing argument vocabulary.
129        args: Vec<AnnotationArgument>,
130    },
131    /// An alias awaiting the enclosing document's context during whole-file decode.
132    PendingCompact { authored_name: String },
133    /// A structured alias awaiting the enclosing document's context.
134    PendingStructured {
135        authored_name: String,
136        args: Vec<AnnotationArgument>,
137    },
138}
139
140/// An argument of a structured annotation.
141#[derive(Debug, Clone, PartialEq)]
142pub enum AnnotationArgument {
143    /// A value expression written on its own.
144    Positional(Value),
145    /// A value expression written under the name it answers to.
146    Named { name: Name, value: Value },
147}
148
149impl Serialize for Annotation {
150    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
151        match self {
152            Annotation::Compact { name, text: None } => {
153                serializer.serialize_str(&name.to_canonical_string())
154            }
155            Annotation::Compact {
156                name,
157                text: Some(text),
158            } => serializer.serialize_str(&format!("{}:{text}", name.to_canonical_string())),
159            Annotation::Structured { name, args } => {
160                let mut map = serializer.serialize_map(None)?;
161                map.serialize_entry("name", &name.to_canonical_string())?;
162                if !args.is_empty() {
163                    map.serialize_entry("arguments", args)?;
164                }
165                map.end()
166            }
167            Annotation::LinkedCompact { authored_name, .. }
168            | Annotation::PendingCompact { authored_name } => {
169                serializer.serialize_str(authored_name)
170            }
171            Annotation::LinkedStructured {
172                authored_name,
173                args,
174                ..
175            }
176            | Annotation::PendingStructured {
177                authored_name,
178                args,
179            } => {
180                let mut map = serializer.serialize_map(None)?;
181                map.serialize_entry("name", authored_name)?;
182                if !args.is_empty() {
183                    map.serialize_entry("arguments", args)?;
184                }
185                map.end()
186            }
187        }
188    }
189}
190
191impl Serialize for AnnotationArgument {
192    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
193        match self {
194            AnnotationArgument::Positional(value) => value.serialize(serializer),
195            AnnotationArgument::Named { name, value } => {
196                let mut map = serializer.serialize_map(Some(2))?;
197                map.serialize_entry("name", &name.to_canonical_string())?;
198                map.serialize_entry("value", value)?;
199                map.end()
200            }
201        }
202    }
203}