Skip to content

Commit

Permalink
only run once at top level
Browse files Browse the repository at this point in the history
  • Loading branch information
frekw committed Oct 14, 2021
1 parent 9d7ef13 commit 4b5124e
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 44 deletions.
2 changes: 1 addition & 1 deletion core/src/main/scala/caliban/validation/FieldMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object FieldMap {
}
}

def apply(context: Context, parentType: __Type, selectionSet: List[Selection]): FieldMap =
def apply(context: Context, parentType: __Type, selectionSet: Iterable[Selection]): FieldMap =
selectionSet.foldLeft(FieldMap.empty)({ case (fields, selection) =>
selection match {
case FragmentSpread(name, directives) =>
Expand Down
69 changes: 34 additions & 35 deletions core/src/main/scala/caliban/validation/FragmentValidator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,41 @@ object FragmentValidator {
selectionSet
)

(for {
sameResponseShapeByName_ <- IO.memoize((sameResponseShapeByName _).tupled).map(f => Function.untupled(f))
groupByCommonParents_ <- IO.memoize((groupByCommonParents _).tupled).map(f => Function.untupled(f))
sameForCommonParentsByName_ <-
IO.memoize((sameForCommonParentsByName(groupByCommonParents_) _).tupled).map(f => Function.untupled(f))
responseShape = sameResponseShapeByName_(context, parentType, fields)
commonParents = sameForCommonParentsByName_(context, parentType, fields)
conflicts <- responseShape zip commonParents
all = conflicts._1 ++ conflicts._2
_ <- IO.fromOption(all.headOption).flip.mapError(ValidationError(_, ""))
} yield ())
val conflicts = sameResponseShapeByName(context, parentType, selectionSet) ++
sameForCommonParentsByName(context, parentType, selectionSet)

conflicts match {
case head :: _ =>
IO.fail(ValidationError(head, ""))
case _ => IO.unit
}
}

def sameResponseShapeByName(context: Context, parentType: __Type, fields: FieldMap): UIO[Iterable[String]] =
UIO.succeed(fields.flatMap { case (name, values) =>
def sameResponseShapeByName(context: Context, parentType: __Type, set: Iterable[Selection]): Iterable[String] = {
val fields = FieldMap(context, parentType, set)
fields.flatMap { case (name, values) =>
cross(values).flatMap { pair =>
val (f1, f2) = pair
if (doTypesConflict(f1.fieldDef.`type`(), f2.fieldDef.`type`()))
if (doTypesConflict(f1.fieldDef.`type`(), f2.fieldDef.`type`())) {
List(
s"$name has conflicting types: ${f1.parentType.name.getOrElse("")}.${f1.fieldDef.name} and ${f2.parentType.name
.getOrElse("")}.${f2.fieldDef.name}. Try using an alias."
)
else List()
} else
sameResponseShapeByName(context, parentType, f1.selection.selectionSet ++ f2.selection.selectionSet)
}
}
}

def sameForCommonParentsByName(context: Context, parentType: __Type, set: Iterable[Selection]): Iterable[String] = {
val fields = FieldMap(context, parentType, set)
fields.flatMap({ case (name, fields) =>
groupByCommonParents(context, parentType, fields).flatMap { group =>
val merged = group.flatMap(_.selection.selectionSet)
requireSameNameAndArguments(group) ++ sameForCommonParentsByName(context, parentType, merged)
}
})
}

def doTypesConflict(t1: __Type, t2: __Type): Boolean =
if (isNonNull(t1))
Expand All @@ -56,24 +66,13 @@ object FragmentValidator {
else true
else if (isListType(t2))
true
else if (isLeafType(t1) && isLeafType(t2))
else if (isLeafType(t1) && isLeafType(t2)) {
t1.name != t2.name
else if (!isComposite(t1) || !isComposite(t2))
} else if (!isComposite(t1) || !isComposite(t2))
true
else
false

def sameForCommonParentsByName(
groupByCommonParents: (Context, __Type, Set[SelectedField]) => UIO[List[Set[SelectedField]]]
)(context: Context, parentType: __Type, fields: FieldMap): UIO[Iterable[String]] =
UIO
.collect(fields.toList)({ case (_, fields) =>
groupByCommonParents(context, parentType, fields).map { grouped =>
grouped.flatMap(group => requireSameNameAndArguments(group))
}
})
.map(_.flatten)

def requireSameNameAndArguments(fields: Set[SelectedField]) =
cross(fields).flatMap { case (f1, f2) =>
if (f1.fieldDef.name != f2.fieldDef.name) {
Expand All @@ -89,7 +88,7 @@ object FragmentValidator {
context: Context,
parentType: __Type,
fields: Set[SelectedField]
): UIO[List[Set[SelectedField]]] = {
): Iterable[Set[SelectedField]] = {
val abstractGroup = fields.collect({
case field if !isConcrete(field.parentType) => field
})
Expand All @@ -98,13 +97,13 @@ object FragmentValidator {
.collect({
case f if isConcrete(f.parentType) && f.parentType.name.isDefined => (f.parentType.name.get, f)
})
.foldLeft(Map.empty[String, Set[SelectedField]])({ case (acc, (name, field)) =>
val entry = acc.get(name).map(_ + field).getOrElse(Set(field))
acc + (name -> entry)
})
.foldLeft(Map.empty[String, Set[SelectedField]]) { case (acc, (name, field)) =>
val value = acc.get(name).map(_ + field).getOrElse(Set(field))
acc + (name -> value)
}

if (concreteGroups.size < 1) UIO.succeed(List(fields))
else UIO.succeed(concreteGroups.map({ case (_, v) => v ++ abstractGroup }).toList)
if (concreteGroups.size < 1) List(fields)
else concreteGroups.values.map(_ ++ abstractGroup)
}

def failValidation[T](msg: String, explanatoryText: String): IO[ValidationError, T] =
Expand Down
32 changes: 24 additions & 8 deletions core/src/main/scala/caliban/validation/Validator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -332,15 +332,35 @@ object Validator {
IO.foreach_(context.document.definitions) {
case OperationDefinition(opType, _, _, _, selectionSet) =>
opType match {
case OperationType.Query => validateFields(context, selectionSet, context.rootType.queryType)
case OperationType.Query =>
validateFields(context, selectionSet, context.rootType.queryType) *>
FragmentValidator.findConflictsWithinSelectionSet(
context,
context.rootType.queryType,
selectionSet
)
case OperationType.Mutation =>
context.rootType.mutationType.fold[IO[ValidationError, Unit]](
failValidation("Mutation operations are not supported on this schema.", "")
)(validateFields(context, selectionSet, _))
)(
validateFields(context, selectionSet, _) *>
FragmentValidator.findConflictsWithinSelectionSet(
context,
context.rootType.queryType,
selectionSet
)
)
case OperationType.Subscription =>
context.rootType.subscriptionType.fold[IO[ValidationError, Unit]](
failValidation("Subscription operations are not supported on this schema.", "")
)(validateFields(context, selectionSet, _))
)(
validateFields(context, selectionSet, _) *>
FragmentValidator.findConflictsWithinSelectionSet(
context,
context.rootType.queryType,
selectionSet
)
)
}
case _: FragmentDefinition => IO.unit
case _: TypeSystemDefinition => IO.unit
Expand All @@ -366,11 +386,7 @@ object Validator {
}
case InlineFragment(typeCondition, _, selectionSet) =>
validateSpread(context, None, currentType, typeCondition, selectionSet)
} *> validateLeafFieldSelection(selectionSet, currentType) *> FragmentValidator.findConflictsWithinSelectionSet(
context,
currentType,
selectionSet
)
} *> validateLeafFieldSelection(selectionSet, currentType)

private def validateSpread(
context: Context,
Expand Down

0 comments on commit 4b5124e

Please sign in to comment.