golem_scalajs_wit_bindgen/codegen/
interface.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::{eyre::eyre, Result};
18use convert_case::{Case, Casing};
19use id_arena::Id;
20use wit_parser::{Interface as WitInterface, TypeDefKind, TypeOwner, UnresolvedPackage};
21
22use super::{Function, Record, Render, Variant};
23use crate::types::TypeMap;
24
25/// Represents the name of an interface (trait) in Scala
26#[derive(Clone)]
27struct InterfaceName(String);
28
29impl Display for InterfaceName {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(f, "{}", self.0)
32    }
33}
34
35impl From<&String> for InterfaceName {
36    fn from(name: &String) -> Self {
37        Self(name.to_case(Case::UpperCamel))
38    }
39}
40
41/// Represents an interface (trait) in Scala
42pub struct Interface {
43    /// The name of the interface
44    name: InterfaceName,
45
46    /// The records associated to the interface
47    records: Vec<Record>,
48
49    /// The variants associated to the interface
50    variants: Vec<Variant>,
51
52    /// The functions associated to the interface
53    functions: Vec<Function>,
54}
55
56impl Interface {
57    /// Constructs an `Interface` from WIT
58    pub fn from_wit(unresolved_package: &UnresolvedPackage) -> Result<Self> {
59        let (interface_id, interface) = Self::get_interface("api", unresolved_package)?;
60        let type_map = TypeMap::from(unresolved_package);
61        let types = &unresolved_package.types;
62
63        let records: Result<Vec<Record>> = types
64            .iter()
65            .filter(|(_, ty)| match ty.owner {
66                TypeOwner::Interface(id) => id == interface_id,
67                _ => false,
68            })
69            .filter_map(|(_, ty)| match &ty.kind {
70                TypeDefKind::Record(record) => ty
71                    .name
72                    .as_ref()
73                    .map(|name| Record::from_wit(name, record, &type_map)),
74                _ => None,
75            })
76            .collect();
77
78        let variants: Result<Vec<Variant>> = types
79            .iter()
80            .filter(|(_, ty)| match ty.owner {
81                TypeOwner::Interface(id) => id == interface_id,
82                _ => false,
83            })
84            .filter_map(|(_, ty)| match &ty.kind {
85                TypeDefKind::Variant(variant) => ty
86                    .name
87                    .as_ref()
88                    .map(|name| Variant::from_wit(name, variant, &type_map)),
89                _ => None,
90            })
91            .collect();
92
93        let functions: Result<Vec<Function>> = interface
94            .functions
95            .iter()
96            .map(|(_, function)| Function::from_wit(function.clone(), &type_map))
97            .collect();
98
99        Ok(Self {
100            name: InterfaceName::from(interface.name.as_ref().ok_or(eyre!(
101                "Interface with ID {interface_id:?} does not have a name"
102            ))?),
103            records: records?,
104            variants: variants?,
105            functions: functions?,
106        })
107    }
108
109    fn get_interface<'a>(
110        name: &'static str,
111        unresolved_package: &'a UnresolvedPackage,
112    ) -> Result<(Id<WitInterface>, &'a WitInterface)> {
113        unresolved_package
114            .interfaces
115            .iter()
116            .find(|(_, interface)| interface.name.clone().unwrap_or_default() == name)
117            .ok_or(eyre!("Interface {name} not found"))
118    }
119
120    /// Renders this to a String
121    pub fn render(self, package: &str) -> Result<String> {
122        fn render(elements: Vec<impl Render>) -> Result<String> {
123            let elements: Result<Vec<String>> = elements.into_iter().map(Render::render).collect();
124            Ok(elements?.join("\n"))
125        }
126
127        let records = render(self.records)?;
128        let variants = render(self.variants)?;
129        let functions = render(self.functions)?;
130        let name = self.name;
131
132        Ok(format!(
133            "
134                // Generated by golem-scalajs-wit-bindgen
135                package {package}
136
137                import scala.scalajs.js
138                import scala.scalajs.js.JSConverters._
139
140                {records}
141
142                {variants}
143
144                trait {name} {{
145                    type WitResult[+Ok, +Err] = Ok
146                    object WitResult {{
147                        def ok[Ok](value: Ok): WitResult[Ok, Nothing] = value
148
149                        def err[Err](value: Err): WitResult[Nothing, Err] = throw js.JavaScriptException(value)
150
151                        val unit: WitResult[Unit, Nothing] = ()
152                    }}
153
154                    type WitOption[+A] = js.UndefOr[A]
155                    object WitOption {{
156                        def some[A](value: A): WitOption[A] = value
157
158                        val none: WitOption[Nothing] = js.undefined
159
160                        def fromOption[A](option: Option[A]) =
161                        option match {{
162                            case Some(value) => value.asInstanceOf[js.UndefOr[A]]
163                            case None        => js.undefined
164                        }}
165                    }}
166
167                    type WitList[A] = js.Array[A]
168                    object WitList {{
169                        def fromList[A](list: List[A]): WitList[A] = list.toJSArray
170                    }}
171                    
172                    {functions}
173                }}
174            "
175        ))
176    }
177}