morphir_projection/normalize/
mod.rs1mod v3;
2mod v4;
3
4use morphir_core::format_version::{Compatibility, ReleaseTriplet, SupportTable};
5use serde_json::Value;
6use thiserror::Error;
7
8use crate::lower_camel;
9use crate::model::ProjectionPackage;
10
11#[derive(Debug, Error)]
13pub enum NormalizeError {
14 #[error("Morphir IR root is missing formatVersion")]
16 MissingFormatVersion,
17 #[error("formatVersion has invalid scalar type: {value}")]
19 InvalidFormatVersionType {
20 value: String,
22 },
23 #[error("formatVersion has invalid syntax: {value}")]
25 InvalidFormatVersionSyntax {
26 value: String,
28 },
29 #[error("formatVersion component is outside the unsigned 32-bit range: {value}")]
31 FormatVersionOutOfRange {
32 value: String,
34 },
35 #[error("unsupported Morphir IR format major: {major}")]
37 UnsupportedFormatVersionMajor {
38 major: u32,
40 },
41 #[error("release {major}.{minor}.{patch} is a minor revision this reader does not support")]
43 UnsupportedFormatVersionMinor {
44 major: u32,
46 minor: u32,
48 patch: u32,
50 },
51 #[error("entry point {identifier:?} has {reason} target {target:?}")]
53 InvalidEntryPointTarget {
54 identifier: String,
56 target: String,
58 reason: &'static str,
60 },
61 #[error("entry point target {target:?} is declared more than once by {identifiers:?}")]
63 DuplicateEntryPointTarget {
64 target: String,
66 identifiers: Vec<String>,
68 },
69 #[error("invalid Morphir IR: {0}")]
71 Decode(#[from] serde_json::Error),
72 #[error("a v3 Specs distribution has no definitions to normalize")]
75 UnsupportedSpecsDistribution,
76}
77
78impl NormalizeError {
79 pub fn code(&self) -> &'static str {
81 match self {
82 Self::MissingFormatVersion => "missing_format_version",
83 Self::InvalidFormatVersionType { .. } => "invalid_format_version_type",
84 Self::InvalidFormatVersionSyntax { .. } => "invalid_format_version_syntax",
85 Self::FormatVersionOutOfRange { .. } => "format_version_out_of_range",
86 Self::UnsupportedFormatVersionMajor { .. } => "unsupported_format_version_major",
87 Self::UnsupportedFormatVersionMinor { .. } => "unsupported_format_version_minor",
88 Self::InvalidEntryPointTarget { .. } => "invalid_entry_point_target",
89 Self::DuplicateEntryPointTarget { .. } => "duplicate_entry_point_target",
90 Self::Decode(_) => "invalid_ir",
91 Self::UnsupportedSpecsDistribution => "unsupported_specs_distribution",
92 }
93 }
94}
95
96pub fn normalize(ir: &Value) -> Result<ProjectionPackage, NormalizeError> {
115 match recognize_version(ir)? {
116 SupportedVersion::V3 => {
117 let distribution = serde_json::from_value(with_integer_version(ir, 3))?;
118 v3::normalize(distribution)
119 }
120 SupportedVersion::V4 => {
121 let ir = serde_json::from_value(with_integer_version(ir, 4))?;
122 v4::normalize(ir)
123 }
124 }
125}
126
127fn with_integer_version(ir: &Value, major: u32) -> Value {
128 let mut normalized = ir.clone();
129 normalized["formatVersion"] = Value::from(major);
130 normalized
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134enum SupportedVersion {
135 V3,
136 V4,
137}
138
139fn recognize_version(ir: &Value) -> Result<SupportedVersion, NormalizeError> {
140 let version = ir
141 .as_object()
142 .and_then(|root| root.get("formatVersion"))
143 .ok_or(NormalizeError::MissingFormatVersion)?;
144 let release = match version {
145 Value::Number(number) => {
146 let Some(major) = number.as_u64() else {
147 return Err(NormalizeError::InvalidFormatVersionType {
148 value: version.to_string(),
149 });
150 };
151 let major =
152 u32::try_from(major).map_err(|_| NormalizeError::FormatVersionOutOfRange {
153 value: version.to_string(),
154 })?;
155 if major == 0 {
156 return Err(NormalizeError::InvalidFormatVersionSyntax {
157 value: version.to_string(),
158 });
159 }
160 (major, 0, 0)
161 }
162 Value::String(source) => parse_release(source)?,
163 _ => {
164 return Err(NormalizeError::InvalidFormatVersionType {
165 value: version.to_string(),
166 });
167 }
168 };
169 supported_version(release)
170}
171
172fn supported_version(release: (u32, u32, u32)) -> Result<SupportedVersion, NormalizeError> {
178 let (major, minor, patch) = release;
179 let triplet = ReleaseTriplet::new(major, minor, patch);
180 match SupportTable::reference().check(&triplet) {
181 Compatibility::Supported => match major {
182 3 => Ok(SupportedVersion::V3),
183 4 => Ok(SupportedVersion::V4),
184 major => Err(NormalizeError::UnsupportedFormatVersionMajor { major }),
185 },
186 Compatibility::UnsupportedMinor => Err(NormalizeError::UnsupportedFormatVersionMinor {
187 major,
188 minor,
189 patch,
190 }),
191 Compatibility::UnsupportedMajor => {
192 Err(NormalizeError::UnsupportedFormatVersionMajor { major })
193 }
194 }
195}
196
197fn parse_release(source: &str) -> Result<(u32, u32, u32), NormalizeError> {
198 let components = source.split('.').collect::<Vec<_>>();
199 if components.len() != 3
200 || components.iter().any(|component| {
201 component.is_empty()
202 || !component.bytes().all(|byte| byte.is_ascii_digit())
203 || (component.len() > 1 && component.starts_with('0'))
204 })
205 {
206 return Err(NormalizeError::InvalidFormatVersionSyntax {
207 value: source.to_owned(),
208 });
209 }
210 let values = components
211 .into_iter()
212 .map(|component| parse_component(source, component))
213 .collect::<Result<Vec<_>, _>>()?;
214 if values[0] < 3 {
215 return Err(NormalizeError::InvalidFormatVersionSyntax {
216 value: source.to_owned(),
217 });
218 }
219 Ok((values[0], values[1], values[2]))
220}
221
222fn parse_component(source: &str, component: &str) -> Result<u32, NormalizeError> {
223 component.bytes().try_fold(0_u32, |value, byte| {
224 value
225 .checked_mul(10)
226 .and_then(|value| value.checked_add(u32::from(byte - b'0')))
227 .ok_or_else(|| NormalizeError::FormatVersionOutOfRange {
228 value: source.to_owned(),
229 })
230 })
231}
232
233pub(crate) fn canonical_fq_name(package: &str, module: &[String], local: &str) -> String {
234 format!("{package}:{}#{local}", module.join("/"))
235}
236
237pub(crate) fn normalize_signature(
238 mut inputs: Vec<crate::model::NamedType>,
239 mut output: Option<crate::model::TypeExpr>,
240) -> (
241 Vec<crate::model::NamedType>,
242 Option<crate::model::TypeExpr>,
243 crate::model::ValueKind,
244) {
245 let mut next_argument = 1;
246 while let Some(crate::model::TypeExpr::Function {
247 input,
248 output: next_output,
249 }) = output
250 {
251 while inputs
252 .iter()
253 .any(|input| lower_camel(&input.name) == format!("arg{next_argument}"))
254 {
255 next_argument += 1;
256 }
257 inputs.push(crate::model::NamedType {
258 name: format!("arg{next_argument}"),
259 tpe: *input,
260 });
261 next_argument += 1;
262 output = Some(*next_output);
263 }
264 let value_kind = if inputs.is_empty() {
265 crate::model::ValueKind::Constant
266 } else {
267 crate::model::ValueKind::Function
268 };
269 (inputs, output, value_kind)
270}