-
Notifications
You must be signed in to change notification settings - Fork 743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix RootEntityType and FulfilledFragment name generation #3045
Changes from all commits
8419ffb
2f77379
2f387c4
e6afd41
f6bb3f3
e407dad
5d80bfc
ed634db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,30 +62,87 @@ class IR { | |
/// Multiple `SelectionSet`s may select fields on the same `Entity`. All `SelectionSet`s that will | ||
/// be selected on the same object share the same `Entity`. | ||
class Entity { | ||
struct FieldPathComponent: Hashable { | ||
let name: String | ||
let type: GraphQLType | ||
|
||
/// Represents the location within a GraphQL definition (operation or fragment) of an `Entity`. | ||
struct Location: Hashable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to generate selection set names properly, an entity needs to be able to indicate where it is located in its definition. This includes needing to know what definition (operation or fragment) it is defined in. Previously, we were using a hacky solution of adding an initial
Representing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like this change, especially helping separate responsibilities between the IR and Templates |
||
enum SourceDefinition: Hashable { | ||
case operation(CompilationResult.OperationDefinition) | ||
case namedFragment(CompilationResult.FragmentDefinition) | ||
|
||
var rootType: GraphQLCompositeType { | ||
switch self { | ||
case let .operation(definition): return definition.rootType | ||
case let .namedFragment(definition): return definition.type | ||
} | ||
} | ||
} | ||
|
||
struct FieldComponent: Hashable { | ||
let name: String | ||
let type: GraphQLType | ||
} | ||
|
||
typealias FieldPath = LinkedList<FieldComponent> | ||
|
||
/// The operation or fragment definition that the entity belongs to. | ||
let source: SourceDefinition | ||
|
||
/// The path of fields from the root of the ``source`` definition to the entity. | ||
/// | ||
/// Example: | ||
/// For an operation: | ||
/// ```graphql | ||
/// query MyQuery { | ||
/// allAnimals { | ||
/// predators { | ||
/// height { | ||
/// ... | ||
/// } | ||
/// } | ||
/// } | ||
/// } | ||
/// ``` | ||
/// The `Height` entity would have a field path of [allAnimals, predators, height]. | ||
let fieldPath: FieldPath? | ||
|
||
func appending(_ fieldComponent: FieldComponent) -> Location { | ||
let fieldPath = self.fieldPath?.appending(fieldComponent) ?? LinkedList(fieldComponent) | ||
return Location(source: self.source, fieldPath: fieldPath) | ||
} | ||
|
||
func appending<C: Collection<FieldComponent>>(_ fieldComponents: C) -> Location { | ||
let fieldPath = self.fieldPath?.appending(fieldComponents) ?? LinkedList(fieldComponents) | ||
return Location(source: self.source, fieldPath: fieldPath) | ||
} | ||
|
||
static func +(lhs: IR.Entity.Location, rhs: FieldComponent) -> Location { | ||
lhs.appending(rhs) | ||
} | ||
} | ||
typealias FieldPath = LinkedList<FieldPathComponent> | ||
|
||
/// The selections that are selected for the entity across all type scopes in the operation. | ||
/// Represented as a tree. | ||
let selectionTree: EntitySelectionTree | ||
|
||
/// A list of path components indicating the path to the field containing the `Entity` in | ||
/// an operation or fragment. | ||
let fieldPath: FieldPath | ||
/// The location within a GraphQL definition (operation or fragment) where the `Entity` is | ||
/// located. | ||
let location: Location | ||
|
||
var rootTypePath: LinkedList<GraphQLCompositeType> { selectionTree.rootTypePath } | ||
|
||
var rootType: GraphQLCompositeType { rootTypePath.last.value } | ||
|
||
init(source: Location.SourceDefinition) { | ||
self.location = .init(source: source, fieldPath: nil) | ||
self.selectionTree = EntitySelectionTree(rootTypePath: LinkedList(source.rootType)) | ||
} | ||
|
||
init( | ||
rootTypePath: LinkedList<GraphQLCompositeType>, | ||
fieldPath: FieldPath | ||
location: Location, | ||
rootTypePath: LinkedList<GraphQLCompositeType> | ||
) { | ||
self.location = location | ||
self.selectionTree = EntitySelectionTree(rootTypePath: rootTypePath) | ||
self.fieldPath = fieldPath | ||
} | ||
} | ||
|
||
|
@@ -143,11 +200,11 @@ class IR { | |
let referencedFragments: OrderedSet<NamedFragment> | ||
|
||
/// All of the Entities that exist in the fragment's selection set, | ||
/// keyed by their relative response path within the fragment. | ||
/// keyed by their relative location (ie. path) within the fragment. | ||
/// | ||
/// - Note: The FieldPath for an entity within a fragment will begin with a path component | ||
/// with the fragment's name and type. | ||
let entities: [IR.Entity.FieldPath: IR.Entity] | ||
let entities: [IR.Entity.Location: IR.Entity] | ||
|
||
var name: String { definition.name } | ||
var type: GraphQLCompositeType { definition.type } | ||
|
@@ -156,7 +213,7 @@ class IR { | |
definition: CompilationResult.FragmentDefinition, | ||
rootField: EntityField, | ||
referencedFragments: OrderedSet<NamedFragment>, | ||
entities: [IR.Entity.FieldPath: IR.Entity] | ||
entities: [IR.Entity.Location: IR.Entity] | ||
) { | ||
self.definition = definition | ||
self.rootField = rootField | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,10 @@ public struct LinkedList<T>: ExpressibleByArrayLiteral { | |
self.value = value | ||
self.index = index | ||
} | ||
|
||
public var isHead: Bool { | ||
index == 0 | ||
} | ||
} | ||
|
||
final class HeadNode: Node { | ||
|
@@ -71,24 +75,25 @@ public struct LinkedList<T>: ExpressibleByArrayLiteral { | |
self.headNode = head | ||
} | ||
|
||
@_disfavoredOverload | ||
public init(_ headValue: T) { | ||
self.init(head: HeadNode(value: headValue)) | ||
} | ||
|
||
public init(array: [T]) { | ||
precondition(!array.isEmpty, "Cannot initialize LinkedList with an empty array. LinkedList must have at least one element.") | ||
var segments = array | ||
let headNode = HeadNode(value: segments.removeFirst()) | ||
public init<C: Collection<T>>(_ collection: C) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made this function take in a generic collection instead of only an array. This allows us to pass in another linked list or an ordered set. |
||
precondition(!collection.isEmpty, "Cannot initialize LinkedList with an empty collection. LinkedList must have at least one element.") | ||
var iterator = collection.makeIterator() | ||
let headNode = HeadNode(value: iterator.next()!) | ||
|
||
self.init(head: headNode) | ||
|
||
for segment in segments { | ||
while let segment = iterator.next() { | ||
append(segment) | ||
} | ||
} | ||
|
||
public init(arrayLiteral segments: T...) { | ||
self.init(array: segments) | ||
self.init(segments) | ||
} | ||
|
||
private func copy() -> Self { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There can never be 2 operations with the same name, as graphql-js would fail validation. So this is safe