@abapify/openai-codegen 0.0.0 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +344 -1
- package/package.json +49 -3
package/README.md
CHANGED
|
@@ -1,3 +1,346 @@
|
|
|
1
1
|
# @abapify/openai-codegen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Deterministic OpenAPI → ABAP client code generator.** Reads an OpenAPI
|
|
4
|
+
3.x spec and emits three global ABAP interfaces, one global exception
|
|
5
|
+
class, and one thin implementation class bundled with a small set of
|
|
6
|
+
local helper classes. The result targets SAP BTP / Steampunk
|
|
7
|
+
(`s4-cloud`) out of the box and has **zero Z-dependencies** — the
|
|
8
|
+
bundled locals wrap kernel APIs (`cl_web_http_client_manager`,
|
|
9
|
+
`cl_http_destination_provider`, `/ui2/cl_json`, `cl_abap_conv_codepage`,
|
|
10
|
+
…) only.
|
|
11
|
+
|
|
12
|
+
Re-running the generator with the same inputs produces byte-identical
|
|
13
|
+
output. Generated artifacts are safe to commit and diff.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add -D @abapify/openai-codegen
|
|
19
|
+
bunx openai-codegen --help
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bunx openai-codegen \
|
|
26
|
+
--input ./petstore3.json \
|
|
27
|
+
--out ./out/abapgit \
|
|
28
|
+
--base petstore3 \
|
|
29
|
+
--format abapgit \
|
|
30
|
+
--target s4-cloud \
|
|
31
|
+
--description "Petstore v3 client"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
With the default `abapgit` layout this writes **11 files**:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
out/abapgit/
|
|
38
|
+
├── package.devc.xml
|
|
39
|
+
└── src/
|
|
40
|
+
├── zif_petstore3_types.intf.abap # Layer 1 — TYPES interface
|
|
41
|
+
├── zif_petstore3_types.intf.xml
|
|
42
|
+
├── zif_petstore3.intf.abap # Layer 2 — operations interface
|
|
43
|
+
├── zif_petstore3.intf.xml
|
|
44
|
+
├── zcx_petstore3_error.clas.abap # global exception class
|
|
45
|
+
├── zcx_petstore3_error.clas.xml
|
|
46
|
+
├── zcl_petstore3.clas.abap # Layer 3 — thin implementation
|
|
47
|
+
├── zcl_petstore3.clas.xml
|
|
48
|
+
├── zcl_petstore3.clas.locals_def.abap # bundled helper locals
|
|
49
|
+
└── zcl_petstore3.clas.locals_imp.abap
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Architecture
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
ZIF_<BASE>_TYPES ── all schemas as ABAP TYPES (+ @openapi-schema markers)
|
|
56
|
+
▲
|
|
57
|
+
│
|
|
58
|
+
ZIF_<BASE> ── all operations as typed METHODS (+ @openapi-operation markers)
|
|
59
|
+
│ ── RAISING ZCX_<BASE>_ERROR
|
|
60
|
+
│
|
|
61
|
+
ZCL_<BASE> ── implements ZIF_<BASE>
|
|
62
|
+
one private attribute: client TYPE REF TO lcl_http
|
|
63
|
+
each method body = fetch(...) + CASE response->status( )
|
|
64
|
+
─────────────────────────────────────────────
|
|
65
|
+
local classes (same .clas file, locals_def/imp):
|
|
66
|
+
lcl_http (HTTP transport)
|
|
67
|
+
lcl_response (status / body / headers)
|
|
68
|
+
json (stringify / render / parse)
|
|
69
|
+
lcl_json_parser (internal JSON parser)
|
|
70
|
+
|
|
71
|
+
ZCX_<BASE>_ERROR ── inherits cx_static_check
|
|
72
|
+
carries status / description / body / headers
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The implementation class is deliberately minimal:
|
|
76
|
+
|
|
77
|
+
- exactly one private attribute (`client`),
|
|
78
|
+
- no private helper methods besides `constructor`,
|
|
79
|
+
- each operation method is **one `fetch` call** followed by a `CASE`
|
|
80
|
+
block that decodes the success branch via `json=>parse( ... )->to( … )`
|
|
81
|
+
and raises `ZCX_<BASE>_ERROR` for every other status.
|
|
82
|
+
|
|
83
|
+
Schema `TYPES` intentionally live on their own **interface** — not on
|
|
84
|
+
the class. ABAP's unified component namespace for types / attributes /
|
|
85
|
+
methods on a class would otherwise force awkward name mangling; putting
|
|
86
|
+
types on `ZIF_<BASE>_TYPES` keeps the main class and the operations
|
|
87
|
+
interface collision-free.
|
|
88
|
+
|
|
89
|
+
## Generated API surface
|
|
90
|
+
|
|
91
|
+
### Layer 1 — types interface (`zif_petstore3_types.intf.abap`)
|
|
92
|
+
|
|
93
|
+
```abap
|
|
94
|
+
"! Generated types for ZCL_PETSTORE3.
|
|
95
|
+
INTERFACE ZIF_PETSTORE3_TYPES PUBLIC.
|
|
96
|
+
"! @openapi-schema Category
|
|
97
|
+
TYPES: BEGIN OF category,
|
|
98
|
+
id TYPE int8,
|
|
99
|
+
name TYPE string,
|
|
100
|
+
END OF category.
|
|
101
|
+
"! @openapi-schema Pet
|
|
102
|
+
TYPES: BEGIN OF pet,
|
|
103
|
+
id TYPE int8,
|
|
104
|
+
name TYPE string,
|
|
105
|
+
category TYPE category,
|
|
106
|
+
photo_urls TYPE STANDARD TABLE OF string WITH EMPTY KEY,
|
|
107
|
+
tags TYPE STANDARD TABLE OF tag WITH EMPTY KEY,
|
|
108
|
+
status TYPE string,
|
|
109
|
+
END OF pet.
|
|
110
|
+
ENDINTERFACE.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Layer 2 — operations interface (`zif_petstore3.intf.abap`)
|
|
114
|
+
|
|
115
|
+
```abap
|
|
116
|
+
INTERFACE zif_petstore3 PUBLIC.
|
|
117
|
+
"! @openapi-operation addPet
|
|
118
|
+
"! @openapi-path POST /pet
|
|
119
|
+
"! Add a new pet to the store.
|
|
120
|
+
METHODS add_pet
|
|
121
|
+
IMPORTING body TYPE zif_petstore3_types=>pet
|
|
122
|
+
RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
|
|
123
|
+
RAISING zcx_petstore3_error.
|
|
124
|
+
"! @openapi-operation getPetById
|
|
125
|
+
"! @openapi-path GET /pet/{petId}
|
|
126
|
+
"! Find pet by ID.
|
|
127
|
+
METHODS get_pet_by_id
|
|
128
|
+
IMPORTING pet_id TYPE int8
|
|
129
|
+
RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
|
|
130
|
+
RAISING zcx_petstore3_error.
|
|
131
|
+
ENDINTERFACE.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Layer 3 — implementation class (`zcl_petstore3.clas.abap`)
|
|
135
|
+
|
|
136
|
+
```abap
|
|
137
|
+
CLASS ZCL_PETSTORE3 DEFINITION PUBLIC CREATE PUBLIC.
|
|
138
|
+
INTERFACES zif_petstore3.
|
|
139
|
+
PUBLIC SECTION.
|
|
140
|
+
METHODS constructor
|
|
141
|
+
IMPORTING
|
|
142
|
+
destination TYPE string
|
|
143
|
+
server TYPE string DEFAULT '/api/v3'.
|
|
144
|
+
PRIVATE SECTION.
|
|
145
|
+
DATA client TYPE REF TO lcl_http.
|
|
146
|
+
ENDCLASS.
|
|
147
|
+
|
|
148
|
+
CLASS ZCL_PETSTORE3 IMPLEMENTATION.
|
|
149
|
+
METHOD constructor.
|
|
150
|
+
client = NEW lcl_http( destination = destination server = server ).
|
|
151
|
+
ENDMETHOD.
|
|
152
|
+
|
|
153
|
+
METHOD zif_petstore3~add_pet.
|
|
154
|
+
DATA(response) = client->fetch(
|
|
155
|
+
method = 'POST'
|
|
156
|
+
path = '/pet'
|
|
157
|
+
body = json=>stringify( body )
|
|
158
|
+
headers = VALUE #( ( name = 'Content-Type' value = 'application/json' ) ) ).
|
|
159
|
+
CASE response->status( ).
|
|
160
|
+
WHEN 200.
|
|
161
|
+
json=>parse( response->body( ) )->to( REF #( pet ) ).
|
|
162
|
+
WHEN 400.
|
|
163
|
+
RAISE EXCEPTION NEW zcx_petstore3_error(
|
|
164
|
+
status = 400
|
|
165
|
+
description = 'Invalid input'
|
|
166
|
+
body = response->body( ) ).
|
|
167
|
+
WHEN OTHERS.
|
|
168
|
+
RAISE EXCEPTION NEW zcx_petstore3_error(
|
|
169
|
+
status = response->status( )
|
|
170
|
+
description = 'Unexpected error'
|
|
171
|
+
body = response->body( ) ).
|
|
172
|
+
ENDCASE.
|
|
173
|
+
ENDMETHOD.
|
|
174
|
+
ENDCLASS.
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Bundled locals (`zcl_petstore3.clas.locals_def.abap` / `locals_imp.abap`)
|
|
178
|
+
|
|
179
|
+
The local classes expose a small, stable API that is **transport-only**
|
|
180
|
+
for HTTP and **typed-by-reference** for JSON:
|
|
181
|
+
|
|
182
|
+
```abap
|
|
183
|
+
" Transport (HTTP): lcl_http / lcl_response
|
|
184
|
+
DATA(response) = client->fetch(
|
|
185
|
+
method = 'GET'
|
|
186
|
+
path = '/pet/123'
|
|
187
|
+
query = VALUE #( ( name = 'detail' value = 'full' ) )
|
|
188
|
+
headers = VALUE #( ( name = 'Accept' value = 'application/json' ) ) ).
|
|
189
|
+
response->status( ). " -> i
|
|
190
|
+
response->body( ). " -> xstring
|
|
191
|
+
response->text( ). " -> string (decoded)
|
|
192
|
+
response->header( 'etag' ).
|
|
193
|
+
response->headers( ).
|
|
194
|
+
|
|
195
|
+
" JSON: json helper (mirrors abapify/json)
|
|
196
|
+
DATA(payload) = json=>stringify( pet ). " string
|
|
197
|
+
DATA(raw) = json=>render( pet ). " xstring
|
|
198
|
+
json=>parse( response->body( ) )->to( REF #( pet ) ).
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`lcl_http->fetch` is deliberately ignorant of JSON, status codes, and
|
|
202
|
+
operation identities. All policy lives in the per-operation method body
|
|
203
|
+
in `ZCL_<BASE>`. The JSON API surface mirrors
|
|
204
|
+
[`abapify/json`](https://github.com/abapify) and wraps `/ui2/cl_json` +
|
|
205
|
+
`cl_abap_conv_codepage`.
|
|
206
|
+
|
|
207
|
+
## Configuration
|
|
208
|
+
|
|
209
|
+
All four global names and all four local names are configurable. The
|
|
210
|
+
simplest path is to provide `--base`; every name then derives from it.
|
|
211
|
+
Any individual override wins.
|
|
212
|
+
|
|
213
|
+
| CLI flag | Library field (`NamesConfig`) | Default |
|
|
214
|
+
| ------------------------------- | ----------------------------- | --------------------------------------- |
|
|
215
|
+
| `--base <name>` | `base` | _required unless all 4 overrides given_ |
|
|
216
|
+
| `--types-interface <name>` | `typesInterface` | `ZIF_<BASE>_TYPES` |
|
|
217
|
+
| `--operations-interface <name>` | `operationsInterface` | `ZIF_<BASE>` |
|
|
218
|
+
| `--class-name <name>` | `implementationClass` | `ZCL_<BASE>` |
|
|
219
|
+
| `--exception-class <name>` | `exceptionClass` | `ZCX_<BASE>_ERROR` |
|
|
220
|
+
| _(library only)_ | `localHttpClass` | `lcl_http` |
|
|
221
|
+
| _(library only)_ | `localResponseClass` | `lcl_response` |
|
|
222
|
+
| _(library only)_ | `localJsonClass` | `json` |
|
|
223
|
+
| _(library only)_ | `localJsonParserClass` | `lcl_json_parser` |
|
|
224
|
+
|
|
225
|
+
Other flags:
|
|
226
|
+
|
|
227
|
+
| Flag | Purpose |
|
|
228
|
+
| ------------------------- | ---------------------------------------------------------------- |
|
|
229
|
+
| `-i, --input <path>` | Path / URL / file for the OpenAPI spec (JSON or YAML). Required. |
|
|
230
|
+
| `-o, --out <dir>` | Output directory. Required. |
|
|
231
|
+
| `-f, --format <list>` | `abapgit` (default), `gcts`, or `abapgit,gcts`. |
|
|
232
|
+
| `-t, --target <profile>` | Target profile (`s4-cloud` only, default). |
|
|
233
|
+
| `-d, --description <txt>` | Description stored in the generated `.clas.xml` / `.intf.xml`. |
|
|
234
|
+
|
|
235
|
+
## Library usage
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { generate } from '@abapify/openai-codegen';
|
|
239
|
+
|
|
240
|
+
const result = await generate({
|
|
241
|
+
input: './petstore3.json',
|
|
242
|
+
outDir: './out/abapgit',
|
|
243
|
+
format: 'abapgit',
|
|
244
|
+
target: 's4-cloud',
|
|
245
|
+
names: { base: 'petstore3' },
|
|
246
|
+
description: 'Petstore v3 client',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
console.log(result.files); // sorted, deduped relative paths
|
|
250
|
+
console.log(result.typeCount, result.operationCount);
|
|
251
|
+
console.log(result.resolvedNames);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Pipeline
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
OpenAPI spec
|
|
258
|
+
→ loadSpec ($ref-deref via @apidevtools/swagger-parser)
|
|
259
|
+
→ planTypes (topological sort of schemas + cycle detection)
|
|
260
|
+
→ resolveNames (NamesConfig → ResolvedNames; validates ABAP idents)
|
|
261
|
+
→ emitTypesInterface (Layer 1 — ZIF_<BASE>_TYPES AST)
|
|
262
|
+
→ emitOperationsInterface (Layer 2 — ZIF_<BASE> AST)
|
|
263
|
+
→ emitExceptionClass (ZCX_<BASE>_ERROR AST)
|
|
264
|
+
→ emitImplementationClass (Layer 3 — ZCL_<BASE> AST)
|
|
265
|
+
→ emitLocalClasses (locals_def + locals_imp templates)
|
|
266
|
+
→ print (four AST → ABAP source, via @abapify/abap-ast)
|
|
267
|
+
→ writeClientBundle (abapgit or gcts on-disk tree + envelopes)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Type mapping
|
|
271
|
+
|
|
272
|
+
| JSON Schema | ABAP |
|
|
273
|
+
| ----------------------------------------- | ----------------------------------------------------------- |
|
|
274
|
+
| `boolean` | `abap_bool` |
|
|
275
|
+
| `integer` / `integer, format: int64` | `i` / `int8` |
|
|
276
|
+
| `number` (default / `float` / `double`) | `decfloat34` / `f` |
|
|
277
|
+
| `string` (default) | `string` |
|
|
278
|
+
| `string, format: date` | `d` |
|
|
279
|
+
| `string, format: date-time` | `timestampl` |
|
|
280
|
+
| `string, format: uuid` | `sysuuid_x16` |
|
|
281
|
+
| `string, format: byte` / `binary` | `xstring` |
|
|
282
|
+
| `array of X` | `STANDARD TABLE OF <X> WITH EMPTY KEY` |
|
|
283
|
+
| `object` (properties) | `BEGIN OF <name> … END OF <name>.` |
|
|
284
|
+
| `object` with `additionalProperties` only | `_map` table of `BEGIN OF entry, key, value, END OF entry` |
|
|
285
|
+
| `enum` | Field typed as base scalar; values documented via ABAPDoc. |
|
|
286
|
+
| `allOf` | Flattened into one structure. |
|
|
287
|
+
| `oneOf` / `anyOf` | Union merge — fields from every branch, optional semantics. |
|
|
288
|
+
| `$ref` (forward) | `NamedTypeRef` into the existing plan entry. |
|
|
289
|
+
| `$ref` (back-edge — cyclic) | `REF TO data` with `"! @openapi-ref <field>:<Target>`. |
|
|
290
|
+
| `nullable: true` | Same ABAP type (nullability preserved at (de)serialise). |
|
|
291
|
+
|
|
292
|
+
Types are emitted in semantic topological order. Back-edges introduced
|
|
293
|
+
by cyclic schemas are broken with a `REF TO data` plus a structured
|
|
294
|
+
`@openapi-ref` marker, so a future reader can reconstruct the original
|
|
295
|
+
reference target.
|
|
296
|
+
|
|
297
|
+
## Round-trip markers
|
|
298
|
+
|
|
299
|
+
Every generated ABAP declaration carries an ABAPDoc block that a
|
|
300
|
+
downstream tool (for example a future `.aclass` parser) can use to
|
|
301
|
+
rebuild the originating OpenAPI shape:
|
|
302
|
+
|
|
303
|
+
| Marker | Attached to | Meaning |
|
|
304
|
+
| ------------------------------------- | ------------------- | ---------------------------------------------------- |
|
|
305
|
+
| `"! @openapi-schema <Name>` | `TYPES` declaration | Original JSON-Schema component name. |
|
|
306
|
+
| `"! @openapi-operation <operationId>` | `METHODS` | Original OpenAPI `operationId`. |
|
|
307
|
+
| `"! @openapi-path <VERB> <path>` | `METHODS` | HTTP verb + templated path. |
|
|
308
|
+
| `"! @openapi-ref <field>:<Target>` | `TYPES` field | Field broken to `REF TO data` references `<Target>`. |
|
|
309
|
+
|
|
310
|
+
Markers are plain lines under the printer's ABAPDoc rules; they are
|
|
311
|
+
never rewritten or wrapped.
|
|
312
|
+
|
|
313
|
+
## Deploying to BTP Steampunk
|
|
314
|
+
|
|
315
|
+
Short recipe (see
|
|
316
|
+
[`samples/petstore3-client/e2e/README.md`](../../samples/petstore3-client/e2e/README.md)
|
|
317
|
+
for the authoritative walk-through):
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
bunx adt auth login
|
|
321
|
+
bunx openai-codegen \
|
|
322
|
+
--input ./spec/openapi.json \
|
|
323
|
+
--out ./generated/abapgit \
|
|
324
|
+
--format abapgit \
|
|
325
|
+
--base petstore3
|
|
326
|
+
bunx adt deploy --source ./generated/abapgit --package ZMY_PACKAGE --activate --unlock
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`S_ABPLNGVS` is granted per package on BTP. Deploy into a user-owned
|
|
330
|
+
`Z*` package (for example `ZMY_PACKAGE` or `ZPEPL`); targeting `$TMP`
|
|
331
|
+
often returns HTTP 403 on BTP.
|
|
332
|
+
|
|
333
|
+
## Not yet supported
|
|
334
|
+
|
|
335
|
+
- OpenAPI webhooks and callbacks.
|
|
336
|
+
- Server-Sent Events / streaming responses.
|
|
337
|
+
- Full OAuth2 `authorization_code` / `client_credentials` flows
|
|
338
|
+
(`apiKey`, `http bearer`, `http basic` are emitted; `oauth2` exposes
|
|
339
|
+
an overridable hook only).
|
|
340
|
+
- `.aclass` DSL parser — tracked in `@abapify/abap-ast`.
|
|
341
|
+
- Per-target runtime bundles beyond `s4-cloud` (profile slots exist for
|
|
342
|
+
`s4-onprem-modern` and `on-prem-classic`; only `s4-cloud` emits in v1).
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abapify/openai-codegen",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
}
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.3.5",
|
|
7
|
+
"description": "Deterministic OpenAPI → ABAP client code generator. Emits a single zero-dependency ABAP class per OpenAPI spec, targeting SAP Cloud / on-prem profiles, packaged as abapGit or gCTS.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"openai-codegen": "./dist/cli.mjs"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./dist/index.mjs",
|
|
15
|
+
"./cli": "./dist/cli.mjs",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@abapify/abap-ast": "0.3.5",
|
|
20
|
+
"@apidevtools/swagger-parser": "^10.1.0",
|
|
21
|
+
"commander": "^11.1.0",
|
|
22
|
+
"fast-xml-parser": "^5.5.0",
|
|
23
|
+
"yaml": "^2.5.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"openapi",
|
|
31
|
+
"swagger",
|
|
32
|
+
"abap",
|
|
33
|
+
"codegen",
|
|
34
|
+
"sap",
|
|
35
|
+
"steampunk"
|
|
36
|
+
],
|
|
37
|
+
"author": "abapify",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@abaplint/core": "^2.118.12"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/abapify/adt-cli.git",
|
|
45
|
+
"directory": "packages/openai-codegen"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/abapify/adt-cli/tree/main/packages/openai-codegen#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/abapify/adt-cli/issues"
|
|
50
|
+
}
|
|
51
|
+
}
|