sgcOpenAPI 2026.9.0: die Überarbeitung des Parsers

· Versionen
sgcOpenAPI 2026.9.0, der OpenAPI-Parser und Codegenerator für Delphi

sgcOpenAPI 2026.9.0 ist das größte Release, das es für dieses Produkt bisher gegeben hat. Die Version davor kam mit der Form von Spezifikation zurecht, die die meisten Tutorials zeigen, und versagte bei allem anderen still und leise. Diesmal wurde der Parser Funktion für Funktion gegen die Spezifikationen OpenAPI 3.0, 3.1 und 3.2 und gegen echte veröffentlichte Dokumente geprüft, und das Ergebnis sind 9 neue Funktionen, 26 behobene Fehler und 5 bewusste Breaking Changes.

Kurz gesagt: Der generierte Client ist jetzt korrekt für Spezifikationen, die vorher Code erzeugt haben, der nicht kompilierte, oder schlimmer, Code, der kompilierte und die falsche URL aufrief.

Der Parser sagt Ihnen jetzt, was er nicht tun konnte

Der alte Parser hatte eine Art, Probleme zu melden, nämlich eine Exception auszulösen, und eine Art, mit allem anderen umzugehen, nämlich still weiterzumachen. Eine Operation, die er nicht generieren konnte, fehlte einfach in der Ausgabe, und Sie erfuhren davon, wenn Sie nach einer Methode suchten, die nicht da war.

Jedes Dokument kommt jetzt mit einer Warnings-Liste zurück. Ein fehlendes openapi- oder info-Element, ein Element mit dem falschen JSON-Typ, eine Operation, die nicht generiert werden konnte, eine nicht aufgelöste Path-Item-Referenz und ein JSON-Schema-Schlüsselwort, das gelesen, aber noch nicht berücksichtigt wird, werden alle dort festgehalten. Die Liste wird bei jedem Lesevorgang geleert, sodass ihr Inhalt zu dem Dokument gehört, das Sie gerade geparst haben.

uses
  sgcOpenAPI_Classes, sgcOpenAPI_Parser_Client_Pascal;

var
  oParser: TsgcOpenAPI_Parser_Client_Pascal;
  i: Integer;
begin
  oParser := TsgcOpenAPI_Parser_Client_Pascal.Create;
  Try
    oParser.OpenAPIClassName := 'TPetStoreClient';
    oParser.OpenAPINamespace := 'PetStore';
    oParser.OutputFileName := 'PetStoreClient.pas';

    oParser.ReadFromFile('petstore.json');

    for i := 0 to oParser.Warnings.Count - 1 do
      Memo1.Lines.Add('warning: ' + oParser.Warnings[i]);

    oParser.SaveToFile('PetStoreClient.pas');
  Finally
    oParser.Free;
  End;
end;

Setzen Sie OutputFileName, bevor Sie speichern. Eine Pascal-Unit kompiliert nur, wenn ihr deklarierter Name mit dem Basisnamen ihrer Datei übereinstimmt, und der Generator benannte die Unit früher nach dem Eingabedokument, sodass beim Generieren von MyClient.pas aus petstore.json eine Unit namens petstore entstand, die nicht kompilierte. Jetzt setzt sich der Ausgabename durch.

Er weiß, welche Version er liest

OpenAPI 3.0 und 3.1 sind sich bei Schlüsselwörtern uneinig, die denselben Namen tragen, und der alte Parser behandelte jedes Dokument als 3.0. Der deutlichste Fall ist exclusiveMinimum, das in 3.0 ein boolescher Modifikator für minimum ist und in 3.1 eine eigenständige Zahl. Liest man das eine als das andere, wird die Grenze falsch.

Die Version wird jetzt in einen Dialekt geparst, verfügbar über Dialect, DialectMajor und DialectMinor, und jedes Schlüsselwort, das sich unterscheidet, wird so gelesen, wie es seine eigene Version verlangt.

oParser.ReadFromFile('api.yaml');

case oParser.Dialect of
  oapiDialect30: ShowMessage('OpenAPI 3.0');
  oapiDialect31: ShowMessage('OpenAPI 3.1');
  oapiDialect32: ShowMessage('OpenAPI 3.2');
end;

