Skip to content

Commit

Permalink
Perfecting support for inferred types
Browse files Browse the repository at this point in the history
  • Loading branch information
nerdsupremacist committed Sep 6, 2020
1 parent a33a669 commit 23127b4
Show file tree
Hide file tree
Showing 17 changed files with 79 additions and 34 deletions.
4 changes: 2 additions & 2 deletions .swiftpm/xcode/xcshareddata/xcschemes/Graphaello.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "codegen --project /Users/mathiasquintero/CovidUI --apollo derivedData"
argument = "codegen --project /Users/mathiasquintero/Projects/MindfulEating/iOS/app --apollo derivedData"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "BUILD_ROOT"
value = "/Users/mathiasquintero/Library/Developer/Xcode/DerivedData/CovidUI-cjkfjxbxqnaapgglporvyzdgduwo/Build/Products"
value = "/Users/mathiasquintero/Library/Developer/Xcode/DerivedData/MindfulEating-akqgqpdhznbxrxebrmxqsqvkqovi/Build/Products"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
Expand Down
7 changes: 5 additions & 2 deletions Sources/Graphaello/Errors/GraphQLFragmentResolverError.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

enum GraphQLFragmentResolverError: Error, CustomStringConvertible {
case cannotInferFragmentType(Schema.GraphQLType)
case invalidTypeNameForFragment(String)
case failedToDecodeAnyOfTheStructsDueToPossibleRecursion([Struct<Stage.Validated>])
case cannotResolveFragmentOrQueryWithEmptyPath(Stage.Resolved.Path)
Expand All @@ -10,6 +11,8 @@ enum GraphQLFragmentResolverError: Error, CustomStringConvertible {

var description: String {
switch self {
case .cannotInferFragmentType(let type):
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):
Expand Down Expand Up @@ -48,9 +51,9 @@ extension Sequence where Element == Struct<Stage.Validated> {

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

case .name(.fullName(let name)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ extension Struct where CurrentStage == Stage.Prepared {

var initializerArguments: [InitializerArgument] {
let stockArguments = properties
.filter { $0.graphqlPath?.resolved.isConnection ?? true }
.map { InitializerArgument(name: $0.name,
type: $0.type.contains("->") ? "@escaping \($0.type)" : $0.type) }
.compactMap { property -> InitializerArgument? in
if let path = property.graphqlPath, !path.resolved.isConnection {
return nil
}

guard case .concrete(let type) = property.type else { return nil }
return InitializerArgument(name: property.name,
type: type.contains("->") ? "@escaping \(type)" : type)
}

let extraAPIArguments = additionalReferencedAPIs.filter { $0.property == nil }.map { InitializerArgument(name: $0.api.name.camelized, type: $0.api.name) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ extension Struct where CurrentStage == Stage.Prepared {
var initializerArgumentAssignmentFromQueryData: [InitializerArgumentAssignmentFromQueryData] {
let stockArguments = properties
.filter { $0.graphqlPath?.resolved.isConnection ?? true }
.filter { property in
guard case .concrete = property.type else { return false }
return true
}
.map { InitializerArgumentAssignmentFromQueryData(name: $0.name,
expression: $0.expression(in: self)) }

Expand All @@ -29,8 +33,12 @@ extension Struct where CurrentStage == Stage.Prepared {
extension Property where CurrentStage == Stage.Prepared {

fileprivate func expression(in graphQlStruct: Struct<Stage.Prepared>) -> CodeTransformable {
guard type != graphQlStruct.query?.api.name else { return "self" }
guard let path = graphqlPath else { return name }
guard let path = graphqlPath else {
if case .concrete(let type) = type, type == graphQlStruct.query?.api.name {
return "self"
}
return name
}
guard case .some(.connection(let connectionFragment)) = path.resolved.referencedFragment else {
fatalError("Invalid State: should not attempt to get an expression from a regular GraphQL Value. Only from connections.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ struct InitializerValueAssignment: SwiftCodeTransformable {
extension Struct where CurrentStage == Stage.Prepared {

var initializerValueAssignments: [InitializerValueAssignment] {
return properties.map { property in
return properties.compactMap { property in
switch property.graphqlPath {
case .some(let path):
let expression = path.initializerExpression(in: self) ?? property.name
return InitializerValueAssignment(name: property.name,
expression: "GraphQL(\(expression))")
case .none:
guard case .concrete = property.type else { return nil }
return InitializerValueAssignment(name: property.name, expression: property.name)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ extension GraphQLMutation {
extension Property where CurrentStage: GraphQLStage {

fileprivate var directArgument: QueryRendererArgument? {
guard graphqlPath == nil else { return nil }
guard graphqlPath == nil, case .concrete(let type) = type else { return nil }
return QueryRendererArgument(name: name,
type: type.contains("->") ? "@escaping \(type)" : type,
expression: nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ struct GraphQLMutation {
let api: API
let target: Schema.GraphQLType
let name: String
let parentName: String
let path: Stage.Resolved.Path
let returnType: Schema.GraphQLType.Field.TypeReference
let object: GraphQLObject
Expand Down
9 changes: 7 additions & 2 deletions Sources/Graphaello/Processing/Model/Struct/Property.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import Foundation

enum PropertyType: Hashable {
case concrete(String)
case inferred
}

struct Property<CurrentStage: StageProtocol> {
let code: SourceCode
let name: String
let type: String
let type: PropertyType
let context: Context

init(code: SourceCode, name: String, type: String, @ContextBuilder context: () throws -> ContextProtocol) rethrows {
init(code: SourceCode, name: String, type: PropertyType, @ContextBuilder context: () throws -> ContextProtocol) rethrows {
self.code = code
self.name = name
self.type = type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ struct BasicPropertyExtractor: PropertyExtractor {
let attributeExtractor: AttributeExtractor

func extract(code: SourceCode) throws -> Property<Stage.Extracted>? {
guard let type = try code.optional(decode: { try $0.typeName() }) else { return nil }
let type = try code.optional(decode: { try $0.typeName() })
let attributes = try code
.optional { try $0.attributes() }?
.map { try attributeExtractor.extract(code: $0) } ?? []

let finalType = attributes
.filter { $0.kind == ._custom }
.map { String($0.code.content.dropFirst()) }
.first { specialAttributes.contains($0) }
.map { "\($0)<\(type)>" } ?? type
let finalType = type.map { type in
attributes
.filter { $0.kind == ._custom }
.map { String($0.code.content.dropFirst()) }
.first { specialAttributes.contains($0) }
.map { "\($0)<\(type)>" } ?? type
}

return Property(code: code,
name: try code.name(),
type: finalType) {
type: finalType.map { .concrete($0) } ?? .inferred) {

.attributes ~> attributes
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,22 @@ struct ResolvedPropertyCollector<Collector: ResolvedValueCollector>: ResolvedVal
case .mutation:
let object = collectedPath.object(propertyName: value.name)
// TODO: Use SwiftSyntax here to get to the proper name for the mutation
let name = value
.type
.replacingOccurrences(of: #"<.*>"#, with: "", options: .regularExpression)
.replacingOccurrences(of: #"[\[\]\.\?]"#, with: "", options: .regularExpression)
let name: String
switch value.type {
case .concrete(let type):
name = type
.replacingOccurrences(of: #"<.*>"#, with: "", options: .regularExpression)
.replacingOccurrences(of: #"[\[\]\.\?]"#, with: "", options: .regularExpression)
case .inferred:
var hasher = Hasher.constantAccrossExecutions()
path.validated.parsed.extracted.code.content.hash(into: &hasher)
name = "Inferred\(hasher.finalize())"
}

let mutation = GraphQLMutation(api: path.validated.api,
target: path.validated.target,
name: name,
parentName: parent.name,
path: path,
returnType: path.validated.returnType,
object: object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ struct PathResolver: ValueResolver {
guard value.components.last?.parsed == .fragment else {
return .resolved(.init(validated: value, referencedFragment: nil, isReferencedFragmentASingleFragmentStruct: false))
}


guard case .concrete(let type) = property.type else {
throw GraphQLFragmentResolverError.cannotInferFragmentType(value.components.last!.underlyingType)
}

if case .mutation = value.parsed.target {
let fragmentName = try StructResolution.ReferencedMutationResultFragment(typeName: property.type).fragmentName
let fragmentName = try StructResolution.ReferencedMutationResultFragment(typeName: type).fragmentName
guard let result = context[fragmentName] else { return .missingFragment }
return .resolved(Stage.Resolved.Path(validated: value, referencedFragment: .mutationResult(result.fragment), isReferencedFragmentASingleFragmentStruct: result.isReferencedFragmentASingleFragmentStruct))
}

switch try StructResolution.ReferencedFragment(typeName: property.type) {
switch try StructResolution.ReferencedFragment(typeName: type) {

case .name(let fragmentName):
guard let result = context[fragmentName] else { return .missingFragment }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ struct BasicApolloProcessInstantiator: ApolloProcessInstantiator {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = [path.string] + apollo.arguments(api: api, graphql: graphql, outputFile: outputFile)
task.standardOutput = nil
task.standardError = nil
// task.standardOutput = nil
// task.standardError = nil
return task
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SwiftSyntax
extension Property where CurrentStage == Stage.Extracted {

func isComputed() throws -> Bool {
if type.hasPrefix("some ") {
if case .concrete(let type) = type, type.hasPrefix("some ") {
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ extension Struct where CurrentStage: ResolvedStage {
}

var additionalReferencedAPIs: OrderedSet<AdditionalReferencedAPI<CurrentStage>> {
let types = Dictionary(properties.filter { $0.graphqlPath == nil }.map { ($0.type, $0) }) { $1 }
let types = Dictionary(
properties
.filter { $0.graphqlPath == nil }
.compactMap { property -> (String, Property<CurrentStage>)? in
guard case .concrete(let type) = property.type else { return nil }
return (type, property)
}
) { $1 }
return OrderedSet(mutations.map { AdditionalReferencedAPI(api: $0.api, property: types[$0.api.name]) })
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension Property where CurrentStage: GraphQLStage {

extension Property where CurrentStage: GraphQLStage {

init(code: SourceCode, name: String, type: String, graphqlPath: CurrentStage.Path?) {
init(code: SourceCode, name: String, type: PropertyType, graphqlPath: CurrentStage.Path?) {
self.init(code: code, name: name, type: type) { CurrentStage.pathKey ~> graphqlPath }
}

Expand Down
2 changes: 1 addition & 1 deletion templates/graphQL/GraphQLMutation.graphql.stencil
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
mutation {{ mutation.name }}{% if graphQLCodeQueryArgument %}({{ graphQLCodeQueryArgument|codeArray|join:", " }}){% endif %} {
mutation {{ mutation.parentName }}{{ mutation.name }}{% if graphQLCodeQueryArgument %}({{ graphQLCodeQueryArgument|codeArray|join:", " }}){% endif %} {
{{ mutation.object|code }}
}
2 changes: 1 addition & 1 deletion templates/swift/MutationStruct.swift.stencil
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension {{ structPrepared.name }}.{{ mutationStruct.mutation.name }} {
@discardableResult
func commit({{ queryRendererArguments|codeArray|join:", " }}, completion: ((Result<Value, Error>) -> Void)? = nil) -> Self {
mutationCounter += 1
api.client.perform(mutation: Apollo{{ mutationStruct.mutation.api.name }}.{{ mutationStruct.mutation.name }}Mutation({{ queryArgumentAssignments|codeArray|join:", " }})) { result in
api.client.perform(mutation: Apollo{{ mutationStruct.mutation.api.name }}.{{ mutationStruct.mutation.parentName }}{{ mutationStruct.mutation.name }}Mutation({{ queryArgumentAssignments|codeArray|join:", " }})) { result in
self.mutationCounter -= 1
switch result {
case let .success(response):
Expand Down

0 comments on commit 23127b4

Please sign in to comment.