diff --git a/src/server/templates/go.ts b/src/server/templates/go.ts index d2cf5b9d..4ffa883a 100644 --- a/src/server/templates/go.ts +++ b/src/server/templates/go.ts @@ -112,6 +112,16 @@ function formatForGoTypeName(name: string): string { .join('') } +// Go raw string literals cannot contain a backtick, so a column name with one +// has to be emitted as an interpreted literal instead. +function formatGoStructTag(name: string): string { + const tag = `json:"${name}"` + if (!tag.includes('`')) { + return `\`${tag}\`` + } + return `"${tag.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` +} + function generateTableStruct( schema: PostgresSchema, table: PostgresTable | PostgresView | PostgresMaterializedView, @@ -157,7 +167,7 @@ function generateTableStruct( const formattedColumnEntries = columnEntries.map(([formattedName, type, name]) => { return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd( maxTypeLength - )} \`json:"${name}"\`` + )} ${formatGoStructTag(name)}` }) return ` @@ -215,7 +225,7 @@ function generateCompositeTypeStruct( const formattedAttributeEntries = attributeEntries.map(([formattedName, type, name]) => { return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd( maxTypeLength - )} \`json:"${name}"\`` + )} ${formatGoStructTag(name)}` }) return ` diff --git a/test/server/templates/go.test.ts b/test/server/templates/go.test.ts index f1be6b50..e3c75fd8 100644 --- a/test/server/templates/go.test.ts +++ b/test/server/templates/go.test.ts @@ -104,3 +104,24 @@ describe('go typegen pgTypeToGoType array fallback', () => { expect(result).toMatch(/Tags\s+\[]string\b/) }) }) + +describe('go typegen struct tags', () => { + test('a column name containing a backtick is emitted as an interpreted literal', () => { + const result = apply(buildMetadata([baseColumn({ name: 'bad`tag' })])) + + expect(result).toContain('"json:\\"bad`tag\\""') + expect(result).not.toContain('`json:"bad`tag"`') + }) + + test('double quotes are escaped alongside the backtick', () => { + const result = apply(buildMetadata([baseColumn({ name: 'a"b`c' })])) + + expect(result).toContain('"json:\\"a\\"b`c\\""') + }) + + test('ordinary column names keep the raw literal form', () => { + const result = apply(buildMetadata([baseColumn({ name: 'title' })])) + + expect(result).toContain('`json:"title"`') + }) +})