fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 219 lines 5.6 kB view raw
1import type { SymbolMeta } from '@hey-api/codegen-core'; 2import { fromRef, ref } from '@hey-api/codegen-core'; 3import type { IR, SchemaWithType } from '@hey-api/shared'; 4import { deduplicateSchema, pathToJsonPointer } from '@hey-api/shared'; 5 6// import { $ } from '../../../py-dsl'; 7import type { Ast, IrSchemaToAstOptions } from '../shared/types'; 8import type { PydanticPlugin } from '../types'; 9import { createProcessor } from './processor'; 10import { irSchemaWithTypeToAst } from './toAst'; 11 12export function irSchemaToAst({ 13 optional, 14 plugin, 15 schema, 16 schemaExtractor, 17 state, 18}: IrSchemaToAstOptions & { 19 optional?: boolean; 20 schema: IR.SchemaObject; 21}): Ast { 22 if (schemaExtractor && !schema.$ref) { 23 const extracted = schemaExtractor({ 24 meta: { 25 resource: 'definition', 26 resourceId: pathToJsonPointer(fromRef(state.path)), 27 }, 28 naming: plugin.config.definitions, 29 path: fromRef(state.path), 30 plugin, 31 schema, 32 }); 33 if (extracted !== schema) schema = extracted; 34 } 35 36 if (schema.$ref) { 37 const query: SymbolMeta = { 38 category: 'schema', 39 resource: 'definition', 40 resourceId: schema.$ref, 41 tool: 'pydantic', 42 }; 43 const refSymbol = plugin.referenceSymbol(query); 44 const refName = typeof refSymbol === 'string' ? refSymbol : refSymbol.name; 45 46 return { 47 // expression: $.expr(refName), 48 fieldConstraints: optional ? { default: null } : undefined, 49 hasLazyExpression: !plugin.isSymbolRegistered(query), 50 models: [], 51 // pipes: [], 52 typeAnnotation: refName, 53 }; 54 } 55 56 if (schema.type) { 57 const typeAst = irSchemaWithTypeToAst({ 58 plugin, 59 schema: schema as SchemaWithType, 60 state, 61 }); 62 63 const constraints: Record<string, unknown> = {}; 64 if (optional) { 65 constraints.default = null; 66 } 67 if (schema.default !== undefined) { 68 constraints.default = schema.default; 69 } 70 if (schema.description) { 71 constraints.description = schema.description; 72 } 73 74 return { 75 ...typeAst, 76 fieldConstraints: { ...typeAst.fieldConstraints, ...constraints }, 77 // pipes: [], 78 }; 79 } 80 81 if (schema.items) { 82 schema = deduplicateSchema({ schema }); 83 84 if (schema.items) { 85 const itemsAnnotations: string[] = []; 86 const itemsConstraints: Record<string, unknown>[] = []; 87 88 for (const item of schema.items) { 89 const itemAst = irSchemaToAst({ 90 plugin, 91 schema: item, 92 state: { 93 ...state, 94 path: ref([...fromRef(state.path), 'items']), 95 }, 96 }); 97 itemsAnnotations.push(itemAst.typeAnnotation); 98 if (itemAst.fieldConstraints) { 99 itemsConstraints.push(itemAst.fieldConstraints); 100 } 101 } 102 103 const unionType = itemsAnnotations.join(' | '); 104 return { 105 // expression: $.expr(`list[${unionType}]`), 106 fieldConstraints: itemsConstraints.length > 0 ? itemsConstraints[0] : undefined, 107 hasLazyExpression: false, 108 models: [], 109 // pipes: [], 110 typeAnnotation: `list[${unionType}]`, 111 }; 112 } 113 } 114 115 return { 116 // expression: $.expr('Any'), 117 hasLazyExpression: false, 118 models: [], 119 // pipes: [], 120 typeAnnotation: 'Any', 121 }; 122} 123 124export const handlerV2: PydanticPlugin['Handler'] = ({ plugin }) => { 125 plugin.symbol('Any', { 126 external: 'typing', 127 importKind: 'named', 128 meta: { 129 category: 'external', 130 resource: 'typing.Any', 131 }, 132 }); 133 plugin.symbol('BaseModel', { 134 external: 'pydantic', 135 importKind: 'named', 136 meta: { 137 category: 'external', 138 resource: 'pydantic.BaseModel', 139 }, 140 }); 141 plugin.symbol('ConfigDict', { 142 external: 'pydantic', 143 importKind: 'named', 144 meta: { 145 category: 'external', 146 resource: 'pydantic.ConfigDict', 147 }, 148 }); 149 plugin.symbol('Field', { 150 external: 'pydantic', 151 importKind: 'named', 152 meta: { 153 category: 'external', 154 resource: 'pydantic.Field', 155 }, 156 }); 157 plugin.symbol('Literal', { 158 external: 'typing', 159 importKind: 'named', 160 meta: { 161 category: 'external', 162 resource: 'typing.Literal', 163 }, 164 }); 165 plugin.symbol('Optional', { 166 external: 'typing', 167 importKind: 'named', 168 meta: { 169 category: 'external', 170 resource: 'typing.Optional', 171 }, 172 }); 173 174 const processor = createProcessor(plugin); 175 176 plugin.forEach('operation', 'parameter', 'requestBody', 'schema', 'webhook', (event) => { 177 switch (event.type) { 178 case 'parameter': 179 processor.process({ 180 meta: { 181 resource: 'definition', 182 resourceId: pathToJsonPointer(event._path), 183 }, 184 naming: plugin.config.definitions, 185 path: event._path, 186 plugin, 187 schema: event.parameter.schema, 188 tags: event.tags, 189 }); 190 break; 191 case 'requestBody': 192 processor.process({ 193 meta: { 194 resource: 'definition', 195 resourceId: pathToJsonPointer(event._path), 196 }, 197 naming: plugin.config.definitions, 198 path: event._path, 199 plugin, 200 schema: event.requestBody.schema, 201 tags: event.tags, 202 }); 203 break; 204 case 'schema': 205 processor.process({ 206 meta: { 207 resource: 'definition', 208 resourceId: pathToJsonPointer(event._path), 209 }, 210 naming: plugin.config.definitions, 211 path: event._path, 212 plugin, 213 schema: event.schema, 214 tags: event.tags, 215 }); 216 break; 217 } 218 }); 219};