1
+ package caliban .schema
2
+
3
+ import caliban .introspection .adt .__Type
4
+
5
+ import scala .quoted .*
6
+
7
+ object TypeUnionDerivation {
8
+ inline def derived [R , T ]: Schema [R , T ] = $ { typeUnionSchema[R , T ] }
9
+
10
+ def typeUnionSchema [R : Type , T : Type ](using quotes : Quotes ): Expr [Schema [R , T ]] = {
11
+ import quotes .reflect .*
12
+
13
+ class TypeAndSchema [A ](val typeRef : String , val schema : Expr [Schema [R , A ]], val tpe : Type [A ])
14
+
15
+ def rec [A ](using tpe : Type [A ]): List [TypeAndSchema [? ]] =
16
+ TypeRepr .of(using tpe).dealias match {
17
+ case OrType (l, r) =>
18
+ rec(using l.asType.asInstanceOf [Type [Any ]]) ++ rec(using r.asType.asInstanceOf [Type [Any ]])
19
+ case otherRepr =>
20
+ val otherString : String = otherRepr.show
21
+ val expr : TypeAndSchema [A ] =
22
+ Expr .summon[Schema [R , A ]] match {
23
+ case Some (foundSchema) =>
24
+ TypeAndSchema [A ](otherString, foundSchema, otherRepr.asType.asInstanceOf [Type [A ]])
25
+ case None =>
26
+ quotes.reflect.report.errorAndAbort(s " Couldn't resolve Schema[Any, $otherString] " )
27
+ }
28
+
29
+ List (expr)
30
+ }
31
+
32
+ val typeAndSchemas : List [TypeAndSchema [? ]] = rec[T ]
33
+
34
+ val schemaByTypeNameList : Expr [List [(String , Schema [R , Any ])]] = Expr .ofList(
35
+ typeAndSchemas.map { case (tas : TypeAndSchema [a]) =>
36
+ given Type [a] = tas.tpe
37
+ ' { ($ { Expr (tas.typeRef) }, $ { tas.schema }.asInstanceOf [Schema [R , Any ]]) }
38
+ }
39
+ )
40
+ val name = TypeRepr .of[T ].show
41
+
42
+ if (name.contains(" |" )) {
43
+ report.error(s " You must explicitly add type parameter to derive Schema for a union type in order to capture the name of the type alias " )
44
+ }
45
+
46
+ val ret = ' {
47
+ val schemaByName : Map [String , Schema [R , Any ]] = $ { schemaByTypeNameList }.toMap
48
+ new Schema [R , T ] {
49
+
50
+ def resolve (value : T ): Step [R ] = {
51
+ var ret : Step [R ] = null
52
+ $ {
53
+ Expr .block(
54
+ typeAndSchemas.map { case (tas : TypeAndSchema [a]) =>
55
+ given Type [a] = tas.tpe
56
+ ' { if value.isInstanceOf [a] then ret = schemaByName($ { Expr (tas.typeRef) }).resolve(value) }
57
+ },
58
+ ' { require(ret != null , s " no schema for ${value}" ) }
59
+ )
60
+ }
61
+ ret
62
+ }
63
+
64
+ def toType (isInput : Boolean , isSubscription : Boolean ): __Type =
65
+ Types .makeUnion(Some ($ { Expr (name) }), None , schemaByName.values.map(_.toType_(isInput, isSubscription)).toList)
66
+ }
67
+ }
68
+ // quotes.reflect.report.warning(ret.show)
69
+ ret
70
+ }
71
+ }
0 commit comments