Skip to main content

morphir_core/ir/classic/
distribution.rs

1//! Classic IR Distribution types
2//!
3//! Distribution wrapper for the Classic Morphir IR format.
4
5use serde::de::{self, IgnoredAny, SeqAccess, Visitor};
6use serde::ser::{SerializeTuple, Serializer};
7use serde::{Deserialize, Deserializer, Serialize};
8use std::borrow::Cow;
9use std::fmt;
10
11use crate::format_version::{DeclaredRelease, ReleaseTriplet, deserialize_declared_release};
12
13use super::Attrs;
14use super::naming::Path;
15use super::package::{PackageDefinition, PackageSpecification, SpecsModuleEntry};
16use super::types::Type;
17
18/// Distribution of packages
19#[derive(Debug, Clone, PartialEq)]
20pub struct Distribution {
21    pub format_version: u32,
22    pub distribution: DistributionBody,
23}
24
25impl Distribution {
26    /// The `formatVersion` value this distribution writes, chosen by the
27    /// content of [`DistributionBody`]: a `Library` writes the classic `3`,
28    /// a `Specs` writes `"3.1.0"`, the version that introduced it.
29    pub fn emitted_format_version(&self) -> serde_json::Value {
30        match &self.distribution {
31            DistributionBody::Library(..) => serde_json::Value::from(3u32),
32            DistributionBody::Specs(..) => serde_json::Value::from("3.1.0"),
33        }
34    }
35}
36
37impl Serialize for Distribution {
38    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39    where
40        S: Serializer,
41    {
42        use serde::ser::SerializeStruct;
43
44        let mut state = serializer.serialize_struct("Distribution", 2)?;
45        state.serialize_field("formatVersion", &self.emitted_format_version())?;
46        state.serialize_field("distribution", &self.distribution)?;
47        state.end()
48    }
49}
50
51/// The release that introduced the v3 `Specs` distribution.
52pub const SPECS_FIRST_RELEASE: ReleaseTriplet = ReleaseTriplet::new(3, 1, 0);
53
54const SPECS_BEFORE_3_1: &str = "a v3 Specs distribution needs formatVersion 3.1.0 or later";
55
56/// Refuses a v3 `Specs` distribution whose declared release is older than
57/// [`SPECS_FIRST_RELEASE`]: integer `3` and `"3.0.x"` cannot hold a `Specs`.
58///
59/// Every decoder of a v3 `Specs` calls this once the body kind is known, so each one refuses
60/// with the same message. [`is_specs_before_3_1`] recognizes that message.
61pub fn check_specs_release(declared: &DeclaredRelease) -> Result<(), String> {
62    if declared.release < SPECS_FIRST_RELEASE {
63        return Err(format!("{SPECS_BEFORE_3_1}, found {}", declared.declared));
64    }
65    Ok(())
66}
67
68/// Whether `message` starts with the refusal [`check_specs_release`] gives.
69pub fn is_specs_before_3_1(message: &str) -> bool {
70    message.starts_with(SPECS_BEFORE_3_1)
71}
72
73impl<'de> Deserialize<'de> for Distribution {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        #[derive(Deserialize)]
79        #[serde(rename_all = "camelCase")]
80        struct DistributionFields {
81            #[serde(deserialize_with = "deserialize_declared_release")]
82            format_version: DeclaredRelease,
83            distribution: DistributionBody,
84        }
85        let fields = DistributionFields::deserialize(deserializer)?;
86        if let DistributionBody::Specs(..) = fields.distribution {
87            check_specs_release(&fields.format_version).map_err(de::Error::custom)?;
88        }
89        Ok(Self {
90            format_version: fields.format_version.release.major(),
91            distribution: fields.distribution,
92        })
93    }
94}
95
96/// Distribution body - serialized as ["Library", packagePath, dependencies, package]
97#[derive(Debug, Clone, PartialEq)]
98pub enum DistributionBody {
99    Library(
100        Path,
101        Vec<(Path, PackageSpecification<Attrs>)>,
102        PackageDefinition<Attrs, Type<Attrs>>,
103    ),
104    /// A package's public interface without definitions, introduced in
105    /// format version 3.1.0.
106    Specs(
107        Path,
108        Vec<(Path, PackageSpecification<Attrs>)>,
109        PackageSpecification<Attrs>,
110    ),
111}
112
113impl Serialize for DistributionBody {
114    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115    where
116        S: Serializer,
117    {
118        match self {
119            DistributionBody::Library(path, deps, package) => {
120                let mut tuple = serializer.serialize_tuple(4)?;
121                tuple.serialize_element("Library")?;
122                tuple.serialize_element(path)?;
123                tuple.serialize_element(deps)?;
124                tuple.serialize_element(package)?;
125                tuple.end()
126            }
127            DistributionBody::Specs(path, deps, spec) => {
128                let mut tuple = serializer.serialize_tuple(4)?;
129                tuple.serialize_element("Specs")?;
130                tuple.serialize_element(path)?;
131                tuple.serialize_element(deps)?;
132                tuple.serialize_element(spec)?;
133                tuple.end()
134            }
135        }
136    }
137}
138
139impl<'de> Deserialize<'de> for DistributionBody {
140    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
141    where
142        D: Deserializer<'de>,
143    {
144        struct DistributionBodyVisitor;
145
146        impl<'de> Visitor<'de> for DistributionBodyVisitor {
147            type Value = DistributionBody;
148
149            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
150                formatter.write_str(
151                    r#"a DistributionBody array ["Library", path, deps, package] or ["Specs", path, deps, spec]"#,
152                )
153            }
154
155            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
156            where
157                V: SeqAccess<'de>,
158            {
159                let tag: Cow<'de, str> = seq
160                    .next_element()?
161                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
162
163                match tag.as_ref() {
164                    "Library" | "library" => {
165                        let path = seq
166                            .next_element::<Path>()?
167                            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
168                        let deps = seq
169                            .next_element::<Vec<(Path, PackageSpecification<Attrs>)>>()?
170                            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
171                        let package = seq
172                            .next_element::<PackageDefinition<Attrs, Type<Attrs>>>()?
173                            .ok_or_else(|| de::Error::invalid_length(3, &self))?;
174
175                        if let Some(IgnoredAny) = seq.next_element()? {
176                            return Err(de::Error::custom(
177                                "Expected end of DistributionBody array",
178                            ));
179                        }
180
181                        Ok(DistributionBody::Library(path, deps, package))
182                    }
183                    "Specs" | "specs" => {
184                        let path = seq
185                            .next_element::<Path>()?
186                            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
187                        let deps = seq
188                            .next_element::<Vec<(Path, PackageSpecification<Attrs>)>>()?
189                            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
190                        let SpecsPackage { modules } = seq
191                            .next_element::<SpecsPackage>()?
192                            .ok_or_else(|| de::Error::invalid_length(3, &self))?;
193                        let spec = PackageSpecification {
194                            modules: modules.into_iter().map(Into::into).collect(),
195                        };
196
197                        if let Some(IgnoredAny) = seq.next_element()? {
198                            return Err(de::Error::custom(
199                                "Expected end of DistributionBody array",
200                            ));
201                        }
202
203                        Ok(DistributionBody::Specs(path, deps, spec))
204                    }
205                    _ => Err(de::Error::unknown_variant(
206                        tag.as_ref(),
207                        &["Library", "Specs"],
208                    )),
209                }
210            }
211        }
212
213        deserializer.deserialize_seq(DistributionBodyVisitor)
214    }
215}
216
217/// A Specs distribution's own package: its modules are read strictly, so a module definition
218/// is refused instead of read as an empty specification.
219#[derive(Deserialize)]
220struct SpecsPackage {
221    modules: Vec<SpecsModuleEntry<Attrs>>,
222}
223
224/// Tag for backward compatibility - no longer needed with custom serde
225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
226pub enum LibraryTag {
227    #[serde(alias = "library")]
228    Library,
229}