Darüber hinaus bringt 3.1 webhooks, jsonSchemaDialect und components.pathItems, den Lizenz-identifier, das Sicherheitsschema mutualTLS, einen als Array deklarierten Typ wie ["string","null"] und ein als einfachen Boolean deklariertes Schema. All das wird unterstützt. Die Schlüsselwörter aus JSON Schema 2020-12, die der Codegenerator noch nicht umsetzt, werden in das Modell eingelesen und über Warnings gemeldet, sodass die Lücke sichtbar statt unsichtbar ist.

Aus 3.2 unterstützt der Parser die Operation query und die Map additionalOperations. Ein Pfad, der eines von beiden deklariert, erzeugt jetzt die passenden Methoden, die als POST mit einem X-HTTP-Method-Override-Header gesendet werden.

Parameter auf Pfadebene

Das ist die Korrektur, die die meisten Anwender spüren werden. Die Spezifikation erlaubt es, einen Parameter einmal am Path Item zu deklarieren, statt ihn in jeder Operation darunter zu wiederholen, und genau diesen Stil empfiehlt die Spezifikation, und genau diesen Stil verwenden die meisten öffentlichen Dokumente. Der alte Parser las diese Parameter und verwarf sie dann.

Die generierte Methode nahm überhaupt keine Argumente entgegen, und die Anfrage ging mit dem Platzhalter noch in der URL hinaus, wörtlich /pets/{petId}. Es sah wie ein funktionierender Client aus, bis der erste Aufruf mit einem 404 zurückkam.

Schemas, die sich zusammensetzen lassen

Die Komposition war der schwächste Teil des alten Parsers, und jeder ihrer Zweige war auf andere Weise falsch. allOf behielt von mehreren Basisschemas nur das letzte und warf die Elemente der übrigen weg. oneOf führte alle Zweige in einer einzigen Klasse zusammen, was doppelte Felder erzeugte. anyOf wurde überhaupt nicht behandelt und zu einem String aufgelöst. Ein Schema, das sowohl properties als auch additionalProperties deklarierte, verlor alle seine Eigenschaften.

Alle vier erzeugen jetzt das, was das Dokument beschreibt. Ein Inline-Objektschema bekommt ebenfalls seine eigene Klasse, statt zu einem String zu verkommen, und items wird als vollständiges Schema gelesen, sodass ein Array von Inline-Objekten, ein Array von Enums und ein verschachteltes Array jeweils den richtigen Typ erzeugen.

Enums, die den Wert tragen, den der Server erwartet

Die generierten Enum-Tabellen enthielten früher den bereinigten Pascal-Bezeichner statt des übertragenen Wertes, sodass allow-all als allowall hinausging und json-file als jsonfile. Jede Anfrage, die aus einem dieser Enums gebaut wurde, wurde abgelehnt.

Die Tabellen tragen jetzt den echten Wert, die Deklarationsreihenfolge der Spezifikation bleibt erhalten, Integer-Enums bekommen ebenfalls eine Tabelle, und ein zusätzliches Element Unknown wird generiert, damit ein Wert, den der Server später hinzufügt, nicht stillschweigend als erstes Element der Liste dekodiert wird.

Eigenschaftsnamen bekommen dieselbe Behandlung aus der anderen Richtung. Eine Schema-Eigenschaft, die nach einem reservierten Delphi-Wort wie property, class, string oder function benannt ist, oder zwei Eigenschaften, die sich nur in der Groß- und Kleinschreibung unterscheiden, etwa Name und name, erzeugten früher eine Unit, die nicht kompilierte. Die Eigenschaft wird jetzt umbenannt, und der übertragene Name bleibt über ein JSONName-Attribut erhalten, sodass die Serialisierung weiterhin zum Dokument passt.

Responses, auch die, die Sie nur einmal deklarieren

Die default-Response und die Bereichs-Responses 2XX, 4XX und 5XX wurden stillschweigend verworfen. Eine API, die ihre Fehler nur über default deklariert, was häufig vorkommt, erzeugte einen Client ganz ohne typisierten Fehler. Sie werden jetzt gelesen. Wenn mehrere erfolgreiche Responses deklariert sind, wird die niedrigste verwendet, und application/json wird bevorzugt, wenn eine Operation mehrere Medientypen anbietet.

Parameter bei der Übertragung

Der generierte Client unterstützt jetzt Cookie-Parameter und die vollständigen OpenAPI-Regeln zur Parameterserialisierung: matrix, label, simple, form, spaceDelimited, pipeDelimited und deepObject, jeweils mit explode und allowReserved. Die neuen Methoden AddArray und AddObject bauen die strukturierten Werte von Hand, wenn Sie das brauchen.

