OpenAPI (Swagger) importer
Note
OpenAPI importer became a separated, stand alone tool and it's available as open-source project at https://github.com/landgraf-dev/openapi-delphi-generator
Warning
All the documentation below is deprecated, classes are not available anymore. We will keep it here as a reference for the open source project mentioned in the note above.
XData allows you to import an existing server Swagger specification and generate service contract interfaces that you can use together with TXDataClient to perform requests to such server. The importer will also create DTO classes representing all the JSON used for requests and responses in the server.
For now the importer can import Swagger 2.0 specification in JSON format.
Generating the imported unit
Use the TOpenApiImporter
class declared in unit XData.OpenApi.Importer
to read a Swagger JSON and generate meta information for the unit to be generated. Then you can use TDelphiCodeGenerator
class (unit Bcl.Code.DelphiGenerator
) to effectively generate the code. Here is an example:
uses {...}, Bcl.Code.DelphiGenerator, Bcl.Code.MetaClasses,
OpenApi.Document, OpenAPI.Json.Serializer, XData.OpenApi.Importer;
function GenerateSource(CodeUnit: TCodeUnit): string;
var
Generator: TDelphiCodeGenerator;
begin
Generator := TDelphiCodeGenerator.Create;
try
Generator.StructureStatements := True;
Result := Generator.GenerateCodeFromUnit(CodeUnit);
finally
Generator.Free;
end;
end;
procedure ImportApi(const SwaggerJson, OutputFile: string);
var
Document: TOpenApiDocument;
Importer: TOpenApiImporter;
begin
Importer := TOpenApiImporter.Create;
try
Document := TOpenApiDeserializer.JsonToDocument(SwaggerJson);
try
// SetImporterEvents(Importer); // Uncomment to set custom events
Importer.Build(Document);
Importer.CodeUnit.Name := TPath.GetFileNameWithoutExtension(OutputFile);
TFile.WriteAllText(OutputFile, GenerateSource(Importer.CodeUnit), TEncoding.UTF8);
finally
Document.Free;
end;
finally
Importer.Free;
end;
end;
The above code will parse the Swagger JSON, build a TCodeUnit
object with the unit information, then generate the file .pas
file with the file name indicated by OutputFile
.
Customizing the imported API
You can use events to customize the generated unit. Sometimes the importer doesn't generate 100% accurate source code, or sometimes you simply want to change the generated API. The following code is an example extracted from the KeapApiImporterV2
demo, available in the demos
folder. The demo shows how to import and generate a client for the Keap API.
function ToPascalCase(const S: string): string;
var
I: Integer;
Convert: Boolean;
begin
I := 1;
Result := '';
Convert := True;
while I <= Length(S) do
begin
if TBclUtils.IsLetter(S[I]) then
begin
if Convert then
begin
Result := Result + UpCase(S[I]);
Convert := False;
end
else
Result := Result + S[I];
end
else
if S[I] = '_' then
Convert := True
else
Result := Result + S[I];
Inc(I);
end;
end;
procedure SetImporterEvents(Importer: TOpenApiImporter);
begin
// OnGetMethodName allows you to change name of the generated service contract method
Importer.OnGetMethodName :=
procedure(var MethodName: string; const Original: string)
var
P: Integer;
begin
// Convert specific names
if Original = 'listCountriesUsingGET_3' then
MethodName := 'ListCountryProvinces'
else
if Original = 'updateCompanyUsingPATCH_3' then
MethodName := 'UpdateCompany2'
else
if Original = 'removeTagsFromContactUsingDELETE_3' then
MethodName := 'RemoveTagFromContact';
end;
// OnGetTypeName also allows you to provide a custom name for the DTO classes
Importer.OnGetTypeName :=
procedure(var TypeName: string; const Original: string)
begin
TypeName := 'T' + TypeName;
end;
// OnGetPropName allows you to modify the name of a property. In the following example,
// some generated property names don't compile because they are invalid identifiers.
Importer.OnGetPropName :=
procedure(var PropName: string; const Original: string)
begin
if Original = '24_hours' then
PropName := '_24Hours'
else
if Original = '30_days' then
PropName := '_30Days'
else
if Original = 'className' then
PropName := 'ClassName_'
else
if Original = 'methodName' then
PropName := 'MethodName_';
else
PropName := ToPascalCase(Original);
end;
// OnMethodCreated is called after the full method meta information is generated. You can then change everything
// you need from it. In this case, optional_properties parameter is removed from the final method
Importer.OnMethodCreated :=
procedure(Method: TCodeMemberMethod; Parent: TCodeTypeDeclaration)
begin
Method.RemoveParameter('optional_properties');
end;
// OnGetServiceName event allows settings the name of the interface type
Importer.OnGetServiceName :=
procedure(var ServiceName: string; var Guid: TGUID; PathItem: TPathItem; Operation: TOperation)
var
Name: string;
begin
if Operation.Tags.Count = 1 then
begin
Name := Operation.Tags[0];
Name := StringReplace(Name, ' ', '', [rfReplaceAll]);
Name := StringReplace(Name, '-', '', [rfReplaceAll]);
ServiceName := Format('IKeap%s', [ToPascalCase(Name)]);
end;
end;
end;
Using the API
The importer generates a unit with service contract interfaces and JSON DTOs. Use it with a TXDataClient the same way you would use a service contract to invoke operations in a XData server. The difference, of course, is that the server is not XData but a 3rd party API.
var
Client: TXDataClient;
Request: TCreatePatchContactRequest;
begin
Client := TXDataClient.Create;
Client.Uri := 'https://api.infusionsoft.com/crm/rest/v2';
Request := TCreatePatchContactRequest.Create;
Request.FamilyName := 'Foo';
Client.Service<IKeapContact>.CreateContact(Request);
Request.Free;
Client.Free;
end;