Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions core/src/main/scala-2/caliban/schema/SchemaDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ import scala.language.experimental.macros

trait CommonSchemaDerivation[R] {

case class DerivationConfig(
/**
* Whether to enable the `SemanticNonNull` feature on derivation.
* It is currently disabled by default since it is not yet stable.
*/
enableSemanticNonNull: Boolean = false
)

/**
* Returns a configuration object that can be used to customize the derivation behavior.
*
* Override this method to customize the configuration.
*/
def config: DerivationConfig = DerivationConfig()

/**
* Default naming logic for input types.
* This is needed to avoid a name clash between a type used as an input and the same type used as an output.
Expand Down Expand Up @@ -80,21 +95,31 @@ trait CommonSchemaDerivation[R] {
ctx.parameters
.filterNot(_.annotations.exists(_ == GQLExcluded()))
.map { p =>
val isOptional = {
val (isNullable, isSemanticNonNull) = {
val hasNullableAnn = p.annotations.contains(GQLNullable())
val hasNonNullAnn = p.annotations.contains(GQLNonNullable())
!hasNonNullAnn && (hasNullableAnn || p.typeclass.optional)

if (hasNonNullAnn) (false, false)
else if (hasNullableAnn) (true, false)
else if (p.typeclass.optional) (true, !p.typeclass.nullable)
else (false, false)
}
Types.makeField(
getName(p),
getDescription(p),
p.typeclass.arguments,
() =>
if (isOptional) p.typeclass.toType_(isInput, isSubscription)
if (isNullable) p.typeclass.toType_(isInput, isSubscription)
else p.typeclass.toType_(isInput, isSubscription).nonNull,
p.annotations.collectFirst { case GQLDeprecated(_) => () }.isDefined,
p.annotations.collectFirst { case GQLDeprecated(reason) => reason },
Option(p.annotations.collect { case GQLDirective(dir) => dir }.toList).filter(_.nonEmpty)
Option(
p.annotations.collect { case GQLDirective(dir) => dir }.toList ++ {
if (config.enableSemanticNonNull && isSemanticNonNull)
Some(SchemaUtils.SemanticNonNull)
else None
}
).filter(_.nonEmpty)
)
}
.toList,
Expand Down
22 changes: 16 additions & 6 deletions core/src/main/scala-3/caliban/schema/DerivationUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,27 +120,37 @@ private object DerivationUtils {
def mkObject[R](
annotations: List[Any],
fields: List[(String, List[Any], Schema[R, Any])],
info: TypeInfo
info: TypeInfo,
enableSemanticNonNull: Boolean
)(isInput: Boolean, isSubscription: Boolean): __Type = makeObject(
Some(getName(annotations, info)),
getDescription(annotations),
fields.map { (name, fieldAnnotations, schema) =>
val deprecatedReason = getDeprecatedReason(fieldAnnotations)
val isOptional = {
val deprecatedReason = getDeprecatedReason(fieldAnnotations)
val (isNullable, isSemanticNonNull) = {
val hasNullableAnn = fieldAnnotations.contains(GQLNullable())
val hasNonNullAnn = fieldAnnotations.contains(GQLNonNullable())
!hasNonNullAnn && (hasNullableAnn || schema.optional)

if (hasNonNullAnn) (false, false)
else if (hasNullableAnn) (true, false)
else if (schema.optional) (true, !schema.nullable)
else (false, false)
}
Types.makeField(
name,
getDescription(fieldAnnotations),
schema.arguments,
() =>
if (isOptional) schema.toType_(isInput, isSubscription)
if (isNullable) schema.toType_(isInput, isSubscription)
else schema.toType_(isInput, isSubscription).nonNull,
deprecatedReason.isDefined,
deprecatedReason,
Option(getDirectives(fieldAnnotations)).filter(_.nonEmpty)
Option(
getDirectives(fieldAnnotations) ++ {
if (enableSemanticNonNull && isSemanticNonNull) Some(SchemaUtils.SemanticNonNull)
else None
}
).filter(_.nonEmpty)
)
},
getDirectives(annotations),
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala-3/caliban/schema/EnumValueSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import magnolia1.TypeInfo

final private class EnumValueSchema[R, A](
info: TypeInfo,
anns: List[Any]
anns: List[Any],
enableSemanticNonNull: Boolean
) extends Schema[R, A] {

def toType(isInput: Boolean, isSubscription: Boolean): __Type =
if (isInput) mkInputObject[R](anns, Nil, info)(isInput, isSubscription)
else mkObject[R](anns, Nil, info)(isInput, isSubscription)
else mkObject[R](anns, Nil, info, enableSemanticNonNull)(isInput, isSubscription)

private val step = PureStep(EnumValue(getName(anns, info)))
def resolve(value: A): Step[R] = step
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala-3/caliban/schema/ObjectSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ final private class ObjectSchema[R, A](
_methodFields: => List[(String, List[Any], Schema[R, ?])],
info: TypeInfo,
anns: List[Any],
paramAnnotations: Map[String, List[Any]]
paramAnnotations: Map[String, List[Any]],
enableSemanticNonNull: Boolean
)(using ct: ClassTag[A])
extends Schema[R, A] {

Expand Down Expand Up @@ -48,7 +49,7 @@ final private class ObjectSchema[R, A](
def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
val _ = resolver // Init the lazy val
if (isInput) mkInputObject[R](anns, fields.map(_._1), info)(isInput, isSubscription)
else mkObject[R](anns, fields.map(_._1), info)(isInput, isSubscription)
else mkObject[R](anns, fields.map(_._1), info, enableSemanticNonNull)(isInput, isSubscription)
}

def resolve(value: A): Step[R] = resolver.resolve(value)
Expand Down
21 changes: 19 additions & 2 deletions core/src/main/scala-3/caliban/schema/SchemaDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ object PrintDerived {
trait CommonSchemaDerivation {
export DerivationUtils.customizeInputTypeName

case class DerivationConfig(
/**
* Whether to enable the `SemanticNonNull` feature on derivation.
* It is currently disabled by default since it is not yet stable.
*/
enableSemanticNonNull: Boolean = false
)

/**
* Returns a configuration object that can be used to customize the derivation behavior.
*
* Override this method to customize the configuration.
*/
def config: DerivationConfig = DerivationConfig()

inline def recurseSum[R, P, Label, A <: Tuple](
inline types: List[(String, __Type, List[Any])] = Nil,
inline schemas: List[Schema[R, Any]] = Nil
Expand Down Expand Up @@ -95,7 +110,8 @@ trait CommonSchemaDerivation {
new EnumValueSchema[R, A](
MagnoliaMacro.typeInfo[A],
// Workaround until we figure out why the macro uses the parent's annotations when the leaf is a Scala 3 enum
inline if (!MagnoliaMacro.isEnum[A]) MagnoliaMacro.anns[A] else Nil
inline if (!MagnoliaMacro.isEnum[A]) MagnoliaMacro.anns[A] else Nil,
config.enableSemanticNonNull
)
case _ if Macros.hasAnnotation[A, GQLValueType] =>
new ValueTypeSchema[R, A](
Expand All @@ -109,7 +125,8 @@ trait CommonSchemaDerivation {
Macros.fieldsFromMethods[R, A],
MagnoliaMacro.typeInfo[A],
MagnoliaMacro.anns[A],
MagnoliaMacro.paramAnns[A].toMap
MagnoliaMacro.paramAnns[A].toMap,
config.enableSemanticNonNull
)(using summonInline[ClassTag[A]])
}

Expand Down
Loading