morphir_core/ir/v4/
module.rs1use indexmap::IndexMap;
6use serde::{Deserialize, Serialize};
7
8use super::access::AccessControlled;
9use super::types::{TypeDefinition, TypeSpecification};
10use super::value::{ValueDefinition, ValueSpecification};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Documentation(String);
18
19impl Documentation {
20 pub fn new(text: impl Into<String>) -> Self {
22 Self(text.into().replace("\r\n", "\n"))
23 }
24
25 pub fn text(&self) -> &str {
27 &self.0
28 }
29
30 pub fn lines(&self) -> std::str::Lines<'_> {
32 self.0.lines()
33 }
34}
35
36impl From<String> for Documentation {
37 fn from(value: String) -> Self {
38 Self::new(value)
39 }
40}
41
42impl From<&str> for Documentation {
43 fn from(value: &str) -> Self {
44 Self::new(value)
45 }
46}
47
48impl Serialize for Documentation {
49 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50 where
51 S: serde::Serializer,
52 {
53 serializer.serialize_str(&self.0)
54 }
55}
56
57impl<'de> Deserialize<'de> for Documentation {
58 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59 where
60 D: serde::Deserializer<'de>,
61 {
62 String::deserialize(deserializer).map(Self::new)
63 }
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub struct Documented<T> {
69 pub doc: Option<Documentation>,
70 pub value: T,
71}
72
73impl<T> Documented<T> {
74 pub fn new(doc: Option<Documentation>, value: T) -> Self {
75 Self { doc, value }
76 }
77}
78
79impl<T: Serialize> Serialize for Documented<T> {
80 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: serde::Serializer,
83 {
84 let Some(doc) = &self.doc else {
85 return self.value.serialize(serializer);
86 };
87
88 let written = serde_json::to_value(&self.value).map_err(serde::ser::Error::custom)?;
91 let doc = serde_json::to_value(doc).map_err(serde::ser::Error::custom)?;
92 match written {
93 serde_json::Value::Object(members) => {
94 let mut flattened = serde_json::Map::with_capacity(members.len() + 1);
95 flattened.insert("doc".to_owned(), doc);
96 flattened.extend(members);
97 serde_json::Value::Object(flattened).serialize(serializer)
98 }
99 other => Err(serde::ser::Error::custom(format!(
104 "documentation is flattened beside the node it documents, so a documented node \
105 must serialize as an object; this one wrote {other}"
106 ))),
107 }
108 }
109}
110
111impl<'de, T> Deserialize<'de> for Documented<T>
112where
113 T: for<'value> Deserialize<'value>,
114{
115 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116 where
117 D: serde::Deserializer<'de>,
118 {
119 use super::serde_document::{carried, recover};
120
121 let value = serde_json::Value::deserialize(deserializer)?;
122 super::serde_document::decode_documented(&value, "", |payload, cursor| {
123 serde_json::from_value::<T>(payload.clone()).map_err(|error| recover(&error, cursor))
124 })
125 .map_err(carried)
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize)]
131#[serde(rename_all = "camelCase")]
132pub struct ModuleSpecification {
133 #[serde(skip_serializing_if = "super::annotation::Annotations::is_empty")]
136 pub annotations: super::annotation::Annotations,
137 pub types: IndexMap<String, Documented<TypeSpecification>>,
138 pub values: IndexMap<String, Documented<ValueSpecification>>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub doc: Option<Documentation>,
141}
142
143impl<'de> Deserialize<'de> for ModuleSpecification {
144 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
145 where
146 D: serde::Deserializer<'de>,
147 {
148 super::serde_document::deserialize_standalone_with(
149 deserializer,
150 super::serde_document::decode_module_specification,
151 )
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ModuleDefinition {
159 pub types: IndexMap<String, AccessControlled<Documented<TypeDefinition>>>,
160 pub values: IndexMap<String, AccessControlled<Documented<ValueDefinition>>>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub doc: Option<Documentation>,
163}
164
165impl<'de> Deserialize<'de> for ModuleDefinition {
166 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
167 where
168 D: serde::Deserializer<'de>,
169 {
170 super::serde_document::deserialize_standalone_with(
171 deserializer,
172 super::serde_document::decode_module_definition,
173 )
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn documentation_is_flattened_beside_the_node_it_documents() {
183 let documented = Documented::new(
184 Some(Documentation::from("What this names.".to_owned())),
185 serde_json::json!({ "TypeAliasDefinition": { "typeParams": [] } }),
186 );
187
188 assert_eq!(
189 serde_json::to_value(&documented).unwrap(),
190 serde_json::json!({
191 "doc": "What this names.",
192 "TypeAliasDefinition": { "typeParams": [] }
193 })
194 );
195 }
196
197 #[test]
198 fn a_node_that_is_not_an_object_has_nowhere_to_flatten_documentation_into() {
199 let documented = Documented::new(
203 Some(Documentation::from("What this names.".to_owned())),
204 serde_json::json!("morphir/SDK:string#string"),
205 );
206
207 let error = serde_json::to_value(&documented).unwrap_err().to_string();
208 assert!(
209 error.contains("must serialize as an object"),
210 "unexpected error: {error}"
211 );
212 assert!(!error.contains("\"value\""), "unexpected error: {error}");
213 }
214
215 #[test]
216 fn an_undocumented_node_is_written_as_itself() {
217 let documented = Documented::new(None, serde_json::json!({ "Unit": {} }));
218
219 assert_eq!(
220 serde_json::to_value(&documented).unwrap(),
221 serde_json::json!({ "Unit": {} })
222 );
223 }
224}