Skip to content

Commit

Permalink
Merge pull request #1 - codegen rewrite, DuetGraphQL module, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredh159 authored Jun 1, 2022
2 parents c54c1ea + 2a68e36 commit 348281e
Show file tree
Hide file tree
Showing 19 changed files with 605 additions and 409 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: basic-ci

on: push

jobs:
linux:
runs-on: ubuntu-latest
container: swift:5.5-focal
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
run: swift build
- name: Run tests
run: SWIFT_DETERMINISTIC_HASHING=1 swift test

macos:
runs-on: macos-12
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '13.2.1'
- name: Checkout
uses: actions/checkout@v2
- name: Build
run: swift build
- name: Run tests
run: SWIFT_DETERMINISTIC_HASHING=1 swift test
6 changes: 3 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@
},
{
"package": "swift-tagged",
"repositoryURL": "https://github.com/pointfreeco/swift-tagged",
"repositoryURL": "https://github.com/pointfreeco/swift-tagged.git",
"state": {
"branch": null,
"revision": "f3f773a5e13f3c8f0ab1ce2ae6378058acefa87d",
Expand Down Expand Up @@ -249,8 +249,8 @@
"repositoryURL": "https://github.com/jaredh159/x-kit.git",
"state": {
"branch": null,
"revision": "7deedcba5da944d4e274dc16105c6624f98493b4",
"version": "1.0.2"
"revision": "54871ee5c2d6b2fac29e32ac62df6fbc79256184",
"version": "1.1.0"
}
}
]
Expand Down
30 changes: 26 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ let package = Package(
.library(name: "Duet", targets: ["Duet"]),
.library(name: "DuetSQL", targets: ["DuetSQL"]),
.library(name: "DuetMock", targets: ["DuetMock"]),
.library(name: "DuetGraphQL", targets: ["DuetGraphQL"]),
],
dependencies: [
.package(url: "https://github.com/jaredh159/x-kit.git", from: "1.0.2"),
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.16.0"),
.package(url: "https://github.com/pointfreeco/swift-tagged", from: "0.6.0"),
.package(url: "https://github.com/wickwirew/Runtime.git", from: "2.2.4"),
.package("jaredh159/[email protected]"),
.package("vapor/[email protected]"),
.package("pointfreeco/[email protected]"),
.package("wickwirew/[email protected]"),
.package("vapor/[email protected]"),
.package("alexsteinerde/[email protected]", "GraphQLKit"),
],
targets: [
.target(
Expand All @@ -35,8 +38,27 @@ let package = Package(
.product(name: "Tagged", package: "swift-tagged"),
]
),
.target(
name: "DuetGraphQL",
dependencies: [
.product(name: "Vapor", package: "vapor"),
"DuetSQL",
"GraphQLKit",
]
),
.target(name: "DuetMock", dependencies: ["Duet"]),
.testTarget(name: "DuetSQLTests", dependencies: ["DuetSQL"]),
.testTarget(name: "DuetTests", dependencies: ["Duet"]),
]
)

extension PackageDescription.Package.Dependency {
static func package(_ commitish: String, _ name: String? = nil) -> Package.Dependency {
let parts = commitish.split(separator: "@")
return .package(
name: name,
url: "https://github.com/\(parts[0]).git",
from: .init(stringLiteral: "\(parts[1])")
)
}
}
56 changes: 56 additions & 0 deletions Sources/DuetGraphQL/Schema.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import DuetSQL
import Graphiti
import Vapor

public enum Schema<Resolver> {
public typealias ModelType<Model: DuetSQL.Model> = Type<Resolver, Request, Model>
public typealias AppField<FieldType, Args: Decodable> = Field<Resolver, Request, FieldType, Args>
public typealias AppType<Value: Encodable> = Type<Resolver, Request, Value>
public typealias AppInput<InputObjectType: Decodable> = Input<Resolver, Request, InputObjectType>

public static var IdentifiedEntityType: AppType<IdentifiedEntity> {
Type(IdentifiedEntity.self) {
Field("id", at: \.id.lowercased)
}
}
}

public struct InputArgs<Input: Codable>: Codable {
public let input: Input

public init(input: Input) {
self.input = input
}
}

public struct IdentifyEntityArgs: Codable {
public let id: UUID

public init(id: UUID) {
self.id = id
}
}

public struct GenericResponse: Codable {
public let success: Bool

public init(success: Bool) {
self.success = success
}
}

public struct DeletedEntityResult: Codable {
public let deletedId: UUID

public init(deletedId: UUID) {
self.deletedId = deletedId
}
}

public struct IdentifiedEntity: Codable {
public let id: UUID

public init(id: UUID) {
self.id = id
}
}
6 changes: 5 additions & 1 deletion Sources/DuetSQL/Extend/FluentSQL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public extension SQLRow {
}
}

