golem_scalajs_wit_bindgen/codegen/
record.rs

1// Copyright 2024 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Display;
16
17use color_eyre::Result;
18use convert_case::{Case, Casing};
19use wit_parser::{Field as WitField, Record as WitRecord};
20
21use super::Render;
22use crate::types::{ConcreteName, Type, TypeMap, TypeName};
23
24/// Represents the name of a record field in Scala
25struct FieldName(String);
26
27impl Display for FieldName {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.0)
30    }
31}
32
33impl From<String> for FieldName {
34    fn from(name: String) -> Self {
35        Self(name.to_case(Case::Camel))
36    }
37}
38
39/// Represents a record field in Scala
40struct Field {
41    /// The field name
42    name: FieldName,
43
44    /// The Scala type associated to the field
45    ty: Type,
46}
47
48impl Field {
49    // Constructs a `Field` from WIT
50    pub fn from_wit(field: WitField, type_map: &TypeMap) -> Result<Self> {
51        Ok(Self {
52            name: FieldName::from(field.name),
53            ty: Type::from_wit(field.ty, type_map)?,
54        })
55    }
56}
57
58/// Represents a record in Scala
59pub struct Record {
60    /// The record name
61    name: TypeName,
62
63    /// The record fields
64    fields: Vec<Field>,
65}
66
67impl Record {
68    // Constructs a `Record` from WIT
69    pub fn from_wit(name: &str, record: &WitRecord, type_map: &TypeMap) -> Result<Self> {
70        let fields: Result<Vec<Field>> = record
71            .clone()
72            .fields
73            .into_iter()
74            .map(|field| Field::from_wit(field, type_map))
75            .collect();
76
77        Ok(Self {
78            name: TypeName::Concrete(ConcreteName::from(name.to_owned())),
79            fields: fields?,
80        })
81    }
82}
83
84impl Render for Record {
85    fn render(self) -> Result<String> {
86        fn render<F>(fields: &[Field], sep: &str, formatter: F) -> String
87        where
88            F: FnMut(&Field) -> String,
89        {
90            fields.iter().map(formatter).collect::<Vec<_>>().join(sep)
91        }
92
93        let fields = render(&self.fields, "\n", |Field { name, ty }| {
94            format!("val {name}: {ty}")
95        });
96
97        let apply_params = render(&self.fields, ", ", |Field { name, ty }| {
98            format!("{name}: {ty}")
99        });
100
101        let apply_temp_vars = render(&self.fields, "\n", |Field { name, ty }| {
102            format!("val {name}0: {ty} = {name}")
103        });
104
105        let new_vars = render(&self.fields, "\n", |Field { name, ty }| {
106            format!("val {name}: {ty} = {name}0")
107        });
108
109        let name = self.name;
110
111        Ok(format!(
112            "
113                sealed trait {name} extends js.Object {{
114                    {fields}
115                }}
116                object {name} {{
117                    def apply({apply_params}): {name} = {{
118                        {apply_temp_vars}
119
120                        new {name} {{
121                            {new_vars}
122                        }}
123                    }}
124                }}
125            "
126        ))
127    }
128}