Skip to content

Commit 24a4462

Browse files
yonaskolbmaticzav
andauthored
Improve code generation (#204)
* single objects lookup * generate seperate files * config to disable static field generation * add server setup docs * make config.generateStaticFields optional for backwards compatability * support single file generation with .swift output * make standard out just be the single file again --------- Co-authored-by: Matic Zavadlal <matic.zavadlal@gmail.com>
1 parent d6e6353 commit 24a4462

File tree

5 files changed

+121
-55
lines changed

5 files changed

+121
-55
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
.DS_Store
22
*.log*
3-
.env*
43

54
# Swift
65

Sources/SwiftGraphQLCLI/main.swift

+29-5
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,18 @@ struct SwiftGraphQLCLI: ParsableCommand {
112112

113113
let scalars = ScalarMap(scalars: config.scalars)
114114
let generator = GraphQLCodegen(scalars: scalars)
115-
let code: String
116-
115+
let files: [GeneratedFile]
116+
117+
// If the output is a Swift file generate a single file, otherwise multiple files in that directory
118+
// If there's no output generate a single file as well as it will be printed to standard out
119+
let singleFileOutput = output?.hasSuffix(".swift") ?? true
120+
117121
do {
118-
code = try generator.generate(schema: schema)
122+
files = try generator.generate(
123+
schema: schema,
124+
generateStaticFields: config.generateStaticFields != false,
125+
singleFile: singleFileOutput
126+
)
119127
generateCodeSpinner.success("API generated successfully!")
120128
} catch CodegenError.formatting(let err) {
121129
generateCodeSpinner.error(err.localizedDescription)
@@ -131,9 +139,21 @@ struct SwiftGraphQLCLI: ParsableCommand {
131139

132140
// Write to target file or stdout.
133141
if let outputPath = output {
134-
try Folder.current.createFile(at: outputPath).write(code)
142+
if singleFileOutput, let file = files.first {
143+
// The generator returns a single file if asked to
144+
try Folder.current.createFile(at: outputPath).write(file.contents)
145+
} else {
146+
// Clear the directory, in case some files were removed
147+
try? Folder.current.subfolder(at: outputPath).delete()
148+
for file in files {
149+
try Folder.current.createFile(at: "\(outputPath)/\(file.name).swift").write(file.contents)
150+
}
151+
}
135152
} else {
136-
FileHandle.standardOutput.write(code.data(using: .utf8)!)
153+
for file in files {
154+
// this should always be one file anyway
155+
FileHandle.standardOutput.write(file.contents.data(using: .utf8)!)
156+
}
137157
}
138158

139159
let analyzeSchemaSpinner = Spinner(.dots, "Analyzing Schema")
@@ -174,11 +194,15 @@ struct Config: Codable, Equatable {
174194
/// Key-Value dictionary of scalar mappings.
175195
let scalars: [String: String]
176196

197+
/// Whether to generate static lookups for object fields
198+
var generateStaticFields: Bool?
199+
177200
// MARK: - Initializers
178201

179202
/// Creates an empty configuration instance.
180203
init() {
181204
self.scalars = [:]
205+
self.generateStaticFields = true
182206
}
183207

184208
/// Tries to decode the configuration from a string.

Sources/SwiftGraphQLCodegen/Generator.swift

+71-48
Original file line numberDiff line numberDiff line change
@@ -14,79 +14,102 @@ public struct GraphQLCodegen {
1414
}
1515

1616
// MARK: - Methods
17-
18-
/// Generates a SwiftGraphQL Selection File (i.e. the code that tells how to define selections).
19-
public func generate(schema: Schema) throws -> String {
17+
18+
/// Generates Swift files for the graph selections
19+
/// - Parameters:
20+
/// - schema: The GraphQL schema
21+
/// - generateStaticFields: Whether to generate static selections for fields on objects
22+
/// - singleFile: Whether to return all the swift code in a single file
23+
/// - Returns: A list of generated files
24+
public func generate(schema: Schema, generateStaticFields: Bool, singleFile: Bool = false) throws -> [GeneratedFile] {
2025
let context = Context(schema: schema, scalars: self.scalars)
2126

2227
let subscription = schema.operations.first { $0.isSubscription }?.type.name
23-
24-
// Code Parts
28+
let objects = schema.objects
2529
let operations = schema.operations.map { $0.declaration() }
26-
let objectDefinitions = try schema.objects.map { object in
27-
try object.declaration(
28-
objects: schema.objects,
29-
context: context,
30-
alias: object.name != subscription
31-
)
32-
}
33-
34-
let staticFieldSelection = try schema.objects.map { object in
35-
try object.statics(context: context)
36-
}
37-
38-
let interfaceDefinitions = try schema.interfaces.map {
39-
try $0.declaration(objects: schema.objects, context: context)
40-
}
41-
42-
let unionDefinitions = try schema.unions.map {
43-
try $0.declaration(objects: schema.objects, context: context)
44-
}
45-
46-
let enumDefinitions = schema.enums.map { $0.declaration }
47-
48-
let inputObjectDefinitions = try schema.inputObjects.map {
49-
try $0.declaration(context: context)
50-
}
51-
52-
// API
53-
let code = """
30+
31+
var files: [GeneratedFile] = []
32+
33+
let header = """
5434
// This file was auto-generated using maticzav/swift-graphql. DO NOT EDIT MANUALLY!
5535
import Foundation
5636
import GraphQL
5737
import SwiftGraphQL
38+
"""
5839

59-
// MARK: - Operations
40+
let graphContents = """
6041
public enum Operations {}
6142
\(operations.lines)
6243
63-
// MARK: - Objects
6444
public enum Objects {}
65-
\(objectDefinitions.lines)
66-
\(staticFieldSelection.lines)
6745
68-
// MARK: - Interfaces
6946
public enum Interfaces {}
70-
\(interfaceDefinitions.lines)
7147
72-
// MARK: - Unions
7348
public enum Unions {}
74-
\(unionDefinitions.lines)
7549
76-
// MARK: - Enums
7750
public enum Enums {}
78-
\(enumDefinitions.lines)
7951
80-
// MARK: - Input Objects
81-
8252
/// Utility pointer to InputObjects.
8353
public typealias Inputs = InputObjects
8454
8555
public enum InputObjects {}
86-
\(inputObjectDefinitions.lines)
8756
"""
8857

89-
let formatted = try code.format()
90-
return formatted
58+
func addFile(name: String, contents: String) throws {
59+
let fileContents: String
60+
if singleFile {
61+
fileContents = "\n// MARK: \(name)\n\(contents)"
62+
} else {
63+
fileContents = "\(header)\n\n\(contents)"
64+
}
65+
let file = GeneratedFile(name: name, contents: try fileContents.format())
66+
files.append(file)
67+
}
68+
69+
try addFile(name: "Graph", contents: graphContents)
70+
for object in objects {
71+
var contents = try object.declaration(
72+
objects: objects,
73+
context: context,
74+
alias: object.name != subscription
75+
)
76+
77+
if generateStaticFields {
78+
let staticFieldSelection = try object.statics(context: context)
79+
contents += "\n\n\(staticFieldSelection)"
80+
}
81+
try addFile(name: "Objects/\(object.name)", contents: contents)
82+
}
83+
84+
for object in schema.inputObjects {
85+
let contents = try object.declaration(context: context)
86+
try addFile(name: "InputObjects/\(object.name)", contents: contents)
87+
}
88+
89+
for enumSchema in schema.enums {
90+
try addFile(name: "Enums/\(enumSchema.name)", contents: enumSchema.declaration)
91+
}
92+
93+
for interface in schema.interfaces {
94+
let contents = try interface.declaration(objects: objects, context: context)
95+
try addFile(name: "Interfaces/\(interface.name)", contents: contents)
96+
}
97+
98+
for union in schema.unions {
99+
let contents = try union.declaration(objects: objects, context: context)
100+
try addFile(name: "Unions/\(union.name)", contents: contents)
101+
}
102+
103+
if singleFile {
104+
let fileContent = "\(header)\n\n\(files.map(\.contents).joined(separator: "\n\n"))"
105+
files = [GeneratedFile(name: "Graph", contents: fileContent)]
106+
}
107+
108+
return files
91109
}
92110
}
111+
112+
public struct GeneratedFile {
113+
public let name: String
114+
public let contents: String
115+
}

examples/thesocialnetwork/README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,26 @@ A sample server for a social network that
77
- uses subscriptions,
88
- lets users write to a shared feed.
99

10+
```bash
11+
# Start Server
12+
yarn start
13+
14+
# Generate Prisma Client
15+
yarn prisma generate
16+
17+
# Generate TypeGen
18+
yarn generate
19+
```
20+
21+
1022
### Development Setup
1123

1224
Start local Postgres database using Docker Compose.
1325

1426
```bash
27+
# Start DB in the background
1528
docker-compose up -d
1629

17-
export DATABASE_URL="postgresql://prisma:prisma@localhost:5432/prisma"
30+
# Setup Environment variables
31+
cp .env.example .env
1832
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
AWS_S3_BUCKET="thesocialnetwork-images"
2+
AWS_ACCESS_KEY_ID="AKIA3JJIBRRXWTIC7"
3+
AWS_SECRET_ACCESS_KEY="puU+kTKu288s+K9HpcbPGIrX79TItKly"
4+
5+
DATABASE_URL="postgresql://prisma:prisma@localhost:5432/prisma"
6+

0 commit comments

Comments
 (0)