1mod function;
2mod schema;
3
4pub(crate) use geam_core::{
5 BitArrayValue, HostCall, HostCallCompletion, HostCallError, HostComponentProfile,
6 HostCustomType, HostExternal, HostList, HostListType, HostProfile, HostProvider,
7 HostProviderComponent, HostProviderComponentRegistration, HostProviderModule,
8 HostRegistrationError,
9};
10#[cfg(test)]
11pub(crate) use geam_core::{
12 ExecutionError, HostCustomConstructorSchema, HostCustomFieldSchema, HostCustomTypeSchema,
13 HostError, HostExternalBinding, HostExternalStorage, HostExternalType, HostExternalTypeSchema,
14 HostModule, HostProviderSet, HostSchemaType, HostedExecution, InvariantError, ModuleSource,
15 PackageSource, Value, ValueType, compile_typed_host_program, plan_host_program,
16};
17use geam_stdlib::{
18 Component as GleamStdlibComponent, GleamStdlibHostProfile, GleamStdlibRunState,
19 GleamStdlibStores, IoOutput,
20};
21
22pub trait GleamJsonHostProfile: GleamStdlibHostProfile + HostComponentProfile<Component> {}
24
25impl<Profile> GleamJsonHostProfile for Profile where
26 Profile: GleamStdlibHostProfile + HostComponentProfile<Component>
27{
28}
29
30#[derive(Default)]
32pub struct GleamJsonStores {
33 json: function::Stores,
34}
35
36#[derive(Debug, Clone, Copy)]
38pub struct Component;
39
40impl HostProviderComponent for Component {
41 const ID: &'static str = "gleam_json";
42 type Stores = GleamJsonStores;
43 type RunState = ();
44}
45
46impl geam_core::__macro_support::ProviderPackage for Component {
47 const PACKAGE: &'static str = "gleam_json";
48}
49
50#[derive(Default)]
52pub struct GleamJsonProfileStores {
53 stdlib: GleamStdlibStores,
54 json: GleamJsonStores,
55}
56
57pub struct GleamJsonRunState {
59 stdlib: GleamStdlibRunState,
60 json: (),
61}
62
63#[derive(Debug, Clone, Copy)]
65pub struct GleamJsonProfile;
66
67impl GleamJsonRunState {
68 pub fn new(stdlib: GleamStdlibRunState) -> Self {
70 Self { stdlib, json: () }
71 }
72
73 pub fn stdlib(&self) -> &GleamStdlibRunState {
75 &self.stdlib
76 }
77
78 pub fn stdlib_mut(&mut self) -> &mut GleamStdlibRunState {
80 &mut self.stdlib
81 }
82}
83
84impl HostProfile for GleamJsonProfile {
85 type RunState = GleamJsonRunState;
86 type ExternalStores = GleamJsonProfileStores;
87}
88
89impl HostComponentProfile<GleamStdlibComponent> for GleamJsonProfile {
90 fn component_stores(stores: &Self::ExternalStores) -> &GleamStdlibStores {
91 &stores.stdlib
92 }
93
94 fn component_state(state: &mut Self::RunState) -> &mut GleamStdlibRunState {
95 &mut state.stdlib
96 }
97}
98
99impl GleamStdlibHostProfile for GleamJsonProfile {
100 type Io = Vec<IoOutput>;
101}
102
103impl HostComponentProfile<Component> for GleamJsonProfile {
104 fn component_stores(stores: &Self::ExternalStores) -> &GleamJsonStores {
105 &stores.json
106 }
107
108 fn component_state(state: &mut Self::RunState) -> &mut () {
109 &mut state.json
110 }
111}
112
113pub fn host_providers<Profile>() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>
115where
116 Profile: GleamJsonHostProfile,
117{
118 <Component as HostProviderComponentRegistration<Profile>>::providers()
119}
120
121impl<Profile> HostProviderComponentRegistration<Profile> for Component
122where
123 Profile: GleamJsonHostProfile,
124{
125 fn providers() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError> {
126 function::host_provider::<Profile>().map(|provider| vec![provider])
127 }
128}
129
130pub(crate) fn json_stores<Profile>(stores: &Profile::ExternalStores) -> &GleamJsonStores
131where
132 Profile: GleamJsonHostProfile,
133{
134 <Profile as HostComponentProfile<Component>>::component_stores(stores)
135}
136
137pub(crate) fn provider_stores<Profile>(stores: &Profile::ExternalStores) -> &function::Stores
138where
139 Profile: GleamJsonHostProfile,
140{
141 &json_stores::<Profile>(stores).json
142}
143
144#[cfg(test)]
145mod test_support;
146
147#[cfg(test)]
148mod tests {
149 use super::test_support::{CustomProfile, CustomRunState, CustomStores};
150 use super::{
151 Component, GleamJsonProfile, GleamJsonProfileStores, GleamJsonRunState, host_providers,
152 json_stores,
153 };
154 use crate::{HostComponentProfile, HostProviderComponent, HostProviderComponentRegistration};
155 use geam_stdlib::{Component as GleamStdlibComponent, GleamStdlibRunState};
156
157 #[test]
158 #[should_panic(expected = "decode_to_dynamic should return Result")]
159 fn dynamic_result_assertion_rejects_non_result_types() {
160 assert_dynamic_result(&crate::ValueType::Nil);
161 }
162
163 #[test]
164 #[should_panic(expected = "decode_to_dynamic error should be DecodeError")]
165 fn dynamic_result_assertion_rejects_non_custom_error_types() {
166 assert_decode_error(&crate::ValueType::Nil);
167 }
168
169 #[test]
170 #[should_panic(expected = "expected external type")]
171 fn external_type_assertion_rejects_non_external_types() {
172 assert_external_type(&crate::ValueType::Nil, "package", "module", "Type", &[]);
173 }
174
175 #[test]
176 fn default_and_custom_profiles_project_independent_stdlib_and_json_components() {
177 let default = GleamJsonProfileStores::default();
178 let custom = CustomStores::default();
179
180 assert!(std::ptr::eq(
181 <GleamJsonProfile as HostComponentProfile<GleamStdlibComponent>>::component_stores(
182 &default,
183 ),
184 &default.stdlib,
185 ));
186 assert!(std::ptr::eq(
187 json_stores::<GleamJsonProfile>(&default),
188 &default.json,
189 ));
190 assert!(std::ptr::eq(
191 <CustomProfile as HostComponentProfile<GleamStdlibComponent>>::component_stores(
192 &custom,
193 ),
194 &custom.stdlib,
195 ));
196 assert!(std::ptr::eq(
197 json_stores::<CustomProfile>(&custom),
198 &custom.json,
199 ));
200
201 let mut default_state = GleamJsonRunState::new(GleamStdlibRunState::from_seed([1; 32]));
202 let mut custom_state = CustomRunState {
203 stdlib: GleamStdlibRunState::from_seed([2; 32]),
204 json: (),
205 };
206 let default_pointer = default_state.stdlib_mut() as *mut GleamStdlibRunState;
207 assert!(std::ptr::eq(
208 <GleamJsonProfile as HostComponentProfile<GleamStdlibComponent>>::component_state(
209 &mut default_state,
210 ),
211 default_pointer,
212 ));
213 let custom_pointer = &mut custom_state.stdlib as *mut GleamStdlibRunState;
214 assert!(std::ptr::eq(
215 <CustomProfile as HostComponentProfile<GleamStdlibComponent>>::component_state(
216 &mut custom_state,
217 ),
218 custom_pointer,
219 ));
220 let default_json = &mut default_state.json as *mut ();
221 assert!(std::ptr::eq(
222 <GleamJsonProfile as HostComponentProfile<Component>>::component_state(
223 &mut default_state,
224 ),
225 default_json,
226 ));
227 let custom_json = &mut custom_state.json as *mut ();
228 assert!(std::ptr::eq(
229 <CustomProfile as HostComponentProfile<Component>>::component_state(&mut custom_state),
230 custom_json,
231 ));
232 assert!(default_state.stdlib().io_outputs().is_empty());
233 assert!(custom_state.stdlib.io_outputs().is_empty());
234 }
235
236 #[test]
237 fn registers_only_the_exact_official_erlang_json_provider_inventory() {
238 assert_eq!(<Component as HostProviderComponent>::ID, "gleam_json");
239 let mut providers =
240 <Component as HostProviderComponentRegistration<GleamJsonProfile>>::providers()
241 .expect("JSON component should register");
242 let facade =
243 host_providers::<GleamJsonProfile>().expect("official JSON provider should register");
244 assert_eq!(facade.len(), providers.len());
245 assert_eq!(facade[0].module(), providers[0].module());
246 assert_eq!(providers.len(), 1);
247 let provider = providers
248 .pop()
249 .expect("JSON package should have one provider module");
250
251 assert_eq!(provider.package(), "gleam_json");
252 assert_eq!(provider.module(), "gleam/json");
253 assert_eq!(
254 provider
255 .external_types()
256 .map(|schema| (schema.name().as_str(), schema.parameter_count()))
257 .collect::<Vec<_>>(),
258 [("Json", 0)],
259 );
260 assert_eq!(
261 provider
262 .functions()
263 .map(|function| function.name().as_str())
264 .collect::<Vec<_>>(),
265 [
266 "decode_to_dynamic",
267 "do_to_string",
268 "to_string_tree",
269 "do_string",
270 "do_bool",
271 "do_int",
272 "do_float",
273 "do_null",
274 "do_object",
275 "do_preprocessed_array",
276 ],
277 );
278
279 let functions = provider.functions().collect::<Vec<_>>();
280 for function in &functions {
281 assert!(function.scheme().parameters().is_empty());
282 }
283
284 let json = functions[7].type_().return_().clone();
285 assert_external_type(&json, "gleam_json", "gleam/json", "Json", &[]);
286 assert_eq!(
287 functions[0].type_().argument_types(),
288 [crate::ValueType::BitArray]
289 );
290 assert_dynamic_result(functions[0].type_().return_());
291 assert_eq!(
292 functions[1].type_().argument_types(),
293 std::slice::from_ref(&json),
294 );
295 assert_eq!(functions[1].type_().return_(), &crate::ValueType::String);
296 assert_eq!(
297 functions[2].type_().argument_types(),
298 std::slice::from_ref(&json),
299 );
300 assert_external_type(
301 functions[2].type_().return_(),
302 "gleam_stdlib",
303 "gleam/string_tree",
304 "StringTree",
305 &[],
306 );
307 assert_eq!(
308 functions[3].type_().argument_types(),
309 [crate::ValueType::String]
310 );
311 assert_eq!(
312 functions[4].type_().argument_types(),
313 [crate::ValueType::Bool]
314 );
315 assert_eq!(
316 functions[5].type_().argument_types(),
317 [crate::ValueType::Int]
318 );
319 assert_eq!(
320 functions[6].type_().argument_types(),
321 [crate::ValueType::Float]
322 );
323 assert!(functions[7].type_().argument_types().is_empty());
324 for function in &functions[3..] {
325 assert_eq!(function.type_().return_(), &json);
326 }
327 assert_eq!(
328 functions[8].type_().argument_types(),
329 [crate::ValueType::List(Box::new(crate::ValueType::Tuple(
330 vec![crate::ValueType::String, json.clone(),]
331 )))],
332 );
333 assert_eq!(
334 functions[9].type_().argument_types(),
335 [crate::ValueType::List(Box::new(json))],
336 );
337 }
338
339 fn assert_dynamic_result(type_: &crate::ValueType) {
340 let crate::ValueType::Custom(result) = type_ else {
341 panic!("decode_to_dynamic should return Result: {type_:?}");
342 };
343 assert_eq!(result.type_name().package(), "");
344 assert_eq!(result.type_name().module(), "gleam");
345 assert_eq!(result.type_name().name(), "Result");
346 assert_eq!(result.arguments().len(), 2);
347 assert_external_type(
348 &result.arguments()[0],
349 "gleam_stdlib",
350 "gleam/dynamic",
351 "Dynamic",
352 &[],
353 );
354 assert_decode_error(&result.arguments()[1]);
355 }
356
357 fn assert_decode_error(type_: &crate::ValueType) {
358 let crate::ValueType::Custom(error) = type_ else {
359 panic!("decode_to_dynamic error should be DecodeError");
360 };
361 assert_eq!(error.type_name().package(), "gleam_json");
362 assert_eq!(error.type_name().module(), "gleam/json");
363 assert_eq!(error.type_name().name(), "DecodeError");
364 assert!(error.arguments().is_empty());
365 }
366
367 fn assert_external_type(
368 type_: &crate::ValueType,
369 package: &str,
370 module: &str,
371 name: &str,
372 arguments: &[crate::ValueType],
373 ) {
374 let crate::ValueType::External(type_) = type_ else {
375 panic!("expected external type {package}::{module}.{name}: {type_:?}");
376 };
377 assert_eq!(type_.type_name().package(), package);
378 assert_eq!(type_.type_name().module(), module);
379 assert_eq!(type_.type_name().name(), name);
380 assert_eq!(type_.arguments(), arguments);
381 }
382}