// query parameters built with the serialization style the document declares
oRequest.AddArray('tags', ['dog', 'cat'], True);   // explode
oRequest.AddObject('filter', ['color', 'red', 'size', 'M']);

Externe Referenzen

Eine auf mehrere Dateien aufgeteilte Spezifikation funktionierte kaum. Eine Referenz mit einem JSON-Pointer-Fragment wie ./common.yaml#/components/schemas/Error konnte nicht aufgelöst werden. Zwei Dateien, die sich gegenseitig referenzierten, brachten den Parser zum Absturz. Eine relative Referenz innerhalb eines Teildokuments wurde gegen das Wurzeldokument aufgelöst statt gegen die eigene Datei. Zwei externe Dateien mit demselben Basisnamen überschrieben sich gegenseitig und konnten ein Schema des Hauptdokuments ersetzen. Und eine Kette von Referenzen wurde genau einen Schritt weit verfolgt.

Das alles ist behoben, und eine Sache wurde bewusst verschärft: Eine externe Referenz konnte früher jede Datei auf dem Rechner lesen, ../../../credentials.json eingeschlossen, und den Inhalt in die generierte Unit kopieren. Externe Referenzen sind jetzt auf das Verzeichnis des Hauptdokuments beschränkt. Wenn ein Aufbau wirklich darüber hinausgreifen muss, wird die Beschränkung ausdrücklich aufgehoben.

uses
  sgcOpenAPI_Bundle;

begin
  // off by default: references may not leave the folder of the main document
  sgcOpenAPIAllowRefsOutsideRoot := True;
end;

Dateien, die nicht ganz UTF-8 sind

RFC 8259 legt fest, dass ein JSON-Dokument UTF-8 ist, und zahlreiche veröffentlichte Spezifikationen sind es nicht. Eine Datei mit Byte Order Mark wurde früher mit einem UTF-8-Fehler abgelehnt, sobald sie ein Zeichen außerhalb von ASCII enthielt, und chinesischer oder japanischer Text wurde stillschweigend durch Fragezeichen ersetzt.

Ein Dokument, das kein gültiges UTF-8 ist, wird jetzt als Windows-1252 gelesen und mit einer Warnung vermerkt, statt fehlzuschlagen. Eine UTF-16-Datei mit Byte Order Mark wird korrekt gelesen. Die generierte Datei wird mit einer ausdrücklichen Kodierung geschrieben, und ein Zeichen, das die Zielkodierung nicht darstellen kann, wird gemeldet, statt stillschweigend zu einem Fragezeichen zu werden.

Eine Kommandozeile, die Sie in ein Build-Skript stellen können

Die Kommandozeile setzt jetzt einen Exit-Code: 0 bei Erfolg und 1 bis 7 für die verschiedenen Fehlerfälle, sodass ein Build-Schritt erkennen kann, ob die Generierung funktioniert hat. Fehlermeldungen gehen immer nach Standard Error, und der Schalter -l ist jetzt nur noch für die Fortschrittsprotokollierung da.

sgcOpenAPI -i petstore.json -o PetStoreClient.pas -c TPetStoreClient
if errorlevel 1 (
  echo OpenAPI generation failed with exit code %errorlevel%
  exit /b %errorlevel%
)

Drei Fehler in der Kommandozeile wurden gleich mit behoben. Der dokumentierte Schalter -output schrieb die Unit in eine Datei namens utput im aktuellen Verzeichnis, und weil Meldungen unterdrückt wurden, sah der Lauf trotzdem erfolgreich aus. Es passierte überhaupt nichts, wenn das Werkzeug ohne angehängte Konsole lief, was genau der Fall bei einer geplanten Aufgabe oder einem Build-Agenten ist, und eine vorhandene Ausgabeumleitung wurde verworfen. Und -h gab auf einem nicht aktivierten Rechner einen Lizenzfehler statt des Hilfetextes aus, ein ungültiger Wert für -m oder -a wurde stillschweigend akzeptiert, und ein unbekannter Schalter wurde ignoriert.

Neu in diesem Release: -r (oder -remote) konvertiert ein YAML- oder Swagger-2.0-Dokument über den öffentlichen Konverter unter converter.swagger.io. Das ist standardmäßig aus, weil dabei Ihr Dokument an einen Dritten gesendet wird, also etwas, das Sie bewusst einschalten.

