Skip to content

Commit

Permalink
Adjust schema definition to contain canFail instead of semanticNonNull
Browse files Browse the repository at this point in the history
  • Loading branch information
XiNiHa committed Apr 9, 2024
1 parent 09d4d12 commit a0f82da
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 35 deletions.
11 changes: 6 additions & 5 deletions core/src/main/scala-2/caliban/schema/SchemaDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ trait CommonSchemaDerivation[R] {
getName(p),
getDescription(p),
() =>
if (p.typeclass.optional) p.typeclass.toType_(isInput, isSubscription)
if (p.typeclass.optional || p.typeclass.canFail) p.typeclass.toType_(isInput, isSubscription)
else p.typeclass.toType_(isInput, isSubscription).nonNull,
p.annotations.collectFirst { case GQLDefault(v) => v },
p.annotations.collectFirst { case GQLDeprecated(_) => () }.isDefined,
Expand All @@ -88,23 +88,24 @@ trait CommonSchemaDerivation[R] {
ctx.parameters
.filterNot(_.annotations.exists(_ == GQLExcluded()))
.map { p =>
val isOptional = {
val (isNullable, isNullabilityForced) = {
val hasNullableAnn = p.annotations.contains(GQLNullable())
val hasNonNullAnn = p.annotations.contains(GQLNonNullable())
!hasNonNullAnn && (hasNullableAnn || p.typeclass.optional)
(!hasNonNullAnn && (hasNullableAnn || p.typeclass.optional), hasNullableAnn || hasNonNullAnn)
}
Types.makeField(
getName(p),
getDescription(p),
p.typeclass.arguments,
() =>
if (isOptional) p.typeclass.toType_(isInput, isSubscription)
if (isNullable || (!isNullabilityForced && p.typeclass.canFail))
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 ++ {
if (enableSemanticNonNull && isOptional && p.typeclass.semanticNonNull)
if (enableSemanticNonNull && !isNullable && p.typeclass.canFail)
Some(Directive("semanticNonNull"))
else None
}
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/scala-3/caliban/schema/DerivationUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private object DerivationUtils {
name,
getDescription(fieldAnnotations),
() =>
if (schema.optional) schema.toType_(isInput, isSubscription)
if (schema.optional || schema.canFail) schema.toType_(isInput, isSubscription)
else schema.toType_(isInput, isSubscription).nonNull,
getDefaultValue(fieldAnnotations),
getDeprecatedReason(fieldAnnotations).isDefined,
Expand All @@ -126,24 +126,24 @@ private object DerivationUtils {
Some(getName(annotations, info)),
getDescription(annotations),
fields.map { (name, fieldAnnotations, schema) =>
val deprecatedReason = getDeprecatedReason(fieldAnnotations)
val isOptional = {
val deprecatedReason = getDeprecatedReason(fieldAnnotations)
val (isNullable, isNullabilityForced) = {
val hasNullableAnn = fieldAnnotations.contains(GQLNullable())
val hasNonNullAnn = fieldAnnotations.contains(GQLNonNullable())
!hasNonNullAnn && (hasNullableAnn || schema.optional)
(!hasNonNullAnn && (hasNullableAnn || schema.optional), hasNullableAnn || hasNonNullAnn)
}
Types.makeField(
name,
getDescription(fieldAnnotations),
schema.arguments,
() =>
if (isOptional) schema.toType_(isInput, isSubscription)
if (isNullable || (!isNullabilityForced && schema.canFail)) schema.toType_(isInput, isSubscription)
else schema.toType_(isInput, isSubscription).nonNull,
deprecatedReason.isDefined,
deprecatedReason,
Option(
getDirectives(fieldAnnotations) ++ {
if (enableSemanticNonNull && isOptional && schema.semanticNonNull) Some(Directive("semanticNonNull"))
if (enableSemanticNonNull && !isNullable && schema.canFail) Some(Directive("semanticNonNull"))
else None
}
).filter(_.nonEmpty)
Expand Down
56 changes: 33 additions & 23 deletions core/src/main/scala/caliban/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ trait Schema[-R, T] { self =>
def optional: Boolean = false

/**
* Defines if the type is considered semantically nullable or not.
* Defines if the type can fail during resolution.
*/
def semanticNonNull: Boolean = false
def canFail: Boolean = false

/**
* Defined the arguments of the given type. Should be empty except for `Function`.
Expand All @@ -96,6 +96,7 @@ trait Schema[-R, T] { self =>
*/
def contramap[A](f: A => T): Schema[R, A] = new Schema[R, A] {
override def optional: Boolean = self.optional
override def canFail: Boolean = self.canFail
override def arguments: List[__InputValue] = self.arguments
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = self.toType_(isInput, isSubscription)
override def resolve(value: A): Step[R] = self.resolve(f(value))
Expand All @@ -108,6 +109,7 @@ trait Schema[-R, T] { self =>
*/
def rename(name: String, inputName: Option[String] = None): Schema[R, T] = new Schema[R, T] {
override def optional: Boolean = self.optional
override def canFail: Boolean = self.canFail
override def arguments: List[__InputValue] = self.arguments
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
val tpe = self.toType_(isInput, isSubscription)
Expand Down Expand Up @@ -360,7 +362,7 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
implicit def listSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, List[A]] = new Schema[R0, List[A]] {
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
val t = ev.toType_(isInput, isSubscription)
(if (ev.optional) t else t.nonNull).list
(if (ev.optional || ev.canFail) t else t.nonNull).list
}

override def resolve(value: List[A]): Step[R0] = ListStep(value.map(ev.resolve))
Expand All @@ -374,13 +376,15 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
implicit def functionUnitSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, () => A] =
new Schema[R0, () => A] {
override def optional: Boolean = ev.optional
override def canFail: Boolean = ev.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: () => A): Step[R0] = FunctionStep(_ => ev.resolve(value()))
}
implicit def metadataFunctionSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, Field => A] =
new Schema[R0, Field => A] {
override def arguments: List[__InputValue] = ev.arguments
override def optional: Boolean = ev.optional
override def canFail: Boolean = ev.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: Field => A): Step[R0] = MetadataFunctionStep(field => ev.resolve(value(field)))
}
Expand All @@ -396,11 +400,13 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {

implicit val leftSchema: Schema[RA, A] = new Schema[RA, A] {
override def optional: Boolean = true
override def canFail: Boolean = evA.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = evA.toType_(isInput, isSubscription)
override def resolve(value: A): Step[RA] = evA.resolve(value)
}
implicit val rightSchema: Schema[RB, B] = new Schema[RB, B] {
override def optional: Boolean = true
override def canFail: Boolean = evB.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = evB.toType_(isInput, isSubscription)
override def resolve(value: B): Step[RB] = evB.resolve(value)
}
Expand Down Expand Up @@ -468,14 +474,15 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
__InputValue(
unwrappedArgumentName,
None,
() => if (ev1.optional) inputType else inputType.nonNull,
() => if (ev1.optional || ev1.canFail) inputType else inputType.nonNull,
None
)
)
)
}

override def optional: Boolean = ev2.optional
override def canFail: Boolean = ev2.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev2.toType_(isInput, isSubscription)

override def resolve(f: A => B): Step[RB] =
Expand Down Expand Up @@ -504,24 +511,25 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
implicit def infallibleEffectSchema[R0, R1 >: R0, R2 >: R0, A](implicit ev: Schema[R2, A]): Schema[R0, URIO[R1, A]] =
new Schema[R0, URIO[R1, A]] {
override def optional: Boolean = ev.optional
override def canFail: Boolean = ev.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: URIO[R1, A]): Step[R0] = QueryStep(ZQuery.fromZIONow(value.map(ev.resolve)))
}
implicit def effectSchema[R0, R1 >: R0, R2 >: R0, E <: Throwable, A](implicit
ev: Schema[R2, A]
): Schema[R0, ZIO[R1, E, A]] =
new Schema[R0, ZIO[R1, E, A]] {
override def optional: Boolean = true
override def semanticNonNull: Boolean = !ev.optional
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: ZIO[R1, E, A]): Step[R0] = QueryStep(ZQuery.fromZIONow(value.map(ev.resolve)))
}
def customErrorEffectSchema[R0, R1 >: R0, R2 >: R0, E, A](convertError: E => ExecutionError)(implicit
ev: Schema[R2, A]
): Schema[R0, ZIO[R1, E, A]] =
new Schema[R0, ZIO[R1, E, A]] {
override def optional: Boolean = true
override def semanticNonNull: Boolean = !ev.optional
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: ZIO[R1, E, A]): Step[R0] = QueryStep(
ZQuery.fromZIONow(value.mapBoth(convertError, ev.resolve))
Expand All @@ -532,24 +540,25 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
): Schema[R0, ZQuery[R1, Nothing, A]] =
new Schema[R0, ZQuery[R1, Nothing, A]] {
override def optional: Boolean = ev.optional
override def canFail: Boolean = ev.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: ZQuery[R1, Nothing, A]): Step[R0] = QueryStep(value.map(ev.resolve))
}
implicit def querySchema[R0, R1 >: R0, R2 >: R0, E <: Throwable, A](implicit
ev: Schema[R2, A]
): Schema[R0, ZQuery[R1, E, A]] =
new Schema[R0, ZQuery[R1, E, A]] {
override def optional: Boolean = true
override def semanticNonNull: Boolean = !ev.optional
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: ZQuery[R1, E, A]): Step[R0] = QueryStep(value.map(ev.resolve))
}
def customErrorQuerySchema[R0, R1 >: R0, R2 >: R0, E, A](convertError: E => ExecutionError)(implicit
ev: Schema[R2, A]
): Schema[R0, ZQuery[R1, E, A]] =
new Schema[R0, ZQuery[R1, E, A]] {
override def optional: Boolean = true
override def semanticNonNull: Boolean = !ev.optional
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def resolve(value: ZQuery[R1, E, A]): Step[R0] = QueryStep(value.mapBoth(convertError, ev.resolve))
}
Expand All @@ -558,33 +567,34 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
): Schema[R1, ZStream[R1, Nothing, A]] =
new Schema[R1, ZStream[R1, Nothing, A]] {
override def optional: Boolean = false
override def canFail: Boolean = ev.canFail
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
val t = ev.toType_(isInput, isSubscription)
if (isSubscription) t else (if (ev.optional) t else t.nonNull).list
if (isSubscription) t else (if (ev.optional || ev.canFail) t else t.nonNull).list
}
override def resolve(value: ZStream[R1, Nothing, A]): Step[R1] = StreamStep(value.map(ev.resolve))
}
implicit def streamSchema[R0, R1 >: R0, R2 >: R0, E <: Throwable, A](implicit
ev: Schema[R2, A]
): Schema[R0, ZStream[R1, E, A]] =
new Schema[R0, ZStream[R1, E, A]] {
override def optional: Boolean = true
override def semanticNonNull: Boolean = !ev.optional
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
val t = ev.toType_(isInput, isSubscription)
if (isSubscription) t else (if (ev.optional) t else t.nonNull).list
if (isSubscription) t else (if (ev.optional || ev.canFail) t else t.nonNull).list
}
override def resolve(value: ZStream[R1, E, A]): Step[R0] = StreamStep(value.map(ev.resolve))
}
def customErrorStreamSchema[R0, R1 >: R0, R2 >: R0, E, A](convertError: E => ExecutionError)(implicit
ev: Schema[R2, A]
): Schema[R0, ZStream[R1, E, A]] =
new Schema[R0, ZStream[R1, E, A]] {
override def optional: Boolean = true
override def semanticNonNull: Boolean = !ev.optional
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
val t = ev.toType_(isInput, isSubscription)
if (isSubscription) t else (if (ev.optional) t else t.nonNull).list
if (isSubscription) t else (if (ev.optional || ev.canFail) t else t.nonNull).list
}
override def resolve(value: ZStream[R1, E, A]): Step[R0] = StreamStep(value.mapBoth(convertError, ev.resolve))
}
Expand Down Expand Up @@ -718,13 +728,13 @@ abstract class PartiallyAppliedFieldBase[V](
description,
_ => Nil,
() =>
if (ev.optional) ev.toType_(ft.isInput, ft.isSubscription)
if (ev.optional || ev.canFail) ev.toType_(ft.isInput, ft.isSubscription)
else ev.toType_(ft.isInput, ft.isSubscription).nonNull,
isDeprecated = Directives.isDeprecated(directives),
deprecationReason = Directives.deprecationReason(directives),
directives = Some(
directives.filter(_.name != "deprecated") ++ {
if (enableSemanticNonNull && ev.optional && ev.semanticNonNull) Some(Directive("semanticNonNull"))
if (enableSemanticNonNull && !ev.optional && ev.canFail) Some(Directive("semanticNonNull"))
else None
}
).filter(_.nonEmpty)
Expand Down Expand Up @@ -768,13 +778,13 @@ case class PartiallyAppliedFieldWithArgs[V, A](
description,
ev1.arguments,
() =>
if (ev1.optional) ev1.toType_(fa.isInput, fa.isSubscription)
if (ev1.optional || ev1.canFail) ev1.toType_(fa.isInput, fa.isSubscription)
else ev1.toType_(fa.isInput, fa.isSubscription).nonNull,
isDeprecated = Directives.isDeprecated(directives),
deprecationReason = Directives.deprecationReason(directives),
directives = Some(
directives.filter(_.name != "deprecated") ++ {
if (enableSemanticNonNull && ev1.optional && ev1.semanticNonNull) Some(Directive("semanticNonNull"))
if (enableSemanticNonNull && !ev1.optional && ev1.canFail) Some(Directive("semanticNonNull"))
else None
}
).filter(_.nonEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ object CatsInterop {

override def optional: Boolean =
ev.optional
override def canFail: Boolean = true

override def resolve(value: F[A]): Step[R] =
QueryStep(ZQuery.fromZIO(interop.fromEffect(value).map(ev.resolve)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ object Fs2Interop {
ev.toType_(isInput, isSubscription)

override def optional: Boolean = ev.optional
override def canFail: Boolean = true

override def resolve(value: Stream[RIO[R, *], A]): Step[R] =
ev.resolve(value.toZStream())
Expand All @@ -29,6 +30,7 @@ object Fs2Interop {
ev.toType_(isInput, isSubscription)

override def optional: Boolean = ev.optional
override def canFail: Boolean = true

override def resolve(value: Stream[F, A]): Step[R] =
ev.resolve(value.translate(interop.fromEffectK))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ object MonixInterop {
new Schema[R, MonixTask[A]] {
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
override def optional: Boolean = ev.optional
override def canFail: Boolean = true
override def resolve(value: MonixTask[A]): Step[R] =
QueryStep(ZQuery.fromZIO(value.to[Task].map(ev.resolve)))
}
Expand Down
Loading

0 comments on commit a0f82da

Please sign in to comment.