Table of Contents

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;