fork of hey-api/openapi-ts because I need some additional things
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}