Skip to content

Commit b87b2fb

Browse files
committed
WIP - Adapters passed in a struct
1 parent 90a67ad commit b87b2fb

4 files changed

Lines changed: 107 additions & 25 deletions

File tree

Sources/Compiler/Gen/Language.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
import OrderedCollections
99
import SwiftSyntax
1010
import SwiftSyntaxBuilder
11+
import Foundation
1112

1213
public protocol Language {
1314
init(options: GenerationOptions)
1415

1516
var boolName: String { get }
1617

17-
var builtinAdapters: Set<String> { get }
18+
/// A list of types that have builtin adapters supplied by the library.
19+
/// Note: These are the type names, not the name of the adapter used.
20+
var builtinAdapterTypes: Set<String> { get }
1821

1922
func queryTypeName(input: String, output: String) -> String
2023

@@ -49,6 +52,9 @@ extension Language {
4952
) throws -> String {
5053
let values = try assemble(queries: queries, schema: schema)
5154

55+
let builtinAdapters = builtinAdapterTypes
56+
.map(adapterName(from:))
57+
5258
// Get a list of all adapters used. Right now we only have to look at the
5359
// tables since any output would inhereit the encoding of the source table.
5460
let adapters: Set<String> = values.tables.reduce(into: []) { adapters, table in
@@ -214,7 +220,7 @@ extension Language {
214220
return .encoded(
215221
generationType(for: root),
216222
alias: alias,
217-
adapter: "\(adapter?.description ?? alias)DatabaseValueAdapter"
223+
adapter: adapter.map(adapterName(from:)) ?? adapterName(from: alias)
218224
)
219225
case let .optional(type):
220226
return .optional(generationType(for: type))
@@ -319,8 +325,44 @@ extension Language {
319325

320326
return singleOrMany(.model(model))
321327
}
328+
329+
/// Converts a type name to the usable adapter name. Basically lower camelcases it.
330+
///
331+
/// Int -> int
332+
/// UUID -> uuid
333+
/// Foo.ID -> fooID
334+
private func adapterName<S: StringProtocol>(from typeName: S) -> String {
335+
var result = ""
336+
result.reserveCapacity(typeName.count)
337+
338+
// Right now we only support Swift so we are expecting the input
339+
// to be upper camel case so we really only have to worry about
340+
// lower casing the initial characters until we hit the first lower
341+
// cased character
342+
var hasHitLowerCase = false
343+
344+
for c in typeName {
345+
guard identifierCharacters.contains(c) else { continue }
346+
347+
if !hasHitLowerCase {
348+
result.append(c.lowercased())
349+
} else {
350+
result.append(c)
351+
}
352+
353+
hasHitLowerCase = hasHitLowerCase || c.isLowercase
354+
}
355+
356+
return result
357+
}
322358
}
323359

360+
let identifierCharacters: Set<Character> = {
361+
var characters: Set<Character> = Set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_")
362+
characters.insert("_")
363+
return characters
364+
}()
365+
324366
public struct GenerationOptions: Sendable {
325367
public var databaseName: String
326368
public var imports: [String]

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@ public struct SwiftLanguage: Language {
1515

1616
public var boolName: String { "Bool" }
1717

18-
public var builtinAdapters: Set<String> {
18+
public var builtinAdapterTypes: Set<String> {
1919
[
20-
"BoolDatabaseValueAdapter",
21-
"Int8DatabaseValueAdapter",
22-
"Int16DatabaseValueAdapter",
23-
"Int32DatabaseValueAdapter",
24-
"Int64DatabaseValueAdapter",
25-
"UInt8DatabaseValueAdapter",
26-
"UInt16DatabaseValueAdapter",
27-
"UInt32DatabaseValueAdapter",
28-
"UInt64DatabaseValueAdapter",
29-
"UIntDatabaseValueAdapter",
30-
"FloatDatabaseValueAdapter",
31-
"Float16DatabaseValueAdapter",
32-
"UUIDDatabaseValueAdapter",
33-
"DecimalDatabaseValueAdapter",
34-
"DateDatabaseValueAdapter",
20+
"Bool",
21+
"Int8",
22+
"Int16",
23+
"Int32",
24+
"Int64",
25+
"UInt8",
26+
"UInt16",
27+
"UInt32",
28+
"UInt64",
29+
"UInt",
30+
"Float",
31+
"Float16",
32+
"UUID",
33+
"Decimal",
34+
"Date",
35+
"URL",
3536
]
3637
}
3738

@@ -87,12 +88,13 @@ public struct SwiftLanguage: Language {
8788

8889
writer.write("import Foundation")
8990
writer.write(line: "import Otter")
90-
writer.blankLine()
9191

9292
for `import` in options.imports {
9393
writer.write(line: "import \(`import`)")
9494
}
9595

96+
writer.blankLine()
97+
9698
for table in tables {
9799
declaration(for: table, isOutput: true)
98100
}
@@ -109,7 +111,7 @@ public struct SwiftLanguage: Language {
109111
}
110112
}
111113

112-
dbStruct(queries: queries, migrations: migrations)
114+
dbStruct(queries: queries, migrations: migrations, adapters: adapters)
113115
writer.blankLine()
114116

115117
for query in allQueries {
@@ -166,12 +168,28 @@ public struct SwiftLanguage: Language {
166168

167169
private func dbStruct(
168170
queries: [(String?, [GeneratedQuery])],
169-
migrations: [String]
171+
migrations: [String],
172+
adapters: [String]
170173
) {
171174
writer.write(line: "struct ", options.databaseName, ": Database")
172175

173176
writer.braces {
174177
writer.write(line: "let connection: any Otter.Connection")
178+
179+
writer.write(line: "let adapters: Adapters")
180+
// Don't require initialization if there are no custom adapters
181+
if adapters.isEmpty {
182+
writer.write(" = Adapters()")
183+
}
184+
writer.newline()
185+
186+
writer.write(line: "struct Adapters ")
187+
writer.braces {
188+
for adapter in adapters {
189+
writer.write(line: "let ", adapter, ": DatabaseValueAdapter")
190+
}
191+
}
192+
175193
writer.newline()
176194

177195
writer.write(line: "static var migrations: [String] ")
@@ -289,6 +307,7 @@ public struct SwiftLanguage: Language {
289307
writer.indent()
290308

291309
writer.write(line: "let connection: any Connection")
310+
writer.write(line: "let adapters: ", options.databaseName,"Adapters")
292311
writer.blankLine()
293312

294313
for (position, query) in queries.positional() {
@@ -514,10 +533,10 @@ public struct SwiftLanguage: Language {
514533
writer.write("row.optionallyEmbedded(at: start + ", index.description, ")")
515534
index += model.fields.count
516535
case let .encoded(storage, _, adapter):
517-
writer.write("row.value(at: start + ", index.description, ", using: ", adapter, ".self, storage: ", typeName(for: storage), ".self)")
536+
writer.write("row.value(at: start + ", index.description, ", using: adapters.", adapter, ", storage: ", typeName(for: storage), ".self)")
518537
index += 1
519538
case let .optional(.encoded(storage, _, adapter)):
520-
writer.write("row.optionalValue(at: start + ", index.description, ", using: ", adapter, ".self, storage: ", typeName(for: storage), ".self)")
539+
writer.write("row.optionalValue(at: start + ", index.description, ", using: adapters.", adapter, ", storage: ", typeName(for: storage), ".self)")
521540
index += 1
522541
default:
523542
fatalError("Invalid field type \(field.typeName) \(field.type)")
@@ -680,7 +699,7 @@ public struct SwiftLanguage: Language {
680699
writer.write(name, ", to: ", index.description)
681700

682701
if let adapter {
683-
writer.write(", using: ", adapter.name, ".self, as: ", adapter.storage, ".self")
702+
writer.write(", using: adapters.", adapter.name, ", as: ", adapter.storage, ".self")
684703
}
685704

686705
writer.write(")")

Sources/Otter/DatabaseValueAdapter.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,21 @@ public enum DateDatabaseValueAdapter: DatabaseValueAdapter {
276276
try .double(encodeToDouble(value: value))
277277
}
278278
}
279+
280+
public struct URLDatabaseValueAdapter: DatabaseValueAdapter {
281+
@inlinable public static func encodeToString(value: URL) throws(OtterError) -> String {
282+
value.absoluteString
283+
}
284+
285+
@inlinable public static func encodeToAny(value: URL) throws(OtterError) -> SQLAny {
286+
.string(value.absoluteString)
287+
}
288+
289+
@inlinable public static func decode(from primitive: String) throws(OtterError) -> URL {
290+
guard let url = URL(string: primitive) else {
291+
throw OtterError.cannotEncode(String.self, to: URL.self)
292+
}
293+
294+
return url
295+
}
296+
}

Sources/OtterCLI/GenerateCommand.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ struct GenerateCommand: AsyncParsableCommand {
3030

3131
@Flag(help: "If true, it will emit diagnostics that Xcode can understand")
3232
var xcodeDiagnosticReporter = false
33+
34+
@Flag(help: "If true, the output will be dumped to stdout and not not be written to disk")
35+
var dump = false
3336

3437
mutating func run() async throws {
3538
let config = try Config(at: path)
@@ -73,7 +76,7 @@ struct GenerateCommand: AsyncParsableCommand {
7376

7477
try await driver.generate(
7578
language: Lang.self,
76-
to: project.generatedOutputFile.path,
79+
to: dump ? nil : project.generatedOutputFile.path,
7780
options: options
7881
)
7982

0 commit comments

Comments
 (0)