Skip to content

Commit

Permalink
Fixing error message when a referenced fragment does not exist
Browse files Browse the repository at this point in the history
  • Loading branch information
nerdsupremacist committed Jan 4, 2021
1 parent c678f13 commit 0c2cfea
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 21 deletions.
91 changes: 71 additions & 20 deletions Sources/Graphaello/Errors/GraphQLFragmentResolverError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
enum GraphQLFragmentResolverError: Error, CustomStringConvertible {
case cannotInferFragmentType(Schema.GraphQLType)
case invalidTypeNameForFragment(String)
case failedToDecodeAnyOfTheStructsDueToPossibleRecursion([Struct<Stage.Validated>])
case failedToDecodeImportedFragments([Struct<Stage.Validated>], resolved: [Struct<Stage.Resolved>])
case cannotResolveFragmentOrQueryWithEmptyPath(Stage.Resolved.Path)
case cannotIncludeFragmentsInsideAQuery(GraphQLFragment)
case cannotDowncastQueryToType(Schema.GraphQLType)
Expand All @@ -15,13 +15,9 @@ enum GraphQLFragmentResolverError: Error, CustomStringConvertible {
return "Usage of GraphQL object type \(type.name) must have a selection of subfields. Please use a value from this object or map it to a Fragment."
case .invalidTypeNameForFragment(let name):
return "Failed to parse \(name) as a Fragment"
case .failedToDecodeAnyOfTheStructsDueToPossibleRecursion(let structs):
let recursion = structs.recursion()
return recursion.map { property in
return "\(property.code.location.locationDescription): error: Property \(property.name) is referencing a Fragment that cannot be used. This is either due to a complexer syntax, or a recursive cycle between fragments"
}
.joined(separator: "\n")

case .failedToDecodeImportedFragments(let structs, let resolved):
let errors = structs.resolutionErrors(resolved: resolved)
return errors.map(\.description).joined(separator: "\n")
case .cannotResolveFragmentOrQueryWithEmptyPath:
return "Query with Empty Paths are invalid"
case .cannotIncludeFragmentsInsideAQuery(let fragment):
Expand All @@ -34,11 +30,31 @@ enum GraphQLFragmentResolverError: Error, CustomStringConvertible {
}
}

private enum FragmentResolutionError {
case recursion(Property<Stage.Validated>)
case fragmentNotFound(Property<Stage.Validated>, referencing: StructResolution.ReferencedFragment)

var description: String {
switch self {
case .recursion(let property):
return "\(property.code.location.locationDescription): error: Property \(property.name) is referencing a Fragment that cannot be used. This is either due to a syntax that is too complex for Graphaello, or a recursive cycle between fragments"

case .fragmentNotFound(let property, .name(.fullName(let fullname))),
.fragmentNotFound(let property, .paging(.fullName(let fullname))):
return "\(property.code.location.locationDescription): error: Property \(property.name) is referencing a Fragment (\(fullname)) that could not be found. This is either due to a syntax that is too complex for Graphaello, or most likely because the Fragment no longer exists"

case .fragmentNotFound(let property, .name(.typealiasOnStruct(let structName, let typeName))),
.fragmentNotFound(let property, .paging(.typealiasOnStruct(let structName, let typeName))):
return "\(property.code.location.locationDescription): error: Property \(property.name) is referencing a Fragment of \(typeName) from \(structName) that could not be found. This is either due to a syntax that is too complex for Graphaello, or most likely because the Fragment no longer exists"
}
}
}

extension Sequence where Element == Struct<Stage.Validated> {

fileprivate func recursion() -> [Property<Stage.Validated>] {
fileprivate func resolutionErrors(resolved: [Struct<Stage.Resolved>]) -> [FragmentResolutionError] {
let structNames = Set(map { $0.name.lowercased() })
let fragmentNames = Set(
let fragmentsInSequence = Set(
flatMap { validated -> [String] in
let simpleDefinitionName = validated.name.replacingOccurrences(of: #"[\[\]\.\?]"#, with: "", options: .regularExpression)
return validated.properties.compactMap { property -> String? in
Expand All @@ -49,28 +65,63 @@ extension Sequence where Element == Struct<Stage.Validated> {
}
)

return flatMap { validated -> [Property<Stage.Validated>] in
return validated.properties.filter { property in
guard case .concrete(let type) = property.type, property.graphqlPath?.returnType.isFragment == true else { return false }
let resolvedFragments = Set(resolved.flatMap(\.fragments).map { $0.name.lowercased() })
let structsToFragments = Dictionary(resolved.map { ($0.name, Set($0.fragments.map { $0.name.lowercased() })) }) { $1 }

return flatMap { validated -> [FragmentResolutionError] in
return validated.properties.compactMap { property in
guard case .concrete(let type) = property.type, property.graphqlPath?.returnType.isFragment == true else { return nil }
do {
let fragment = try StructResolution.ReferencedFragment(typeName: type)
switch fragment {

case .name(.fullName(let name)):
return fragmentNames.contains(name.lowercased())
if fragmentsInSequence.contains(name.lowercased()) {
return .recursion(property)
}

if resolvedFragments.contains(name.lowercased()) {
return nil
}

return .fragmentNotFound(property, referencing: fragment)

case .paging(.fullName(let name)):
return fragmentNames.contains(name.lowercased())
if fragmentsInSequence.contains(name.lowercased()) {
return .recursion(property)
}

if resolvedFragments.contains(name.lowercased()) {
return nil
}

return .fragmentNotFound(property, referencing: fragment)

case .name(.typealiasOnStruct(let structName, let targetName)):
if structNames.contains(structName.lowercased()) {
return .recursion(property)
}

if let fragmentsForStruct = structsToFragments[structName], fragmentsForStruct.contains(targetName.lowercased()) {
return nil
}

return .fragmentNotFound(property, referencing: fragment)

case .paging(.typealiasOnStruct(let structName, let targetName)):
if structNames.contains(structName.lowercased()) {
return .recursion(property)
}

case .name(.typealiasOnStruct(let structName, _)):
return structNames.contains(structName.lowercased())
if let fragmentsForStruct = structsToFragments[structName], fragmentsForStruct.contains(targetName.lowercased()) {
return nil
}

case .paging(.typealiasOnStruct(let structName, _)):
return structNames.contains(structName.lowercased())
return .fragmentNotFound(property, referencing: fragment)

}
} catch {
return true
return nil
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ struct BasicResolver<SubResolver: StructResolver>: Resolver where SubResolver.Re

let missing = finalContext.failedDueToMissingFragment

guard missing.count < validated.count else { throw GraphQLFragmentResolverError.failedToDecodeAnyOfTheStructsDueToPossibleRecursion(missing) }
guard missing.count < validated.count else {
throw GraphQLFragmentResolverError.failedToDecodeImportedFragments(missing, resolved: finalContext.resolved)
}

return try resolve(validated: missing, using: finalContext.cleared())
}
Expand Down

0 comments on commit 0c2cfea

Please sign in to comment.