Skip to content

Commit 2d04c44

Browse files
committed
Adjust schema definition to contain canFail instead of semanticNonNull
1 parent 09d4d12 commit 2d04c44

7 files changed

Lines changed: 50 additions & 35 deletions

File tree

core/src/main/scala-2/caliban/schema/SchemaDerivation.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ trait CommonSchemaDerivation[R] {
6969
getName(p),
7070
getDescription(p),
7171
() =>
72-
if (p.typeclass.optional) p.typeclass.toType_(isInput, isSubscription)
72+
if (p.typeclass.optional || p.typeclass.canFail) p.typeclass.toType_(isInput, isSubscription)
7373
else p.typeclass.toType_(isInput, isSubscription).nonNull,
7474
p.annotations.collectFirst { case GQLDefault(v) => v },
7575
p.annotations.collectFirst { case GQLDeprecated(_) => () }.isDefined,
@@ -88,23 +88,24 @@ trait CommonSchemaDerivation[R] {
8888
ctx.parameters
8989
.filterNot(_.annotations.exists(_ == GQLExcluded()))
9090
.map { p =>
91-
val isOptional = {
91+
val (isNullable, isNullabilityForced) = {
9292
val hasNullableAnn = p.annotations.contains(GQLNullable())
9393
val hasNonNullAnn = p.annotations.contains(GQLNonNullable())
94-
!hasNonNullAnn && (hasNullableAnn || p.typeclass.optional)
94+
(!hasNonNullAnn && (hasNullableAnn || p.typeclass.optional), hasNullableAnn || hasNonNullAnn)
9595
}
9696
Types.makeField(
9797
getName(p),
9898
getDescription(p),
9999
p.typeclass.arguments,
100100
() =>
101-
if (isOptional) p.typeclass.toType_(isInput, isSubscription)
101+
if (isNullable || (!isNullabilityForced && schema.canFail))
102+
p.typeclass.toType_(isInput, isSubscription)
102103
else p.typeclass.toType_(isInput, isSubscription).nonNull,
103104
p.annotations.collectFirst { case GQLDeprecated(_) => () }.isDefined,
104105
p.annotations.collectFirst { case GQLDeprecated(reason) => reason },
105106
Option(
106107
p.annotations.collect { case GQLDirective(dir) => dir }.toList ++ {
107-
if (enableSemanticNonNull && isOptional && p.typeclass.semanticNonNull)
108+
if (enableSemanticNonNull && !isNullable && p.typeclass.canFail)
108109
Some(Directive("semanticNonNull"))
109110
else None
110111
}

core/src/main/scala-3/caliban/schema/DerivationUtils.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private object DerivationUtils {
105105
name,
106106
getDescription(fieldAnnotations),
107107
() =>
108-
if (schema.optional) schema.toType_(isInput, isSubscription)
108+
if (schema.optional || schema.canFail) schema.toType_(isInput, isSubscription)
109109
else schema.toType_(isInput, isSubscription).nonNull,
110110
getDefaultValue(fieldAnnotations),
111111
getDeprecatedReason(fieldAnnotations).isDefined,
@@ -126,24 +126,24 @@ private object DerivationUtils {
126126
Some(getName(annotations, info)),
127127
getDescription(annotations),
128128
fields.map { (name, fieldAnnotations, schema) =>
129-
val deprecatedReason = getDeprecatedReason(fieldAnnotations)
130-
val isOptional = {
129+
val deprecatedReason = getDeprecatedReason(fieldAnnotations)
130+
val (isNullable, isNullabilityForced) = {
131131
val hasNullableAnn = fieldAnnotations.contains(GQLNullable())
132132
val hasNonNullAnn = fieldAnnotations.contains(GQLNonNullable())
133-
!hasNonNullAnn && (hasNullableAnn || schema.optional)
133+
(!hasNonNullAnn && (hasNullableAnn || schema.optional), hasNullableAnn || hasNonNullAnn)
134134
}
135135
Types.makeField(
136136
name,
137137
getDescription(fieldAnnotations),
138138
schema.arguments,
139139
() =>
140-
if (isOptional) schema.toType_(isInput, isSubscription)
140+
if (isNullable || (!isNullabilityForced && schema.canFail)) schema.toType_(isInput, isSubscription)
141141
else schema.toType_(isInput, isSubscription).nonNull,
142142
deprecatedReason.isDefined,
143143
deprecatedReason,
144144
Option(
145145
getDirectives(fieldAnnotations) ++ {
146-
if (enableSemanticNonNull && isOptional && schema.semanticNonNull) Some(Directive("semanticNonNull"))
146+
if (enableSemanticNonNull && !isNullable && schema.canFail) Some(Directive("semanticNonNull"))
147147
else None
148148
}
149149
).filter(_.nonEmpty)

core/src/main/scala/caliban/schema/Schema.scala

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ trait Schema[-R, T] { self =>
7878
def optional: Boolean = false
7979

8080
/**
81-
* Defines if the type is considered semantically nullable or not.
81+
* Defines if the type can fail during resolution.
8282
*/
83-
def semanticNonNull: Boolean = false
83+
def canFail: Boolean = false
8484

8585
/**
8686
* Defined the arguments of the given type. Should be empty except for `Function`.
@@ -96,6 +96,7 @@ trait Schema[-R, T] { self =>
9696
*/
9797
def contramap[A](f: A => T): Schema[R, A] = new Schema[R, A] {
9898
override def optional: Boolean = self.optional
99+
override def canFail: Boolean = self.canFail
99100
override def arguments: List[__InputValue] = self.arguments
100101
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = self.toType_(isInput, isSubscription)
101102
override def resolve(value: A): Step[R] = self.resolve(f(value))
@@ -108,6 +109,7 @@ trait Schema[-R, T] { self =>
108109
*/
109110
def rename(name: String, inputName: Option[String] = None): Schema[R, T] = new Schema[R, T] {
110111
override def optional: Boolean = self.optional
112+
override def canFail: Boolean = self.canFail
111113
override def arguments: List[__InputValue] = self.arguments
112114
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
113115
val tpe = self.toType_(isInput, isSubscription)
@@ -360,7 +362,7 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
360362
implicit def listSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, List[A]] = new Schema[R0, List[A]] {
361363
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
362364
val t = ev.toType_(isInput, isSubscription)
363-
(if (ev.optional) t else t.nonNull).list
365+
(if (ev.optional || ev.canFail) t else t.nonNull).list
364366
}
365367

366368
override def resolve(value: List[A]): Step[R0] = ListStep(value.map(ev.resolve))
@@ -374,13 +376,15 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
374376
implicit def functionUnitSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, () => A] =
375377
new Schema[R0, () => A] {
376378
override def optional: Boolean = ev.optional
379+
override def canFail: Boolean = ev.canFail
377380
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
378381
override def resolve(value: () => A): Step[R0] = FunctionStep(_ => ev.resolve(value()))
379382
}
380383
implicit def metadataFunctionSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, Field => A] =
381384
new Schema[R0, Field => A] {
382385
override def arguments: List[__InputValue] = ev.arguments
383386
override def optional: Boolean = ev.optional
387+
override def canFail: Boolean = ev.canFail
384388
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
385389
override def resolve(value: Field => A): Step[R0] = MetadataFunctionStep(field => ev.resolve(value(field)))
386390
}
@@ -396,11 +400,13 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
396400

397401
implicit val leftSchema: Schema[RA, A] = new Schema[RA, A] {
398402
override def optional: Boolean = true
403+
override def canFail: Boolean = evA.canFail
399404
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = evA.toType_(isInput, isSubscription)
400405
override def resolve(value: A): Step[RA] = evA.resolve(value)
401406
}
402407
implicit val rightSchema: Schema[RB, B] = new Schema[RB, B] {
403408
override def optional: Boolean = true
409+
override def canFail: Boolean = evB.canFail
404410
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = evB.toType_(isInput, isSubscription)
405411
override def resolve(value: B): Step[RB] = evB.resolve(value)
406412
}
@@ -468,14 +474,15 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
468474
__InputValue(
469475
unwrappedArgumentName,
470476
None,
471-
() => if (ev1.optional) inputType else inputType.nonNull,
477+
() => if (ev1.optional || ev1.canFail) inputType else inputType.nonNull,
472478
None
473479
)
474480
)
475481
)
476482
}
477483

478484
override def optional: Boolean = ev2.optional
485+
override def canFail: Boolean = ev2.canFail
479486
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev2.toType_(isInput, isSubscription)
480487

481488
override def resolve(f: A => B): Step[RB] =
@@ -504,24 +511,25 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
504511
implicit def infallibleEffectSchema[R0, R1 >: R0, R2 >: R0, A](implicit ev: Schema[R2, A]): Schema[R0, URIO[R1, A]] =
505512
new Schema[R0, URIO[R1, A]] {
506513
override def optional: Boolean = ev.optional
514+
override def canFail: Boolean = ev.canFail
507515
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
508516
override def resolve(value: URIO[R1, A]): Step[R0] = QueryStep(ZQuery.fromZIONow(value.map(ev.resolve)))
509517
}
510518
implicit def effectSchema[R0, R1 >: R0, R2 >: R0, E <: Throwable, A](implicit
511519
ev: Schema[R2, A]
512520
): Schema[R0, ZIO[R1, E, A]] =
513521
new Schema[R0, ZIO[R1, E, A]] {
514-
override def optional: Boolean = true
515-
override def semanticNonNull: Boolean = !ev.optional
522+
override def optional: Boolean = ev.optional
523+
override def canFail: Boolean = true
516524
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
517525
override def resolve(value: ZIO[R1, E, A]): Step[R0] = QueryStep(ZQuery.fromZIONow(value.map(ev.resolve)))
518526
}
519527
def customErrorEffectSchema[R0, R1 >: R0, R2 >: R0, E, A](convertError: E => ExecutionError)(implicit
520528
ev: Schema[R2, A]
521529
): Schema[R0, ZIO[R1, E, A]] =
522530
new Schema[R0, ZIO[R1, E, A]] {
523-
override def optional: Boolean = true
524-
override def semanticNonNull: Boolean = !ev.optional
531+
override def optional: Boolean = ev.optional
532+
override def canFail: Boolean = true
525533
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
526534
override def resolve(value: ZIO[R1, E, A]): Step[R0] = QueryStep(
527535
ZQuery.fromZIONow(value.mapBoth(convertError, ev.resolve))
@@ -532,24 +540,25 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
532540
): Schema[R0, ZQuery[R1, Nothing, A]] =
533541
new Schema[R0, ZQuery[R1, Nothing, A]] {
534542
override def optional: Boolean = ev.optional
543+
override def canFail: Boolean = ev.canFail
535544
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
536545
override def resolve(value: ZQuery[R1, Nothing, A]): Step[R0] = QueryStep(value.map(ev.resolve))
537546
}
538547
implicit def querySchema[R0, R1 >: R0, R2 >: R0, E <: Throwable, A](implicit
539548
ev: Schema[R2, A]
540549
): Schema[R0, ZQuery[R1, E, A]] =
541550
new Schema[R0, ZQuery[R1, E, A]] {
542-
override def optional: Boolean = true
543-
override def semanticNonNull: Boolean = !ev.optional
551+
override def optional: Boolean = ev.optional
552+
override def canFail: Boolean = true
544553
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
545554
override def resolve(value: ZQuery[R1, E, A]): Step[R0] = QueryStep(value.map(ev.resolve))
546555
}
547556
def customErrorQuerySchema[R0, R1 >: R0, R2 >: R0, E, A](convertError: E => ExecutionError)(implicit
548557
ev: Schema[R2, A]
549558
): Schema[R0, ZQuery[R1, E, A]] =
550559
new Schema[R0, ZQuery[R1, E, A]] {
551-
override def optional: Boolean = true
552-
override def semanticNonNull: Boolean = !ev.optional
560+
override def optional: Boolean = ev.optional
561+
override def canFail: Boolean = true
553562
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
554563
override def resolve(value: ZQuery[R1, E, A]): Step[R0] = QueryStep(value.mapBoth(convertError, ev.resolve))
555564
}
@@ -558,33 +567,34 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
558567
): Schema[R1, ZStream[R1, Nothing, A]] =
559568
new Schema[R1, ZStream[R1, Nothing, A]] {
560569
override def optional: Boolean = false
570+
override def canFail: Boolean = ev.canFail
561571
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
562572
val t = ev.toType_(isInput, isSubscription)
563-
if (isSubscription) t else (if (ev.optional) t else t.nonNull).list
573+
if (isSubscription) t else (if (ev.optional || ev.canFail) t else t.nonNull).list
564574
}
565575
override def resolve(value: ZStream[R1, Nothing, A]): Step[R1] = StreamStep(value.map(ev.resolve))
566576
}
567577
implicit def streamSchema[R0, R1 >: R0, R2 >: R0, E <: Throwable, A](implicit
568578
ev: Schema[R2, A]
569579
): Schema[R0, ZStream[R1, E, A]] =
570580
new Schema[R0, ZStream[R1, E, A]] {
571-
override def optional: Boolean = true
572-
override def semanticNonNull: Boolean = !ev.optional
581+
override def optional: Boolean = ev.optional
582+
override def canFail: Boolean = true
573583
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
574584
val t = ev.toType_(isInput, isSubscription)
575-
if (isSubscription) t else (if (ev.optional) t else t.nonNull).list
585+
if (isSubscription) t else (if (ev.optional || ev.canFail) t else t.nonNull).list
576586
}
577587
override def resolve(value: ZStream[R1, E, A]): Step[R0] = StreamStep(value.map(ev.resolve))
578588
}
579589
def customErrorStreamSchema[R0, R1 >: R0, R2 >: R0, E, A](convertError: E => ExecutionError)(implicit
580590
ev: Schema[R2, A]
581591
): Schema[R0, ZStream[R1, E, A]] =
582592
new Schema[R0, ZStream[R1, E, A]] {
583-
override def optional: Boolean = true
584-
override def semanticNonNull: Boolean = !ev.optional
593+
override def optional: Boolean = ev.optional
594+
override def canFail: Boolean = true
585595
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = {
586596
val t = ev.toType_(isInput, isSubscription)
587-
if (isSubscription) t else (if (ev.optional) t else t.nonNull).list
597+
if (isSubscription) t else (if (ev.optional || ev.canFail) t else t.nonNull).list
588598
}
589599
override def resolve(value: ZStream[R1, E, A]): Step[R0] = StreamStep(value.mapBoth(convertError, ev.resolve))
590600
}
@@ -718,13 +728,13 @@ abstract class PartiallyAppliedFieldBase[V](
718728
description,
719729
_ => Nil,
720730
() =>
721-
if (ev.optional) ev.toType_(ft.isInput, ft.isSubscription)
731+
if (ev.optional || ev.canFail) ev.toType_(ft.isInput, ft.isSubscription)
722732
else ev.toType_(ft.isInput, ft.isSubscription).nonNull,
723733
isDeprecated = Directives.isDeprecated(directives),
724734
deprecationReason = Directives.deprecationReason(directives),
725735
directives = Some(
726736
directives.filter(_.name != "deprecated") ++ {
727-
if (enableSemanticNonNull && ev.optional && ev.semanticNonNull) Some(Directive("semanticNonNull"))
737+
if (enableSemanticNonNull && !ev.optional && ev.canFail) Some(Directive("semanticNonNull"))
728738
else None
729739
}
730740
).filter(_.nonEmpty)
@@ -768,13 +778,13 @@ case class PartiallyAppliedFieldWithArgs[V, A](
768778
description,
769779
ev1.arguments,
770780
() =>
771-
if (ev1.optional) ev1.toType_(fa.isInput, fa.isSubscription)
781+
if (ev1.optional || ev1.canFail) ev1.toType_(fa.isInput, fa.isSubscription)
772782
else ev1.toType_(fa.isInput, fa.isSubscription).nonNull,
773783
isDeprecated = Directives.isDeprecated(directives),
774784
deprecationReason = Directives.deprecationReason(directives),
775785
directives = Some(
776786
directives.filter(_.name != "deprecated") ++ {
777-
if (enableSemanticNonNull && ev1.optional && ev1.semanticNonNull) Some(Directive("semanticNonNull"))
787+
if (enableSemanticNonNull && !ev1.optional && ev1.canFail) Some(Directive("semanticNonNull"))
778788
else None
779789
}
780790
).filter(_.nonEmpty)

interop/cats/src/main/scala/caliban/interop/cats/CatsInterop.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ object CatsInterop {
192192

193193
override def optional: Boolean =
194194
ev.optional
195+
override def canFail: Boolean = true
195196

196197
override def resolve(value: F[A]): Step[R] =
197198
QueryStep(ZQuery.fromZIO(interop.fromEffect(value).map(ev.resolve)))

interop/cats/src/main/scala/caliban/interop/fs2/Fs2Interop.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ object Fs2Interop {
1515
ev.toType_(isInput, isSubscription)
1616

1717
override def optional: Boolean = ev.optional
18+
override def canFail: Boolean = true
1819

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

3132
override def optional: Boolean = ev.optional
33+
override def canFail: Boolean = true
3234

3335
override def resolve(value: Stream[F, A]): Step[R] =
3436
ev.resolve(value.translate(interop.fromEffectK))

interop/monix/src/main/scala/caliban/interop/monix/MonixInterop.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ object MonixInterop {
6161
new Schema[R, MonixTask[A]] {
6262
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)
6363
override def optional: Boolean = ev.optional
64+
override def canFail: Boolean = true
6465
override def resolve(value: MonixTask[A]): Step[R] =
6566
QueryStep(ZQuery.fromZIO(value.to[Task].map(ev.resolve)))
6667
}

interop/tapir/src/main/scala/caliban/interop/tapir/package.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ package object tapir {
106106
Types.makeField(
107107
extractPath(serverEndpoint.endpoint.info.name, serverEndpoint.endpoint.input),
108108
serverEndpoint.endpoint.info.description,
109-
getArgs(inputSchema.toType_(isInput = true), inputSchema.optional),
109+
getArgs(inputSchema.toType_(isInput = true), inputSchema.optional || inputSchema.canFail),
110110
() =>
111111
if (serverEndpoint.endpoint.errorOutput == EndpointOutput.Void[E]())
112112
outputSchema.toType_().nonNull

0 commit comments

Comments
 (0)