Skip to content

Commit

Permalink
Merge pull request #31 from sebastianwessel/29-fix-handling-of-array-…
Browse files Browse the repository at this point in the history
…type

fix: handling of array type #29
  • Loading branch information
sebastianwessel authored Jul 4, 2024
2 parents 1bd4131 + 39107b5 commit 2ddeeb9
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 68 deletions.
5 changes: 4 additions & 1 deletion example.surql
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,7 @@ DEFINE TABLE book SCHEMAFULL;

DEFINE FIELD vendors ON TABLE book TYPE array<object> DEFAULT [];
DEFINE FIELD vendors[*].name ON TABLE book TYPE string;
DEFINE FIELD vendors[*].price ON TABLE book TYPE number;
DEFINE FIELD vendors[*].price ON TABLE book TYPE number;

DEFINE FIELD vendors[*].ratings ON TABLE book TYPE array<object> DEFAULT [];
DEFINE FIELD vendors[*].ratings[*].score ON TABLE book TYPE number DEFAULT 0;
23 changes: 0 additions & 23 deletions src/genSchema/fieldsToObject.ts

This file was deleted.

8 changes: 4 additions & 4 deletions src/genSchema/generateTableSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const generateSchemaForTable = async (name: string, tableInfo: string) =>

const { fields } = await getTableInfo(name)

let inputFields = mergeNested(fields, true)
let outputFields = mergeNested(fields, false)
let inputFields = mergeNested(fields, true, name)
let outputFields = mergeNested(fields, false, name)

if (!isSchemaFull) {
inputFields += '.passthrough()'
Expand Down Expand Up @@ -58,10 +58,10 @@ export const generateTableSchema = async (outFolder: string, tableInfo: Record<s
import { z } from "zod";
// the create schema for table ${name}
export const ${tableName}InputSchemaGen = ${inputFields};
export ${inputFields};
// the select schema for table ${name}
export const ${tableName}OutputSchemaGen = ${outputFields};
export ${outputFields};
`,
)
Expand Down
55 changes: 55 additions & 0 deletions src/genSchema/generateZodSchemaCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { FieldDetail } from './getDetailsFromDefinition.js'

export const generateZodSchemaCode = (fields: FieldDetail[], schemaName: string): string => {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const buildSchema = (fieldMap: { [key: string]: any }, fields: FieldDetail[]) => {
for (const field of fields) {
const parts = field.name.split('.').map(part => part.replace('[*]', ''))
let current = fieldMap

let i = 0
for (const part of parts) {
if (i === parts.length - 1) {
// Leaf node
if (field.type?.startsWith('array')) {
current[part] = `z.array(${field.zodString.replace('{}', '')}).default(${field.default ?? '[]'})`
} else {
const fieldDefault = field.default ? `.default(${field.default})` : ''
current[part] = `${field.zodString}${fieldDefault}`
}
} else {
// Intermediate node
if (typeof current[part] === 'string') {
current[part] = {}
}
if (!current[part]) {
current[part] = {}
}
current = current[part]
}
i++
}
}
}

const generateCode = (fieldMap: { [key: string]: unknown }, schemaName: string): string => {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const buildObject = (obj: { [key: string]: any }): string => {
const entries = Object.entries(obj).map(([key, value]) => {
if (typeof value === 'string') {
return `${key}: ${value}`
// biome-ignore lint/style/noUselessElse: <explanation>
} else {
return `${key}: z.object({\n${buildObject(value)}\n})`
}
})
return entries.join(',\n ')
}

return `const ${schemaName} = z.object({\n ${buildObject(fieldMap)}\n})`
}

const fieldMap: { [key: string]: unknown } = {}
buildSchema(fieldMap, fields)
return generateCode(fieldMap, schemaName)
}
46 changes: 6 additions & 40 deletions src/genSchema/mergeNested.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,13 @@
import { fieldsToObject } from './fieldsToObject.js'
import { type FieldDetail, getDetailsFromDefinition } from './getDetailsFromDefinition.js'
import { generateZodSchemaCode } from './generateZodSchemaCode.js'
import { getDetailsFromDefinition } from './getDetailsFromDefinition.js'

export const mergeNested = (fields: Record<string, string>, isInputSchema: boolean, tableName: string) => {
const schemaName = `${tableName}${isInputSchema ? 'Input' : 'Output'}SchemaGen`

export const mergeNested = (fields: Record<string, string>, isInputSchema: boolean) => {
const inputFields = Object.entries(fields)
.map(([_fname, definition]) => {
return getDetailsFromDefinition(definition, isInputSchema)
})
.filter(entry => !entry.skip)

const obj = fieldsToObject(inputFields)

const fieldMap = new Map<string, FieldDetail>()

for (const field of inputFields) {
fieldMap.set(field.name, field)
}

const mergeSchemaString = (o: object, prev?: string) => {
const entries = Object.entries(o)

const current = prev ? fieldMap.get(prev) : undefined

if (!entries.length) {
return current?.zodString ?? 'z.any()'
}

let res = ''

for (const [name, value] of entries) {
const n = prev ? `${prev}.${name}` : name
res += `${name}: ${mergeSchemaString(value, n)},\n`
}

if (current?.zodString.startsWith('z.object({})')) {
return current.zodString.replace('z.object({})', `z.object({${res}})`)
}

if (current?.zodString.startsWith('z.array(z.any())')) {
return current.zodString.replace('z.array(z.any())', `z.array(z.object({${res}}))`)
}

return `z.object({${res}})`
}

return mergeSchemaString(obj)
return generateZodSchemaCode(inputFields, schemaName)
}

0 comments on commit 2ddeeb9

Please sign in to comment.