Skip to main content

@abapify/openai-codegen

Deterministic OpenAPI → ABAP client code generator. Reads an OpenAPI 3.x spec and emits three global ABAP interfaces, one global exception class, and one thin implementation class bundled with a small set of local helper classes. The result targets SAP BTP / Steampunk (s4-cloud) and has zero Z-dependencies — the bundled locals wrap kernel APIs (cl_web_http_client_manager, cl_http_destination_provider, /ui2/cl_json, cl_abap_conv_codepage) only.

Re-running the generator with the same inputs produces byte-identical output; generated artifacts are safe to commit and diff.

Install

bun add -D @abapify/openai-codegen
bunx openai-codegen --help

Quick start

bunx openai-codegen \
--input ./spec/openapi.json \
--out ./generated/abapgit \
--base petstore3 \
--format abapgit \
--target s4-cloud \
--description "Petstore v3 client"

With the default abapgit layout this writes 11 files:

generated/abapgit/
├── package.devc.xml
└── src/
├── zif_petstore3_types.intf.abap # Layer 1 — TYPES interface
├── zif_petstore3_types.intf.xml
├── zif_petstore3.intf.abap # Layer 2 — operations interface
├── zif_petstore3.intf.xml
├── zcx_petstore3_error.clas.abap # global exception class
├── zcx_petstore3_error.clas.xml
├── zcl_petstore3.clas.abap # Layer 3 — thin implementation
├── zcl_petstore3.clas.xml
├── zcl_petstore3.clas.locals_def.abap # bundled helper locals
└── zcl_petstore3.clas.locals_imp.abap

Architecture

ZIF_<BASE>_TYPES ── all schemas as ABAP TYPES (+ @openapi-schema markers)

│ zif_<base>_types=>…

ZIF_<BASE> ── all operations as typed METHODS (+ @openapi-operation markers)
│ RAISING ZCX_<BASE>_ERROR

ZCL_<BASE> ── implements ZIF_<BASE>
one private attribute: client TYPE REF TO lcl_http
each method = fetch(...) + CASE response->status( )
─────────────────────────────────────────────
local classes (same .clas file, locals_def/imp):
lcl_http (HTTP transport)
lcl_response (status / body / headers)
json (stringify / render / parse)
lcl_json_parser (internal JSON parser)

ZCX_<BASE>_ERROR ── inherits cx_static_check
carries status / description / body / headers

The implementation class is intentionally minimal: exactly one private attribute (client), no private helper methods besides constructor, and each operation method is one fetch call followed by a CASE block that decodes the success branch via json=>parse( … )->to( … ) and raises ZCX_<BASE>_ERROR for every other status.

Schema TYPES intentionally live on their own interface — not on the class. ABAP's unified component namespace for types / attributes / methods on a class would otherwise force awkward name mangling; putting types on ZIF_<BASE>_TYPES keeps both the implementation class and the operations interface collision-free.

Generated API surface

Types interface

"! Generated types for ZCL_PETSTORE3.
INTERFACE ZIF_PETSTORE3_TYPES PUBLIC.
"! @openapi-schema Pet
TYPES: BEGIN OF pet,
id TYPE int8,
name TYPE string,
category TYPE category,
photo_urls TYPE STANDARD TABLE OF string WITH EMPTY KEY,
tags TYPE STANDARD TABLE OF tag WITH EMPTY KEY,
status TYPE string,
END OF pet.
ENDINTERFACE.

Operations interface

INTERFACE zif_petstore3 PUBLIC.
"! @openapi-operation getPetById
"! @openapi-path GET /pet/{petId}
"! Find pet by ID.
METHODS get_pet_by_id
IMPORTING pet_id TYPE int8
RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
RAISING zcx_petstore3_error.
ENDINTERFACE.

Implementation class

CLASS ZCL_PETSTORE3 DEFINITION PUBLIC CREATE PUBLIC.
INTERFACES zif_petstore3.
PUBLIC SECTION.
METHODS constructor
IMPORTING
destination TYPE string
server TYPE string DEFAULT '/api/v3'.
PRIVATE SECTION.
DATA client TYPE REF TO lcl_http.
ENDCLASS.

CLASS ZCL_PETSTORE3 IMPLEMENTATION.
METHOD constructor.
client = NEW lcl_http( destination = destination server = server ).
ENDMETHOD.

