Skip to content

Commit

Permalink
Improve code generation (#204)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
yonaskolb and maticzav authored May 6, 2024
1 parent d6e6353 commit 24a4462
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 55 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.DS_Store
*.log*
.env*

# Swift

Expand Down
34 changes: 29 additions & 5 deletions Sources/SwiftGraphQLCLI/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,18 @@ struct SwiftGraphQLCLI: ParsableCommand {

let scalars = ScalarMap(scalars: config.scalars)
let generator = GraphQLCodegen(scalars: scalars)
let code: String

let files: [GeneratedFile]

// If the output is a Swift file generate a single file, otherwise multiple files in that directory
// If there's no output generate a single file as well as it will be printed to standard out
let singleFileOutput = output?.hasSuffix(".swift") ?? true

do {
code = try generator.generate(schema: schema)
files = try generator.generate(
schema: schema,
generateStaticFields: config.generateStaticFields != false,
singleFile: singleFileOutput
)
generateCodeSpinner.success("API generated successfully!")
} catch CodegenError.formatting(let err) {
generateCodeSpinner.error(err.localizedDescription)
Expand All @@ -131,9 +139,21 @@ struct SwiftGraphQLCLI: ParsableCommand {

// Write to target file or stdout.
if let outputPath = output {
try Folder.current.createFile(at: outputPath).write(code)
if singleFileOutput, let file = files.first {
// The generator returns a single file if asked to
try Folder.current.createFile(at: outputPath).write(file.contents)
} else {
// Clear the directory, in case some files were removed
try? Folder.current.subfolder(at: outputPath).delete()
for file in files {
try Folder.current.createFile(at: "\(outputPath)/\(file.name).swift").write(file.contents)
}
}
} else {
FileHandle.standardOutput.write(code.data(using: .utf8)!)
for file in files {
// this should always be one file anyway
FileHandle.standardOutput.write(file.contents.data(using: .utf8)!)
}
}

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

/// Whether to generate static lookups for object fields
var generateStaticFields: Bool?

// MARK: - Initializers

/// Creates an empty configuration instance.
init() {
self.scalars = [:]
self.generateStaticFields = true
}

/// Tries to decode the configuration from a string.
Expand Down
119 changes: 71 additions & 48 deletions Sources/SwiftGraphQLCodegen/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,79 +14,102 @@ public struct GraphQLCodegen {
}

// MARK: - Methods

/// Generates a SwiftGraphQL Selection File (i.e. the code that tells how to define selections).
public func generate(schema: Schema) throws -> String {

/// Generates Swift files for the graph selections
/// - Parameters:
/// - schema: The GraphQL schema
/// - generateStaticFields: Whether to generate static selections for fields on objects
/// - singleFile: Whether to return all the swift code in a single file
/// - Returns: A list of generated files
public func generate(schema: Schema, generateStaticFields: Bool, singleFile: Bool = false) throws -> [GeneratedFile] {
let context = Context(schema: schema, scalars: self.scalars)

let subscription = schema.operations.first { $0.isSubscription }?.type.name

// Code Parts
let objects = schema.objects
let operations = schema.operations.map { $0.declaration() }
let objectDefinitions = try schema.objects.map { object in
try object.declaration(
objects: schema.objects,
context: context,
alias: object.name != subscription
)
}

let staticFieldSelection = try schema.objects.map { object in
try object.statics(context: context)
}

let interfaceDefinitions = try schema.interfaces.map {
try $0.declaration(objects: schema.objects, context: context)
}

let unionDefinitions = try schema.unions.map {
try $0.declaration(objects: schema.objects, context: context)
}

let enumDefinitions = schema.enums.map { $0.declaration }

let inputObjectDefinitions = try schema.inputObjects.map {
try $0.declaration(context: context)
}

// API
let code = """

var files: [GeneratedFile] = []

let header = """
// This file was auto-generated using maticzav/swift-graphql. DO NOT EDIT MANUALLY!
import Foundation
import GraphQL
import SwiftGraphQL
"""

// MARK: - Operations
let graphContents = """
public enum Operations {}
\(operations.lines)
// MARK: - Objects
public enum Objects {}
\(objectDefinitions.lines)
\(staticFieldSelection.lines)
// MARK: - Interfaces
public enum Interfaces {}
\(interfaceDefinitions.lines)
// MARK: - Unions
public enum Unions {}
\(unionDefinitions.lines)
// MARK: - Enums
public enum Enums {}
\(enumDefinitions.lines)
// MARK: - Input Objects
/// Utility pointer to InputObjects.
public typealias Inputs = InputObjects
public enum InputObjects {}
\(inputObjectDefinitions.lines)
"""

let formatted = try code.format()
return formatted
func addFile(name: String, contents: String) throws {
let fileContents: String
if singleFile {
fileContents = "\n// MARK: \(name)\n\(contents)"
} else {
fileContents = "\(header)\n\n\(contents)"
}
let file = GeneratedFile(name: name, contents: try fileContents.format())
files.append(file)
}

try addFile(name: "Graph", contents: graphContents)
for object in objects {
var contents = try object.declaration(
objects: objects,
context: context,
alias: object.name != subscription
)

if generateStaticFields {
let staticFieldSelection = try object.statics(context: context)
contents += "\n\n\(staticFieldSelection)"
}
try addFile(name: "Objects/\(object.name)", contents: contents)
}

for object in schema.inputObjects {
let contents = try object.declaration(context: context)
try addFile(name: "InputObjects/\(object.name)", contents: contents)
}

for enumSchema in schema.enums {
try addFile(name: "Enums/\(enumSchema.name)", contents: enumSchema.declaration)
}

for interface in schema.interfaces {
let contents = try interface.declaration(objects: objects, context: context)
try addFile(name: "Interfaces/\(interface.name)", contents: contents)
}

for union in schema.unions {
let contents = try union.declaration(objects: objects, context: context)
try addFile(name: "Unions/\(union.name)", contents: contents)
}

if singleFile {
let fileContent = "\(header)\n\n\(files.map(\.contents).joined(separator: "\n\n"))"
files = [GeneratedFile(name: "Graph", contents: fileContent)]
}

return files
}
}

public struct GeneratedFile {
public let name: String
public let contents: String
}
16 changes: 15 additions & 1 deletion examples/thesocialnetwork/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,26 @@ A sample server for a social network that
- uses subscriptions,
- lets users write to a shared feed.

```bash
# Start Server
yarn start

# Generate Prisma Client
yarn prisma generate

# Generate TypeGen
yarn generate
```


### Development Setup

Start local Postgres database using Docker Compose.

```bash
# Start DB in the background
docker-compose up -d

export DATABASE_URL="postgresql://prisma:prisma@localhost:5432/prisma"
# Setup Environment variables
cp .env.example .env
```
6 changes: 6 additions & 0 deletions examples/thesocialnetwork/server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
AWS_S3_BUCKET="thesocialnetwork-images"
AWS_ACCESS_KEY_ID="AKIA3JJIBRRXWTIC7"
AWS_SECRET_ACCESS_KEY="puU+kTKu288s+K9HpcbPGIrX79TItKly"

DATABASE_URL="postgresql://prisma:prisma@localhost:5432/prisma"

0 comments on commit 24a4462

Please sign in to comment.