1use indexmap::IndexMap;
6use serde::{Deserialize, Serialize};
7
8use super::access::{Access, AccessControlled};
9use super::module::{Documented, ModuleDefinition, ModuleSpecification};
10use super::types::{
11 ConstructorArgSpec, ConstructorSpecification, TypeDefinition, TypeSpecification,
12};
13use super::value::{ValueDefinition, ValueSpecification};
14
15#[derive(Debug, Clone, PartialEq, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct PackageSpecification {
19 pub modules: IndexMap<String, ModuleSpecification>,
20}
21
22impl<'de> Deserialize<'de> for PackageSpecification {
23 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24 where
25 D: serde::Deserializer<'de>,
26 {
27 super::serde_document::deserialize_standalone_with(
28 deserializer,
29 super::serde_document::decode_package_specification,
30 )
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize)]
36#[serde(rename_all = "camelCase")]
37pub struct PackageDefinition {
38 pub modules: IndexMap<String, AccessControlled<ModuleDefinition>>,
39}
40
41impl PackageDefinition {
42 pub fn to_specification(&self) -> PackageSpecification {
53 PackageSpecification {
54 modules: self
55 .modules
56 .iter()
57 .filter(|(_, controlled)| controlled.access == Access::Public)
58 .map(|(name, controlled)| {
59 (name.clone(), module_to_specification(&controlled.value))
60 })
61 .collect(),
62 }
63 }
64}
65
66fn module_to_specification(definition: &ModuleDefinition) -> ModuleSpecification {
67 ModuleSpecification {
68 annotations: Vec::new().into(),
70 types: definition
71 .types
72 .iter()
73 .filter(|(_, controlled)| controlled.access == Access::Public)
74 .map(|(name, controlled)| {
75 let Documented { doc, value } = &controlled.value;
76 (
77 name.clone(),
78 Documented::new(doc.clone(), type_to_specification(value)),
79 )
80 })
81 .collect(),
82 values: definition
83 .values
84 .iter()
85 .filter(|(_, controlled)| controlled.access == Access::Public)
86 .filter_map(|(name, controlled)| {
87 let Documented { doc, value } = &controlled.value;
88 let specification = value_to_specification(value)?;
89 Some((name.clone(), Documented::new(doc.clone(), specification)))
90 })
91 .collect(),
92 doc: definition.doc.clone(),
93 }
94}
95
96fn value_to_specification(definition: &ValueDefinition) -> Option<ValueSpecification> {
98 Some(ValueSpecification {
99 annotations: Vec::new().into(),
100 inputs: definition.input_types.clone(),
101 output: definition.output_type.clone()?,
102 })
103}
104
105fn type_to_specification(definition: &TypeDefinition) -> TypeSpecification {
106 match definition {
107 TypeDefinition::TypeAliasDefinition {
108 type_params,
109 type_expr,
110 } => TypeSpecification::TypeAliasSpecification {
111 annotations: Vec::new().into(),
112 type_params: type_params.clone(),
113 type_expr: type_expr.clone(),
114 },
115 TypeDefinition::CustomTypeDefinition {
116 type_params,
117 constructors,
118 } => match constructors.access {
119 Access::Public => TypeSpecification::CustomTypeSpecification {
120 annotations: Vec::new().into(),
121 type_params: type_params.clone(),
122 constructors: constructors
123 .value
124 .iter()
125 .map(|constructor| ConstructorSpecification {
126 name: constructor.name.clone(),
127 args: constructor
128 .args
129 .iter()
130 .map(|argument| ConstructorArgSpec {
131 name: argument.name.clone(),
132 arg_type: argument.arg_type.clone(),
133 })
134 .collect(),
135 })
136 .collect(),
137 },
138 Access::Private => TypeSpecification::OpaqueTypeSpecification {
139 annotations: Vec::new().into(),
140 type_params: type_params.clone(),
141 },
142 },
143 TypeDefinition::IncompleteTypeDefinition { type_params, .. } => {
145 TypeSpecification::OpaqueTypeSpecification {
146 annotations: Vec::new().into(),
147 type_params: type_params.clone(),
148 }
149 }
150 }
151}
152
153impl<'de> Deserialize<'de> for PackageDefinition {
154 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155 where
156 D: serde::Deserializer<'de>,
157 {
158 super::serde_document::deserialize_standalone_with(
159 deserializer,
160 super::serde_document::decode_package_definition,
161 )
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::super::attributes::ValueAttributes;
168 use super::*;
169 use crate::ir::v4::module::Documentation;
170 use crate::ir::v4::types::{ConstructorArg, ConstructorDefinition, Incompleteness};
171 use crate::ir::v4::value::{Value, ValueBody};
172 use crate::naming::Name;
173
174 fn attrs() -> super::super::attributes::TypeAttributes {
175 super::super::attributes::TypeAttributes::default()
176 }
177
178 fn unit_type() -> super::super::types::Type {
179 super::super::types::Type::unit(attrs())
180 }
181
182 fn public<T>(value: T) -> AccessControlled<T> {
183 AccessControlled {
184 access: Access::Public,
185 value,
186 }
187 }
188
189 fn private<T>(value: T) -> AccessControlled<T> {
190 AccessControlled {
191 access: Access::Private,
192 value,
193 }
194 }
195
196 fn undocumented<T>(value: T) -> Documented<T> {
197 Documented::new(None, value)
198 }
199
200 fn documented<T>(doc: &str, value: T) -> Documented<T> {
201 Documented::new(Some(Documentation::from(doc)), value)
202 }
203
204 fn value_definition(output_type: Option<super::super::types::Type>) -> ValueDefinition {
205 ValueDefinition {
206 input_types: IndexMap::new(),
207 output_type,
208 body: ValueBody::Expression(Value::Unit(ValueAttributes::default())),
209 }
210 }
211
212 fn sample_package() -> PackageDefinition {
218 let mut public_module_types = IndexMap::new();
219 public_module_types.insert(
220 "PrivType".to_string(),
221 private(undocumented(TypeDefinition::TypeAliasDefinition {
222 type_params: vec![],
223 type_expr: unit_type(),
224 })),
225 );
226 public_module_types.insert(
227 "OpaqueFromPrivateConstructors".to_string(),
228 public(undocumented(TypeDefinition::CustomTypeDefinition {
229 type_params: vec![],
230 constructors: private(vec![ConstructorDefinition {
231 name: Name::from("hidden"),
232 args: vec![ConstructorArg {
233 name: Name::from("value"),
234 arg_type: unit_type(),
235 }],
236 }]),
237 })),
238 );
239 public_module_types.insert(
240 "Incomplete".to_string(),
241 public(undocumented(TypeDefinition::IncompleteTypeDefinition {
242 type_params: vec![],
243 incompleteness: Incompleteness::Draft,
244 partial_type_expr: None,
245 })),
246 );
247 public_module_types.insert(
248 "PublicAlias".to_string(),
249 public(documented(
250 "An alias for unit.",
251 TypeDefinition::TypeAliasDefinition {
252 type_params: vec![],
253 type_expr: unit_type(),
254 },
255 )),
256 );
257
258 let mut public_module_values = IndexMap::new();
259 public_module_values.insert(
260 "privValue".to_string(),
261 private(undocumented(value_definition(Some(unit_type())))),
262 );
263 public_module_values.insert(
264 "noOutputYet".to_string(),
265 public(undocumented(value_definition(None))),
266 );
267 public_module_values.insert(
268 "publicValue".to_string(),
269 public(undocumented(value_definition(Some(unit_type())))),
270 );
271
272 let mut modules = IndexMap::new();
273 modules.insert(
274 "PrivateModule".to_string(),
275 private(ModuleDefinition {
276 types: IndexMap::new(),
277 values: IndexMap::new(),
278 doc: None,
279 }),
280 );
281 modules.insert(
282 "PublicModule".to_string(),
283 public(ModuleDefinition {
284 types: public_module_types,
285 values: public_module_values,
286 doc: None,
287 }),
288 );
289
290 PackageDefinition { modules }
291 }
292
293 #[test]
294 fn to_specification_pins_every_filtering_and_translation_rule() {
295 let specification = sample_package().to_specification();
296
297 assert_eq!(specification.modules.len(), 1);
299 let module = specification.modules.get("PublicModule").unwrap();
300
301 assert_eq!(
304 module.types.keys().collect::<Vec<_>>(),
305 vec!["OpaqueFromPrivateConstructors", "Incomplete", "PublicAlias"]
306 );
307 assert_eq!(
308 module.values.keys().collect::<Vec<_>>(),
309 vec!["publicValue"]
310 );
311
312 match &module
315 .types
316 .get("OpaqueFromPrivateConstructors")
317 .unwrap()
318 .value
319 {
320 TypeSpecification::OpaqueTypeSpecification { annotations, .. } => {
321 assert!(annotations.is_empty());
322 }
323 other => panic!("expected an opaque specification, got {other:?}"),
324 }
325
326 match &module.types.get("Incomplete").unwrap().value {
328 TypeSpecification::OpaqueTypeSpecification { .. } => {}
329 other => panic!("expected an opaque specification, got {other:?}"),
330 }
331
332 let alias = module.types.get("PublicAlias").unwrap();
334 assert_eq!(
335 alias.doc.as_ref().map(Documentation::text),
336 Some("An alias for unit.")
337 );
338 match &alias.value {
339 TypeSpecification::TypeAliasSpecification { annotations, .. } => {
340 assert!(annotations.is_empty());
341 }
342 other => panic!("expected an alias specification, got {other:?}"),
343 }
344
345 assert!(!module.values.contains_key("noOutputYet"));
347 assert!(!module.values.contains_key("privValue"));
348
349 assert!(module.annotations.is_empty());
351 assert_eq!(
352 module.values.get("publicValue").unwrap().value.annotations,
353 Vec::new()
354 );
355 }
356}