Skip to main content

geam_json/
lib.rs

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
22/// A host profile that composes the official Gleam JSON and standard-library components.
23pub trait GleamJsonHostProfile: GleamStdlibHostProfile + HostComponentProfile<Component> {}
24
25impl<Profile> GleamJsonHostProfile for Profile where
26    Profile: GleamStdlibHostProfile + HostComponentProfile<Component>
27{
28}
29
30/// External value stores used by the official Gleam JSON provider.
31#[derive(Default)]
32pub struct GleamJsonStores {
33    json: function::Stores,
34}
35
36/// The statically composed provider component for the official Gleam JSON package.
37#[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/// External stores for the default combined standard-library and JSON profile.
51#[derive(Default)]
52pub struct GleamJsonProfileStores {
53    stdlib: GleamStdlibStores,
54    json: GleamJsonStores,
55}
56
57/// Caller-owned run state for the default combined standard-library and JSON profile.
58pub struct GleamJsonRunState {
59    stdlib: GleamStdlibRunState,
60    json: (),
61}
62
63/// The default profile for composing the official standard-library and JSON providers.
64#[derive(Debug, Clone, Copy)]
65pub struct GleamJsonProfile;
66
67impl GleamJsonRunState {
68    /// Combines caller-owned standard-library state with the stateless JSON component.
69    pub fn new(stdlib: GleamStdlibRunState) -> Self {
70        Self { stdlib, json: () }
71    }
72
73    /// Returns the standard-library state.
74    pub fn stdlib(&self) -> &GleamStdlibRunState {
75        &self.stdlib
76    }
77
78    /// Returns mutable standard-library state.
79    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
113/// Registers the Rust provider for the official Gleam JSON package.
114pub 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}