extension SQLQueryString {
public extension SQLQueryString {
mutating func appendInterpolation<T: RawRepresentable>(id: T) where T.RawValue == UUID {
appendInterpolation(raw: id.rawValue.uuidString)
}
Expand All @@ -16,6 +16,10 @@ extension SQLQueryString {
appendInterpolation(raw: model.tableName)
}

mutating func appendInterpolation(col: FieldKey) {
appendInterpolation(raw: col.description)
}

mutating func appendInterpolation(col: String) {
appendInterpolation(raw: col)
}
Expand Down
32 changes: 26 additions & 6 deletions codegen/generate-models.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
import path from 'path';
import fs from 'fs';
import { generateModelConformances } from './lib/models/model-conformances';
import { duetSqlModelConformance } from './lib/models/sql-conformance';
import { generateModelMocks } from './lib/models/model-mocks';
import { generateModelGraphQLTypes } from './lib/models/graphql';
import { scriptData } from './lib/script-helpers';

const { models, appRoot, types } = scriptData();
const { models, appRoot, types, config } = scriptData();

let duetConformancesCode = /* swift */ `// auto-generated, do not edit
import Duet
import Tagged
`;

let sqlConformancesCode = /* swift */ `// auto-generated, do not edit
import DuetSQL
`;

let graphqlDir = config.graphqlConformancesDir;

for (const model of models) {
const [conformancePath, conformanceCode] = generateModelConformances(model, types);
fs.writeFileSync(`${appRoot}/${conformancePath}`, conformanceCode);
duetConformancesCode += `${model.duetIdentifiableConformance}\n${model.codingKeysExtension}\n`;
sqlConformancesCode += `\n${duetSqlModelConformance(model, types)}\n`;

// @TODO model mocks
const [mocksPath, mocksCode] = generateModelMocks(model, types);
const testDir = path.dirname(`${appRoot}/${mocksPath}`);
if (!fs.existsSync(testDir)) {
fs.mkdirSync(testDir);
}
fs.writeFileSync(`${appRoot}/${mocksPath}`, mocksCode);

const [graphqlPath, graphqlCode] = generateModelGraphQLTypes(model, types);
fs.writeFileSync(`${appRoot}/${graphqlPath}`, graphqlCode);
if (graphqlDir) {
const graphqlCode = generateModelGraphQLTypes(model, types);
fs.writeFileSync(`${graphqlDir}/${model.name}+GraphQL.swift`, graphqlCode);
}
}

fs.writeFileSync(config.duetConformancesLocation, duetConformancesCode);

if (config.duetSqlConformancesLocation) {
fs.writeFileSync(config.duetSqlConformancesLocation, sqlConformancesCode);
}
38 changes: 38 additions & 0 deletions codegen/lib/models/Model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import stripIndent from 'strip-indent';

export type Prop = {
name: string;
type: string;
Expand Down Expand Up @@ -29,7 +31,43 @@ export default class Model {
return this.name.charAt(0).toLowerCase() + this.name.slice(1);
}

public get duetIdentifiableConformance(): string {
return stripIndent(/* swift */ `
extension ${this.name}: Duet.Identifiable {
public typealias Id = Tagged<${this.name}, UUID>
}
`);
}

public get codingKeysExtension(): string {
let code = `public extension ${this.name} {\n`;
code += ` enum CodingKeys: String, CodingKey, CaseIterable {\n `;
code += this.props.map((p) => `case ${p.name}`).join(`\n `);
code += `\n }\n}`;
return code;
}

public get apiModelConformance(): string {
return `extension ${this.name}: ApiModel {}`;
}

public get tableName(): string {
return this.migrationNumber
? `M${this.migrationNumber}.tableName`
: `"${pascalToSnake(this.name)}s"`;
}

public static mock(): Model {
return new Model(`Thing`, `Sources/App/Models/Things/Thing.swift`);
}
}

// helpers

function pascalToSnake(str: string): string {
return str
.trim()
.split(/(?=[A-Z])/)
.join('_')
.toLowerCase();
}
43 changes: 43 additions & 0 deletions codegen/lib/models/__tests__/Model.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, it, expect } from '@jest/globals';
import stripIndent from 'strip-indent';
import Model from '../Model';

describe(`Model`, () => {
it(`generates correct Duet.IdentifiableConformance`, () => {
const expected = stripIndent(/* swift */ `
extension Thing: Duet.Identifiable {
public typealias Id = Tagged<Thing, UUID>
}
`);

expect(Model.mock().duetIdentifiableConformance).toBe(expected);
});

it(`generates correct CodingKeys`, () => {
const expected = stripIndent(/* swift */ `
public extension Thing {
enum CodingKeys: String, CodingKey, CaseIterable {
case id
case foo
}
}
`);

const model = Model.mock();
model.props = [
{ name: `id`, type: `Id` },
{ name: `foo`, type: `String` },
];
expect(model.codingKeysExtension).toBe(expected.trim());
});

it(`tableName (w/ migration number)`, () => {
const model = Model.mock();
model.migrationNumber = 1;
expect(model.tableName).toBe(`M1.tableName`);
});

it(`tableName (NO migration number)`, () => {
expect(Model.mock().tableName).toBe(`"things"`);
});
});
Loading

0 comments on commit 348281e

Please sign in to comment.