Skip to content

Commit b8477c5

Browse files
committed
Fix optional Variant enum types
1 parent 2c997ef commit b8477c5

File tree

4 files changed

+144
-67
lines changed

4 files changed

+144
-67
lines changed

Diff for: Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rust-purs-gql"
3-
version = "0.1.12"
3+
version = "0.1.13"
44
edition = "2021"
55
default-run = "pursgql"
66
repository = "https://github.com/OxfordAbstracts/purescript-graphql-schema-gen"

Diff for: src/config/workspace.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl WorkspaceConfig {
5151
yaml_hash.get(&Yaml::String("shared_graphql_enums_dir".to_string()))?;
5252
let schema_libs_prefix = yaml_hash.get(&Yaml::String("schema_libs_prefix".to_string()))?;
5353
let schema_libs_dir = yaml_hash.get(&Yaml::String("schema_libs_dir".to_string()))?;
54-
let variant_enums = yaml_hash.get(&Yaml::String("variant_enums".to_string()))?;
54+
let variant_enums = yaml_hash.get(&Yaml::String("variant_enums".to_string()));
5555
Some(Self {
5656
postgres_enums_lib: postgres_enums_lib
5757
.as_str()
@@ -78,6 +78,7 @@ impl WorkspaceConfig {
7878
.expect("Workspace yaml should contain schema_libs_dir key.")
7979
.to_string(),
8080
variant_enums: variant_enums
81+
.unwrap_or(&Yaml::Array(vec![]))
8182
.as_vec()
8283
.unwrap_or(&vec![])
8384
.iter()

Diff for: src/enums/generate_enum.rs

+140-64
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cynic_introspection::EnumType;
2-
use stringcase::pascal_case;
2+
use stringcase::{camel_case, pascal_case};
33

44
use crate::config::workspace::WorkspaceConfig;
55
use crate::purescript_gen::purescript_enum::Enum;
@@ -50,12 +50,20 @@ pub async fn generate_enum(
5050
&workspace_config.shared_graphql_enums_lib
5151
);
5252
let package_name = pascal_case(&workspace_config.shared_graphql_enums_lib);
53-
54-
if let Some(variant) = variant_mod(&name, &original_values) {
53+
let helper_module = format!("{package_name}.Utils.VariantHelpers");
54+
if let Some(variant) = variant_mod(
55+
&name,
56+
&original_values,
57+
&format!("\nimport {helper_module} (var, match)"),
58+
) {
5559
write(
5660
&format!("{lib_path}/src/{package_name}/{name}.purs"),
5761
&variant,
5862
);
63+
write(
64+
&format!("{lib_path}/src/{package_name}/Utils/VariantHelpers.purs"),
65+
&format!("module {helper_module} where \n{VARIANT_HELPERS_MOD}"),
66+
);
5967
write(&format!("{lib_path}/spago.yaml"), &enums_spago_yaml());
6068
}
6169

@@ -214,75 +222,82 @@ fn enum_instances(name: &str, values: &Vec<String>, original_values: &Vec<String
214222
instances
215223
}
216224

217-
fn variant_mod(name: &str, original_values: &Vec<String>) -> Option<String> {
225+
fn variant_mod(name: &str, original_values: &Vec<String>, helper_import: &str) -> Option<String> {
218226
if original_values.len() == 0 {
219227
return None;
220228
}
221229

222230
let values: Vec<String> = original_values.iter().map(|v| v.to_lowercase()).collect();
231+
let zipped: Vec<(&String, &String)> = values.iter().zip(original_values.iter()).collect();
223232

224233
let mut instances = String::new();
225234
let first_value = &values[0];
226-
let last_value = values
227-
.last()
228-
.expect("Enums should have at least one value in order to get last.");
235+
let first_fn = camel_case(&original_values[0]);
236+
let last_fn = camel_case(
237+
values
238+
.last()
239+
.expect("Enums should have at least one value in order to get last."),
240+
);
229241

230242
let mut variant = String::new();
231243
let mut variant_fns = String::new();
232244

233245
// Define the type
234246
variant.push_str(&format!(
235-
r#"
236-
type {name} = Variant"#
247+
r#"newtype {name} = {name} {name}Variant
248+
249+
type {name}Variant = Variant
250+
"#
237251
));
238252

239-
for value in &values {
240-
let variant_name = value.to_lowercase();
241-
let variant_member = if value == first_value {
242-
format!(" ( {variant_name}\n")
253+
for (lower, original) in zipped {
254+
let variant_member = if lower == first_value {
255+
format!(" ( \"{original}\" :: Unit\n")
243256
} else {
244-
format!(" , {variant_name}\n")
257+
format!(" , \"{original}\" :: Unit\n")
245258
};
246259

247260
// Add the variant member to the row
248261
variant.push_str(&variant_member);
249262

250263
// Define the variant fn for easy calling
251-
variant_fns.push_str(&to_variant(name, &variant_name));
264+
variant_fns.push_str(&to_variant(name, &original));
252265
}
253266

254267
// Add the variant type closing bracket
255-
variant.push_str("\n )");
268+
variant.push_str(" )");
256269

257270
// instances:
258271

259272
// Next values in the enum
260-
let succ_values = values
273+
let succ_values = original_values
261274
.iter()
262275
.enumerate()
263276
.map(|(i, v)| {
264277
if i == values.len() - 1 {
265-
format!("{} -> Nothing", v)
278+
format!(r#"# match @"{v}" Nothing"#)
266279
} else {
267-
format!("{} -> Just {}", v, values[i + 1])
280+
let next_fn = camel_case(&values[i + 1]);
281+
format!(r#"# match @"{v}" (Just {next_fn})"#)
268282
}
269283
})
270284
.collect::<Vec<String>>()
271-
.join("\n ");
285+
.join("\n ");
272286

273287
// Previous values in the enum
274-
let pred_values = values
288+
let pred_values = original_values
275289
.iter()
276290
.enumerate()
277291
.map(|(i, v)| {
278292
if i == 0 {
279-
format!("{} -> Nothing", v)
293+
format!(r#"# match @"{v}" Nothing"#)
280294
} else {
281-
format!("{} -> Just {}", v, values[i - 1])
295+
let prev_fn = camel_case(&values[i - 1]);
296+
format!(r#"# match @"{v}" (Just {prev_fn})"#)
282297
}
283298
})
284299
.collect::<Vec<String>>()
285-
.join("\n ");
300+
.join("\n ");
286301

287302
// Cardinality of the enum
288303
let cardinality = values.len();
@@ -291,48 +306,36 @@ type {name} = Variant"#
291306
let to_enum = values
292307
.iter()
293308
.enumerate()
294-
.map(|(i, v)| format!("{} -> Just {}", i, v))
309+
.map(|(i, v)| format!("{i} -> Just {}", camel_case(v)))
295310
.collect::<Vec<String>>()
296311
.join("\n ");
297312

298313
// Convert an enum value to the corresponding index
299-
let from_enum = values
314+
let from_enum = original_values
300315
.iter()
301316
.enumerate()
302-
.map(|(i, v)| format!("{} -> {}", v, i))
317+
.map(|(i, v)| format!(r#"# match @"{v}" {i}"#))
303318
.collect::<Vec<String>>()
304-
.join("\n ");
305-
306-
let decode_json = values
307-
.iter()
308-
.zip(original_values.iter())
309-
.map(|(v, original)| format!(r#""{original}" -> pure {v}"#))
310-
.collect::<Vec<String>>()
311-
.join("\n ");
319+
.join("\n ");
312320

313-
let encode_json = values
321+
let decode_json = original_values
314322
.iter()
315-
.zip(original_values.iter())
316-
.map(|(v, original)| format!(r#"on (Proxy @"{v}") (\_ -> show {original})"#))
323+
.map(|original| {
324+
let fn_name = camel_case(original);
325+
format!(r#""{original}" -> pure {fn_name}"#)
326+
})
317327
.collect::<Vec<String>>()
318328
.join("\n ");
319329

320330
instances.push_str(&format!(
321331
r#"
322-
323-
instance DecodeJson {name} where
324-
decodeJson = decodeJson >=> case _ of
325-
{decode_json}
326-
s -> Left $ TypeMismatch $ \"Not a {name}: \" <> s
327-
328-
instance EncodeJson {name} where
329-
encodeJson = case_
330-
{encode_json}
331-
332-
instance MakeFixture {name} where mkFixture = {first_value}
332+
derive instance Newtype {name} _
333333
334334
instance Show {name} where
335-
show a = unvariant q.data_type # \(Unvariant f) -> f \p _ -> reflectSymbol p
335+
show = unwrap >>> unvariant >>> \(Unvariant f) -> f \p _ -> reflectSymbol p
336+
337+
instance MakeFixture {name} where
338+
mkFixture = {first_fn}
336339
337340
instance Eq {name} where
338341
eq = eq `on` show
@@ -343,40 +346,53 @@ instance Ord {name} where
343346
instance GqlArgString {name} where
344347
toGqlArgStringImpl = show
345348
349+
instance DecodeJson {name} where
350+
decodeJson = decodeJson >=> case _ of
351+
{decode_json}
352+
s -> Left $ TypeMismatch $ "Not a {name}: " <> s
353+
354+
instance EncodeJson {name} where
355+
encodeJson = show >>> encodeJson
356+
346357
instance DecodeHasura {name} where
347358
decodeHasura = decodeJson
348359
349360
instance EncodeHasura {name} where
350361
encodeHasura = encodeJson
351362
352363
instance Enum {name} where
353-
succ a = case a of
354-
{succ_values}
355-
pred a = case a of
356-
{pred_values}
364+
succ = unwrap >>>
365+
( case_
366+
{succ_values}
367+
)
368+
pred = unwrap >>>
369+
( case_
370+
{pred_values}
371+
)
357372
358373
instance Bounded {name} where
359-
top = {last_value}
360-
bottom = {first_value}
374+
bottom = {first_fn}
375+
top = {last_fn}
361376
362377
instance BoundedEnum {name} where
363378
cardinality = Cardinality {cardinality}
364379
toEnum a = case a of
365380
{to_enum}
366381
_ -> Nothing
367-
fromEnum a = case a of
368-
{from_enum}
382+
fromEnum = unwrap >>>
383+
( case_
384+
{from_enum}
385+
)
369386
"#
370387
));
371388

372-
Some(format!("module {name} where\n\n{VARIANT_MODULE_IMPORTS}\n\n{variant}\n\n{variant_fns}\n\n{instances}"))
389+
Some(format!("module {name} where\n\n{VARIANT_MODULE_IMPORTS}{helper_import}\n\n{variant}\n\n{variant_fns}{instances}"))
373390
}
374391

375392
fn to_variant(type_name: &str, name: &str) -> String {
393+
let fn_name = camel_case(name);
376394
format!(
377-
r#"
378-
var :: {type_name}
379-
var = inj (Proxy @"{name}") unit
395+
r#"{fn_name} = var @"{name}" :: {type_name}
380396
"#
381397
)
382398
}
@@ -427,6 +443,66 @@ import Class.DecodeOa (class DecodeOa)"#;
427443

428444
const VARIANT_MODULE_IMPORTS: &str = r#"import Prelude
429445
430-
import Data.Variant (Unvariant(..), Variant, inj, unvariant)
446+
import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError(..), decodeJson)
447+
import Data.Argonaut.Encode (class EncodeJson, encodeJson)
448+
import Data.Either (Either(..))
431449
import Data.Enum (class Enum, class BoundedEnum, Cardinality(..))
432-
import Proxy (Proxy(..))"#;
450+
import Data.Function (on)
451+
import Data.Maybe (Maybe(..))
452+
import Data.Newtype (class Newtype, unwrap)
453+
import Data.Symbol (reflectSymbol)
454+
import Data.Variant (Unvariant(..), Variant, case_, unvariant)
455+
import GraphQL.Client.ToGqlString (class GqlArgString)
456+
import GraphQL.Hasura.Decode (class DecodeHasura)
457+
import GraphQL.Hasura.Encode (class EncodeHasura)
458+
import OaMakeFixture (class MakeFixture)"#;
459+
460+
const VARIANT_HELPERS_MOD: &str = r#"
461+
import Prelude
462+
463+
import Data.Maybe (Maybe)
464+
import Data.Newtype (class Newtype, wrap, unwrap)
465+
import Data.Symbol (class IsSymbol)
466+
import Data.Variant (Variant, inj)
467+
import Data.Variant as V
468+
import Data.Variant.Internal (class VariantTags)
469+
import Prim.Row as R
470+
import Prim.RowList as RL
471+
import Type.Proxy (Proxy(..))
472+
473+
var :: ∀ @sym r1 r2 t. R.Cons sym Unit r1 r2 ⇒ IsSymbol sym => Newtype t (Variant r2) => t
474+
var = wrap $ inj (Proxy @sym) unit
475+
476+
match
477+
:: ∀ @sym a r1 r2 b
478+
. R.Cons sym a r1 r2
479+
=> IsSymbol sym
480+
=> b
481+
→ (Variant r1 → b)
482+
→ Variant r2
483+
→ b
484+
match b = V.on (Proxy @sym) (\_ -> b)
485+
486+
-- | Expand a newtyped variant into a target newtyped variant.
487+
-- | Requires the input variant be a sub-variant of the target variant.
488+
expand
489+
:: forall t1 t2 v1 r_ v2
490+
. Newtype t1 (Variant v1)
491+
=> R.Union v1 r_ v2
492+
=> Newtype t2 (Variant v2)
493+
=> t1
494+
-> t2
495+
expand = unwrap >>> V.expand >>> wrap
496+
497+
-- | Contract one newtyped variant into another.
498+
-- | Will return Nothing if the variant is not a variant of the target type.
499+
contract
500+
:: forall t1 v1 v2 t2 rl r_
501+
. Newtype t1 (Variant v1)
502+
=> RL.RowToList v2 rl
503+
=> R.Union v2 r_ v1
504+
=> VariantTags rl
505+
=> Newtype t2 (Variant v2)
506+
=> t1
507+
-> Maybe t2
508+
contract = unwrap >>> V.contract >>> map wrap"#;

0 commit comments

Comments
 (0)