Die Swagger-2.0-Konvertierung selbst war auf zwei Arten kaputt, die eine Erwähnung wert sind. Jede Zahl wurde zu einem String, sodass ein numerischer Standardwert eine Unit erzeugte, die nicht kompilierte, und das konvertierte Dokument war kein gültiges OpenAPI 3.0. Und ein discriminator aus Swagger 2.0, der dort ein einfacher String ist, brach den gesamten Parse-Vorgang mit einem ungültigen Typecast ab.

Breaking Changes

Fünf Änderungen verlangen eine Entscheidung von Ihnen statt nur ein Upgrade.

Generierte Clients prüfen jetzt das Serverzertifikat. Vorher taten sie das nicht, was bedeutet, dass sie jedes Zertifikat akzeptierten, auch eines, das ein Man in the Middle vorlegt. Um einen selbstsignierten oder einen Test-Endpunkt zu erreichen, schalten Sie die Prüfung bewusst ab.

oClient.TLSOptions.VerifyCertificate := False;   // test endpoints only
// certificates are trusted through the OpenSSL default paths, so a machine
// with no certificate store configured needs an explicit root
oClient.TLSOptions.RootCertFile := 'cacert.pem';

Der Request-Body ist UTF-8. So wie RFC 8259 es verlangt. Eine Klasse serialisiert einen leeren String jetzt als "field": "", statt ihn wegzulassen. Null-Werte werden separat gesteuert.

oClient.JSONIgnoreEmptyStrings := True;  // previous output
oClient.JSONIgnoreNullValues := True;    // default

Eine Response gibt einen von Ihnen bereitgestellten ResponseStream nicht mehr frei. Setzen Sie OwnsResponseStream auf True für das alte Verhalten. Den Client aus seinem eigenen OnResponse-, OnError- oder OnCancel-Handler heraus freizugeben, löst jetzt einen klaren Fehler aus, statt zu hängen.

Der Wert eines Kommandozeilenschalters muss als -name value oder -name:value geschrieben werden. Die angehängte Form ohne Trennzeichen, etwa -x"GET /pets", wird nicht mehr akzeptiert. Diese Form war auch der Grund dafür, dass -x auf andere Schalter passte, die mit x beginnen, etwa -xml.

Ein als Array deklarierter Parameter wird als Array generiert. Früher wurde er als String generiert, sodass sich die Signatur der generierten Methode für diese Operationen ändert.

Alles Weitere

Die übrigen Korrekturen sind die Sorte, die man erst bemerkt, wenn sie zuschlägt. Ein Element mit einem unerwarteten JSON-Typ, zum Beispiel "properties": [], brach den Parse-Vorgang mit einem ungültigen Typecast ab, statt übersprungen zu werden. Ein Schema vom Typ Integer ohne Standardwert bekam den Standardwert 0, und ein Enum mit einem einzigen Wert wurde als Konstante behandelt, was den Parameter ganz aus der generierten Methode entfernte. Dasselbe Dokument zweimal zu lesen, verdoppelte jeden Pfad, jedes Tag, jeden Server und jedes Schema. Eine Spezifikationserweiterung wie x-tagGroups, die zwischen den Pfaden stand, wurde gelesen, als wäre sie ein Pfad. enum, required und tags wurden mit einem Hilfsmittel für kommagetrennten Text geparst, sodass ein Wert, der ein Komma oder ein JSON-Escape enthielt, aufgeteilt oder beschädigt wurde. Eine Sicherheitsanforderung, die mehrere Schemata auflistete, behielt nur das erste und verlor damit die Bedingung, dass alle erfüllt sein müssen. info.contact und info.license wurden wegen einer Prüfung, die niemals wahr sein konnte, überhaupt nie gelesen. Eine Server-URL mit mehreren Variablen setzte den falschen Wert ein und konnte einen Listenindexfehler auslösen. Das Bündeln einer Spezifikation überschrieb die Eingabedatei ohne Sicherung und ohne Meldung und löschte jeden typografischen Apostroph aus dem Dokument. Und eine Spezifikation, die unter einem Pfad mit einem Leerzeichen abgelegt war, etwa C:\My Specs\, konnte ihre externen Referenzen nicht auflösen.

So bekommen Sie es

sgcOpenAPI 2026.9.0 ist ab sofort verfügbar, mit vollständigem Quellcode und einem Jahr Updates. Es unterstützt Delphi 7 bis Delphi 13 Florence und die entsprechenden C++ Builder Versionen.

Produktseite · Testversion herunterladen · Changelog

Fragen oder Feedback? Nehmen Sie Kontakt auf, Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.