METHOD zif_petstore3~add_pet.
DATA(response) = client->fetch(
method = 'POST'
path = '/pet'
body = json=>stringify( body )
headers = VALUE #( ( name = 'Content-Type' value = 'application/json' ) ) ).
CASE response->status( ).
WHEN 200.
json=>parse( response->body( ) )->to( REF #( pet ) ).
WHEN OTHERS.
RAISE EXCEPTION NEW zcx_petstore3_error(
status = response->status( )
description = 'Unexpected error'
body = response->body( ) ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

Bundled locals

" Transport — lcl_http + lcl_response
DATA(response) = client->fetch(
method = 'GET'
path = '/pet/123'
headers = VALUE #( ( name = 'Accept' value = 'application/json' ) ) ).
response->status( ). response->body( ). response->text( ).
response->header( 'etag' ). response->headers( ).

" JSON — json helper (mirrors abapify/json)
DATA(payload) = json=>stringify( pet ).
json=>parse( response->body( ) )->to( REF #( pet ) ).

lcl_http->fetch is transport-only — no JSON, no status-code dispatch, no operation awareness. All mapping lives in the per-operation method in ZCL_<BASE>. The json helper API matches abapify/json and internally wraps /ui2/cl_json plus cl_abap_conv_codepage.

Configuration

CLI flagLibrary field (NamesConfig)Default
--base <name>baserequired unless all 4 overrides given
--types-interface <name>typesInterfaceZIF_<BASE>_TYPES
--operations-interface <name>operationsInterfaceZIF_<BASE>
--class-name <name>implementationClassZCL_<BASE>
--exception-class <name>exceptionClassZCX_<BASE>_ERROR
(library only)localHttpClasslcl_http
(library only)localResponseClasslcl_response
(library only)localJsonClassjson
(library only)localJsonParserClasslcl_json_parser

Other flags: --input, --out, --format (abapgit / gcts / abapgit,gcts), --target (s4-cloud only in v1), --description.

Library usage

import { generate } from '@abapify/openai-codegen';

const result = await generate({
input: './spec/openapi.json',
outDir: './generated/abapgit',
format: 'abapgit',
target: 's4-cloud',
names: { base: 'petstore3' },
description: 'Petstore v3 client',
});

console.log(result.files); // sorted, deduped relative paths
console.log(result.typeCount); // entries in the types interface
console.log(result.operationCount); // methods in the operations interface
console.log(result.resolvedNames); // the final ABAP global + local names

Pipeline

OpenAPI spec
→ loadSpec ($ref-deref via @apidevtools/swagger-parser)
→ planTypes (topological sort + cycle breaking)
→ resolveNames (NamesConfig → ResolvedNames; validates idents)
→ emitTypesInterface (Layer 1 — ZIF_<BASE>_TYPES AST)
→ emitOperationsInterface (Layer 2 — ZIF_<BASE> AST)
→ emitExceptionClass (ZCX_<BASE>_ERROR AST)
→ emitImplementationClass (Layer 3 — ZCL_<BASE> AST)
→ emitLocalClasses (locals_def + locals_imp from templates)
→ print (4 × deterministic print, via @abapify/abap-ast)
→ writeClientBundle (abapgit or gcts on-disk tree + envelopes)

OpenAPI → ABAP type mapping

JSON SchemaABAP
booleanabap_bool
integer / integer format:int64i / int8
number (default / float / double)decfloat34 / f
string (default)string
string, format: dated
string, format: date-timetimestampl
string, format: uuidsysuuid_x16
string, format: byte / binaryxstring
array of XSTANDARD TABLE OF <X> WITH EMPTY KEY
object (properties)BEGIN OF <name> … END OF <name>.
object with additionalProperties only_map table of { key, value } entries
allOfFlattened into one structure
oneOf / anyOfUnion merge — fields from every branch
$ref (forward)NamedTypeRef
$ref (back-edge, cyclic)REF TO data + "! @openapi-ref <field>:<Target>
nullable: trueSame ABAP type (nullability preserved at (de)serialise)

Round-trip markers

MarkerAttached toMeaning
"! @openapi-schema <Name>TYPES declarationOriginal JSON-Schema component name.
"! @openapi-operation <operationId>METHODSOriginal OpenAPI operationId.
"! @openapi-path <VERB> <path>METHODSHTTP verb + templated path.
"! @openapi-ref <field>:<Target>TYPES fieldField broken to REF TO data references <Target>.

Markers follow the printer's ABAPDoc rules from @abapify/abap-ast: plain "! <line> above the declaration, never rewritten or wrapped.

Deploying to BTP Steampunk

bunx adt auth login
bunx openai-codegen \
--input ./spec/openapi.json \
--out ./generated/abapgit \
--format abapgit \
--base petstore3
bunx adt deploy \
--source ./generated/abapgit \
--package ZMY_PACKAGE \
--activate --unlock

S_ABPLNGVS is granted per package on BTP. Use a user-owned Z* package (for example ZMY_PACKAGE or ZPEPL) — $TMP often fails with HTTP 403. See samples/petstore3-client/e2e/README.md for the full walk-through.

Not yet supported (v1)

  • OpenAPI webhooks and callbacks.
  • Server-Sent Events / streaming responses.
  • Full OAuth2 authorization_code / client_credentials flows (apiKey, http bearer, http basic are emitted; oauth2 exposes an overridable hook only).
  • .aclass DSL parser — tracked in @abapify/abap-ast.
  • Per-target runtime bundles beyond s4-cloud — profile slots exist for s4-onprem-modern and on-prem-classic, but only s4-cloud emits in v1.

See also