fork of hey-api/openapi-ts because I need some additional things
at feat/use-query-options-param 207 lines 5.5 kB view raw
1import type { 2 StructureItem, 3 StructureNode, 4 StructureShell, 5 Symbol, 6 SymbolMeta, 7} from '@hey-api/codegen-core'; 8import type { IR } from '@hey-api/shared'; 9import { applyNaming, toCase } from '@hey-api/shared'; 10 11import { $ } from '../../../../py-dsl'; 12import { createOperationComment } from '../../../shared/utils/operation'; 13import type { HeyApiSdkPlugin } from '../types'; 14 15export interface OperationItem { 16 operation: IR.OperationObject; 17 path: ReadonlyArray<string | number>; 18 tags: ReadonlyArray<string> | undefined; 19} 20 21export const source = globalThis.Symbol('@hey-api/python-sdk'); 22 23function attachComment<T extends ReturnType<typeof $.func>>(args: { 24 node: T; 25 operation: IR.OperationObject; 26 plugin: HeyApiSdkPlugin['Instance']; 27}): T { 28 const { node, operation, plugin } = args; 29 return node.$if(plugin.config.comments && createOperationComment(operation), (n, v) => 30 n.doc(v), 31 ) as T; 32} 33 34function createShellMeta(node: StructureNode): SymbolMeta { 35 return { 36 category: 'utility', 37 resource: 'class', 38 resourceId: node.getPath().join('.'), 39 tool: 'sdk', 40 }; 41} 42 43function createFnSymbol( 44 plugin: HeyApiSdkPlugin['Instance'], 45 item: StructureItem & { data: OperationItem }, 46): Symbol { 47 const { operation, path, tags } = item.data; 48 const name = item.location[item.location.length - 1]!; 49 return plugin.symbol(applyNaming(name, plugin.config.operations.methodName), { 50 meta: { 51 category: 'sdk', 52 path, 53 resource: 'operation', 54 resourceId: operation.id, 55 tags, 56 tool: 'sdk', 57 }, 58 }); 59} 60 61function childToNode( 62 resource: StructureNode, 63 plugin: HeyApiSdkPlugin['Instance'], 64): ReadonlyArray<ReturnType<typeof $.func>> { 65 const refChild = plugin.referenceSymbol(createShellMeta(resource)); 66 const memberNameStr = toCase( 67 refChild.name, 68 plugin.config.operations.methodName.casing ?? 'camelCase', 69 ); 70 const memberName = plugin.symbol(memberNameStr); 71 return [ 72 $.func( 73 memberName, 74 (f) => f.returns('None'), 75 // f.returns(refChild).do( 76 // $('this') 77 // .attr(privateName) 78 // .nullishAssign( 79 // $.new(refChild).args($.object().prop('client', $('this').attr('client'))), 80 // ) 81 // .return(), 82 // ), 83 ), 84 ]; 85} 86 87export function createShell(plugin: HeyApiSdkPlugin['Instance']): StructureShell { 88 return { 89 define: (node) => { 90 const symbol = plugin.symbol( 91 applyNaming( 92 node.name, 93 node.isRoot 94 ? plugin.config.operations.containerName 95 : plugin.config.operations.segmentName, 96 ), 97 { 98 meta: createShellMeta(node), 99 }, 100 ); 101 102 const symbolClient = plugin.external('client.Client'); 103 104 const c = $.class(symbol).extends(symbolClient); 105 106 const dependencies: Array<ReturnType<typeof $.class>> = []; 107 108 return { dependencies, node: c }; 109 }, 110 }; 111} 112 113function implementFn<T extends ReturnType<typeof $.func>>(args: { 114 node: T; 115 operation: IR.OperationObject; 116 plugin: HeyApiSdkPlugin['Instance']; 117}): T { 118 const { node, operation } = args; 119 120 // Build the URL path 121 const path = operation.path; 122 123 // Get the HTTP method (default to GET) 124 const method = operation.method?.toLowerCase() || 'get'; 125 126 // Create the client call expression: self.client.get(path) 127 // self is the Python equivalent of 'this' for instance methods 128 const selfExpr = $.id('self'); 129 const clientExpr = $.attr(selfExpr, 'client'); 130 const methodExpr = $.attr(clientExpr, method); 131 const clientCall = $.call(methodExpr, $.literal(path)); 132 133 // Return the client call 134 node.do($.return(clientCall)); 135 136 return node; 137} 138 139export function toNode( 140 model: StructureNode, 141 plugin: HeyApiSdkPlugin['Instance'], 142): { 143 dependencies?: Array<ReturnType<typeof $.class | typeof $.func>>; 144 nodes: ReadonlyArray<ReturnType<typeof $.class | typeof $.func>>; 145} { 146 if (model.virtual) { 147 const nodes: Array<ReturnType<typeof $.func>> = []; 148 for (const item of model.itemsFrom<OperationItem>(source)) { 149 const fnName = applyNaming( 150 String(item.location[item.location.length - 1]), 151 plugin.config.operations.methodName, 152 ); 153 const node = $.func(fnName); 154 node.do($.return($.id('None'))); 155 nodes.push(node); 156 } 157 return { nodes }; 158 } 159 160 if (!model.shell) { 161 return { nodes: [] }; 162 } 163 164 const nodes: Array<ReturnType<typeof $.class | typeof $.func>> = []; 165 const shell = model.shell.define(model); 166 const node = shell.node as ReturnType<typeof $.class | typeof $.func>; 167 168 let index = 0; 169 for (const item of model.itemsFrom<OperationItem>(source)) { 170 const { operation } = item.data; 171 if (node['~dsl'] === 'FuncPyDsl') { 172 // TODO: function? 173 } else { 174 if (index > 0 || node.hasBody) node.newline(); 175 const method = implementFn({ 176 node: $.func(createFnSymbol(plugin, item), (m) => 177 attachComment({ 178 node: m, 179 operation, 180 plugin, 181 }), 182 ), 183 operation, 184 plugin, 185 }); 186 node.do(method); 187 // exampleIntent(method, operation, plugin); 188 } 189 index += 1; 190 } 191 192 for (const child of model.children.values()) { 193 if (node['~dsl'] === 'FuncPyDsl') { 194 // TODO: function? 195 } else { 196 if (node.hasBody) node.newline(); 197 node.do(...childToNode(child, plugin)); 198 } 199 } 200 201 nodes.push(node); 202 203 return { 204 dependencies: shell.dependencies as Array<ReturnType<typeof $.class | typeof $.func>>, 205 nodes, 206 }; 207}