1
1
use cynic_introspection:: EnumType ;
2
- use stringcase:: pascal_case;
2
+ use stringcase:: { camel_case , pascal_case} ;
3
3
4
4
use crate :: config:: workspace:: WorkspaceConfig ;
5
5
use crate :: purescript_gen:: purescript_enum:: Enum ;
@@ -50,12 +50,20 @@ pub async fn generate_enum(
50
50
& workspace_config. shared_graphql_enums_lib
51
51
) ;
52
52
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 ! ( "\n import {helper_module} (var, match)" ) ,
58
+ ) {
55
59
write (
56
60
& format ! ( "{lib_path}/src/{package_name}/{name}.purs" ) ,
57
61
& variant,
58
62
) ;
63
+ write (
64
+ & format ! ( "{lib_path}/src/{package_name}/Utils/VariantHelpers.purs" ) ,
65
+ & format ! ( "module {helper_module} where \n {VARIANT_HELPERS_MOD}" ) ,
66
+ ) ;
59
67
write ( & format ! ( "{lib_path}/spago.yaml" ) , & enums_spago_yaml ( ) ) ;
60
68
}
61
69
@@ -214,75 +222,82 @@ fn enum_instances(name: &str, values: &Vec<String>, original_values: &Vec<String
214
222
instances
215
223
}
216
224
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 > {
218
226
if original_values. len ( ) == 0 {
219
227
return None ;
220
228
}
221
229
222
230
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 ( ) ;
223
232
224
233
let mut instances = String :: new ( ) ;
225
234
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
+ ) ;
229
241
230
242
let mut variant = String :: new ( ) ;
231
243
let mut variant_fns = String :: new ( ) ;
232
244
233
245
// Define the type
234
246
variant. push_str ( & format ! (
235
- r#"
236
- type {name} = Variant"#
247
+ r#"newtype {name} = {name} {name}Variant
248
+
249
+ type {name}Variant = Variant
250
+ "#
237
251
) ) ;
238
252
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 " )
243
256
} else {
244
- format ! ( " , {variant_name} \n " )
257
+ format ! ( " , \" {original} \" :: Unit \n " )
245
258
} ;
246
259
247
260
// Add the variant member to the row
248
261
variant. push_str ( & variant_member) ;
249
262
250
263
// 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 ) ) ;
252
265
}
253
266
254
267
// Add the variant type closing bracket
255
- variant. push_str ( "\n )" ) ;
268
+ variant. push_str ( " )" ) ;
256
269
257
270
// instances:
258
271
259
272
// Next values in the enum
260
- let succ_values = values
273
+ let succ_values = original_values
261
274
. iter ( )
262
275
. enumerate ( )
263
276
. map ( |( i, v) | {
264
277
if i == values. len ( ) - 1 {
265
- format ! ( "{} -> Nothing", v )
278
+ format ! ( r#"# match @"{v}" Nothing"# )
266
279
} 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})"# )
268
282
}
269
283
} )
270
284
. collect :: < Vec < String > > ( )
271
- . join ( "\n " ) ;
285
+ . join ( "\n " ) ;
272
286
273
287
// Previous values in the enum
274
- let pred_values = values
288
+ let pred_values = original_values
275
289
. iter ( )
276
290
. enumerate ( )
277
291
. map ( |( i, v) | {
278
292
if i == 0 {
279
- format ! ( "{} -> Nothing", v )
293
+ format ! ( r#"# match @"{v}" Nothing"# )
280
294
} 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})"# )
282
297
}
283
298
} )
284
299
. collect :: < Vec < String > > ( )
285
- . join ( "\n " ) ;
300
+ . join ( "\n " ) ;
286
301
287
302
// Cardinality of the enum
288
303
let cardinality = values. len ( ) ;
@@ -291,48 +306,36 @@ type {name} = Variant"#
291
306
let to_enum = values
292
307
. iter ( )
293
308
. enumerate ( )
294
- . map ( |( i, v) | format ! ( "{} -> Just {}" , i , v ) )
309
+ . map ( |( i, v) | format ! ( "{i } -> Just {}" , camel_case ( v ) ) )
295
310
. collect :: < Vec < String > > ( )
296
311
. join ( "\n " ) ;
297
312
298
313
// Convert an enum value to the corresponding index
299
- let from_enum = values
314
+ let from_enum = original_values
300
315
. iter ( )
301
316
. enumerate ( )
302
- . map ( |( i, v) | format ! ( "{} -> {}" , v , i ) )
317
+ . map ( |( i, v) | format ! ( r#"# match @"{v}" {i}"# ) )
303
318
. 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 " ) ;
312
320
313
- let encode_json = values
321
+ let decode_json = original_values
314
322
. 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
+ } )
317
327
. collect :: < Vec < String > > ( )
318
328
. join ( "\n " ) ;
319
329
320
330
instances. push_str ( & format ! (
321
331
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} _
333
333
334
334
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}
336
339
337
340
instance Eq {name} where
338
341
eq = eq `on` show
@@ -343,40 +346,53 @@ instance Ord {name} where
343
346
instance GqlArgString {name} where
344
347
toGqlArgStringImpl = show
345
348
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
+
346
357
instance DecodeHasura {name} where
347
358
decodeHasura = decodeJson
348
359
349
360
instance EncodeHasura {name} where
350
361
encodeHasura = encodeJson
351
362
352
363
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
+ )
357
372
358
373
instance Bounded {name} where
359
- top = {last_value }
360
- bottom = {first_value }
374
+ bottom = {first_fn }
375
+ top = {last_fn }
361
376
362
377
instance BoundedEnum {name} where
363
378
cardinality = Cardinality {cardinality}
364
379
toEnum a = case a of
365
380
{to_enum}
366
381
_ -> Nothing
367
- fromEnum a = case a of
368
- {from_enum}
382
+ fromEnum = unwrap >>>
383
+ ( case_
384
+ {from_enum}
385
+ )
369
386
"#
370
387
) ) ;
371
388
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}" ) )
373
390
}
374
391
375
392
fn to_variant ( type_name : & str , name : & str ) -> String {
393
+ let fn_name = camel_case ( name) ;
376
394
format ! (
377
- r#"
378
- var :: {type_name}
379
- var = inj (Proxy @"{name}") unit
395
+ r#"{fn_name} = var @"{name}" :: {type_name}
380
396
"#
381
397
)
382
398
}
@@ -427,6 +443,66 @@ import Class.DecodeOa (class DecodeOa)"#;
427
443
428
444
const VARIANT_MODULE_IMPORTS : & str = r#"import Prelude
429
445
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(..))
431
449
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