How-To: Generating Documentation for RAD Server APIs

by Jun 18, 2019

This guide covers generating and beautifying Swagger documentation for RAD Server. Using Swagger makes it easier for 3rd parties to interface with an consume your RAD Server REST API. This guide was written by Eli M. and is part of the collection of New RAD Server (EMS) Articles, Resources, and eBook.

What is RAD Server?

RAD Server makes it fast and easy to build flexible back-end servers for your multi-tier Delphi or C++Builder application solution. Developers can choose from a number of built-in REST APIs or create your own custom APIs. RAD Server automatically provides documentation of the built-in REST API endpoints and an easy way to document your custom resource modules. This makes it easy to build REST API endpoints for existing or new databases and document the new endpoints.

We will cover the following in this post:

  1. RAD Server And Swagger 2.0 Documentation
  2. Beautify Your RAD Server Endpoint Documentation
  3. Documenting A RAD Server Endpoint
  4. APIDocAttributes Sample Project Documentation

Let’s take a deeper look at this RAD Server documentation functionality.

RAD Server And Swagger 2.0 Documentation

RAD Server can automatically publish REST API documentation from your code by adding a few simple attributes. The existing RAD Server built-in APIs such as Users, Groups, and Push plus new API endpoints you create are documented using the Swagger 2.0 format. Swagger is a specification that defines the format of a YAML or JSON file to describe and document a RESTful API. An open source Swagger-UI project can parse a Swagger JSON file and beautify the API into a more human readable form. Additionally, the Swagger-UI project also offers the ability to test the APIs from within it’s browser based documentation explorer. This allows to you get started with RAD Server quickly and integrate the existing APIs easily.

The built-in API definitions such as Users, Groups, Installations, EdgeModules, and Push are available through RAD Server via the Swagger YAML and JSON files. These files are available at the following endpoints /api/apidoc.yaml and /api/apidoc.json. Custom RAD Server resource modules that you build yourself and install into RAD Server tie into this existing documentation framework by simply adding a few lines of attributes to your code above each endpoint definition. Additionally, you can access the documentation for each custom resource by itself at the following endpoint /api/{item}/apidoc.yaml where {item} is the ResourceName for that resource module.

Beautify Your RAD Server Endpoint Documentation

In order to display your RAD Server Swagger documentation you should retrieve the Swagger-UI distribution files. You can download the Swagger-UI files from GitHub at https://github.com/swagger-api/swagger-ui/tree/master/dist and place them in a separate directory with your RAD Server project. There are a number of CSS and JavaScript files plus an index.html file. Here is a list of the Swagger-UI distribution files at this time:

  • favicon-16×16.png
  • favicon-32×32.png
  • index.html
  • oauth2-redirect.html
  • swagger-ui-bundle.js
  • swagger-ui-bundle.js.map
  • swagger-ui-standalone-preset.js
  • swagger-ui-standalone-preset.js.map
  • swagger-ui.css
  • swagger-ui.css.map
  • swagger-ui.js
  • swagger-ui.js.map

Once you have downloaded the Swagger-UI distribution files you can edit the index.html file and change the url parameter from it’s existing value of https://petstore.swagger.io/v2/swagger.json to point at your local EMSDevServer URL. If EMSDevServer is running this may be at localhost:8080/api/apidoc.json for example. Note: You may need to modify your emsserver.ini file in the Server.APICrossDomain section and set the CrossDomain parameter to * to allow cross site scripting between the Swagger-UI page and the EMSDevServer.

If you have made these changes correctly you should be able to launch index.html in your browser and you will see the RAD Server API documentation displayed in human readable form within the Swagger-UI Explorer.

Documenting A RAD Server Endpoint

You must include both the EndPointRequestSummary and EndPointResponseDetails attribute above each endpoint that you create in your project in order for the documentation to be generated correctly. If you receive an error when trying to access the apidoc.yaml or apidoc.json files then most likely you are missing these two attributes on one or more of your endpoints (or the attribute parameters are incorrect). Here are some sample attributes placed above a standard Object Pascal based RAD Server Get endpoint published procedure:

[ResourceName('test')]
TTestResource1 = class(TDataModule)
  published
[EndPointRequestSummary('Sample Tag', 'Summary Title', 'Get Method Description', 'application/json', '')]
[EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '')]
procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

You can find out more about the parameters on each attribute in the Custom API Documentation. For a C++-based RAD Server Get endpoint, you will need to add the attributes via the Register section like in the below code:

static void Register()
{

		// Get
		//Add Summary
		std::unique_ptr<EndPointRequestSummaryAttribute> RequestSummary(new EndPointRequestSummaryAttribute("Sample Tag",
			"Summary Title", "Get Method Description", "application/json", ""));
		attributes->RequestSummary["Get"] = RequestSummary.get();
		//Add Responses
		std::unique_ptr<EndPointResponseDetailsAttribute> ResponseDetail(new EndPointResponseDetailsAttribute(200, "OK",
			TAPIDoc::TPrimitiveType::spObject, TAPIDoc::TPrimitiveFormat::None, "", "#/definitions/EmployeeTable"));
		attributes->AddResponseDetail("Get", ResponseDetail.get());
}

When you view this endpoint through the Swagger-UI Explorer it will look something like the following:

APIDocAttributes Sample Project Documentation

RAD Studio comes with a sample project called APIDocAttributes which offers more examples for various attributes that you can add to your REST API endpoints. The sample project is available in both Object Pascal and C++. Here is some sample code from the Object Pascal project which shows additional attributes like EndPointRequestParameter you can use. You can get access to the full source code for the APIDocAttributes sample source code with your RAD Studio installation or through the Embarcadero DocWiki.

  [EndPointRequestSummary('Sample Tag', 'Summary Title', 'Get Method Description', 'application/json', '')]
    [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Query, 'skip', 'Query Parameter Description',
      false, TAPIDoc.TPrimitiveType.spNumber, TAPIDoc.TPrimitiveFormat.Int32, TAPIDoc.TPrimitiveType.spNull, '', '')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '#/definitions/EmployeeTable')]
    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

    [ResourceSuffix('{item}')]
    [EndPointRequestSummary('Sample Tag', 'Summary Title', 'GetItem Method Description', 'application/json', '')]
    [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Path, 'item', 'Path Parameter item Description', true, TAPIDoc.TPrimitiveType.spString,
      TAPIDoc.TPrimitiveFormat.None, TAPIDoc.TPrimitiveType.spString, '', '')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '#/definitions/EmployeeTable')]
    [EndPointResponseDetails(404, 'Not Found', TAPIDoc.TPrimitiveType.spNull, TAPIDoc.TPrimitiveFormat.None, '', '')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

    [EndPointRequestSummary('Sample Tag', 'Summary Title', 'Post Method Description', 'application/json', 'application/json')]
    [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Body, 'body', 'Body Param Description', true, TAPIDoc.TPrimitiveType.spObject,
      TAPIDoc.TPrimitiveFormat.None, TAPIDoc.TPrimitiveType.spObject, '', '#/definitions/PostObject')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spNull, TAPIDoc.TPrimitiveFormat.None, '', '#/definitions/ItemPostedResponseObject')]
    [EndPointResponseDetails(409, ' Item Exist', TAPIDoc.TPrimitiveType.spNull, TAPIDoc.TPrimitiveFormat.None, '', '')]
    procedure Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

    [ResourceSuffix('{item}')]
    [EndPointRequestSummary('Sample Tag', 'Summary Title', 'PutItem Method Description', 'application/json', 'application/json')]
    [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Path, 'item', 'Path Parameter item Description', true, TAPIDoc.TPrimitiveType.spString,
      TAPIDoc.TPrimitiveFormat.None, TAPIDoc.TPrimitiveType.spString, '', '')]
    [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Body, 'body', 'Body Param Description', true, TAPIDoc.TPrimitiveType.spObject,
      TAPIDoc.TPrimitiveFormat.None, TAPIDoc.TPrimitiveType.spObject, '', '#/definitions/PutObject')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '#/definitions/ItemPutResponseObject')]
    [EndPointResponseDetails(404, 'Not Found', TAPIDoc.TPrimitiveType.spNull, TAPIDoc.TPrimitiveFormat.None, '', '')]
    procedure PutItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

Here is sample code from the C++ project which shows additional features you can use like EndPointRequestParameter.

// Get
		//Add Summary
		std::unique_ptr<EndPointRequestSummaryAttribute> RequestSummary(new EndPointRequestSummaryAttribute("Sample Tag",
			"Summary Title", "Get Method Description", "application/json", ""));
		attributes->RequestSummary["Get"] = RequestSummary.get();
		//Add Parameters
		std::unique_ptr<EndPointRequestParameterAttribute> ResponseParameter(new EndPointRequestParameterAttribute(TAPIDocParameter::TParameterIn::Query, "skip",
			"Query Parameter Description", false, TAPIDoc::TPrimitiveType::spNumber, TAPIDoc::TPrimitiveFormat::Int32, TAPIDoc::TPrimitiveType::spNull, "", ""));
		attributes->AddRequestParameter("Get", ResponseParameter.get());
		//Add Responses
		std::unique_ptr<EndPointResponseDetailsAttribute> ResponseDetail(new EndPointResponseDetailsAttribute(200, "OK",
			TAPIDoc::TPrimitiveType::spObject, TAPIDoc::TPrimitiveFormat::None, "", "#/definitions/EmployeeTable"));
		attributes->AddResponseDetail("Get", ResponseDetail.get());

		// Get Item
		//Add Summary
		attributes->ResourceSuffix["GetItem"] = "{item}";
		//Add Summary
		RequestSummary.reset(new EndPointRequestSummaryAttribute("Sample Tag", "Summary Title", "GetItem Method Description",
			"application/json", ""));
		attributes->RequestSummary["GetItem"] = RequestSummary.get();
		//Add Parameters
		ResponseParameter.reset(new EndPointRequestParameterAttribute(TAPIDocParameter::TParameterIn::Path, "item",
			"Path Parameter item Description", true, TAPIDoc::TPrimitiveType::spString, TAPIDoc::TPrimitiveFormat::None, TAPIDoc::TPrimitiveType::spString, "", ""));
		attributes->AddRequestParameter("GetItem", ResponseParameter.get());
		//Add Responses
		ResponseDetail.reset(new EndPointResponseDetailsAttribute(200, "OK", TAPIDoc::TPrimitiveType::spObject,
			TAPIDoc::TPrimitiveFormat::None, "", "#/definitions/EmployeeTable"));
		attributes->AddResponseDetail("GetItem", ResponseDetail.get());
		ResponseDetail.reset(new EndPointResponseDetailsAttribute(404, "Not Found", TAPIDoc::TPrimitiveType::spNull,
			TAPIDoc::TPrimitiveFormat::None, "", ""));
		attributes->AddResponseDetail("GetItem", ResponseDetail.get());

		// Post
		//Add Summary
		RequestSummary.reset(new EndPointRequestSummaryAttribute("Sample Tag", "Summary Title", "Post Method Description",
			"application/json", "application/json"));
		attributes->RequestSummary["Post"] = RequestSummary.get();
		//Add Parameters
		ResponseParameter.reset(new EndPointRequestParameterAttribute(TAPIDocParameter::TParameterIn::Body, "body",
			"Body Param Description", true, TAPIDoc::TPrimitiveType::spObject, TAPIDoc::TPrimitiveFormat::None, TAPIDoc::TPrimitiveType::spObject,
			"", "#/definitions/PostObject"));
		attributes->AddRequestParameter("Post", ResponseParameter.get());
		//Add Responses
		ResponseDetail.reset(new EndPointResponseDetailsAttribute(200, "OK", TAPIDoc::TPrimitiveType::spObject,
			TAPIDoc::TPrimitiveFormat::None, "", "#/definitions/ItemPostedResponseObject"));
		attributes->AddResponseDetail("Post", ResponseDetail.get());
		ResponseDetail.reset(new EndPointResponseDetailsAttribute(409, "Item Exist", TAPIDoc::TPrimitiveType::spNull,
			TAPIDoc::TPrimitiveFormat::None, "", ""));
		attributes->AddResponseDetail("Post", ResponseDetail.get());

		// Put
		//Add Summary
		attributes->ResourceSuffix["PutItem"] = "{item}";
		RequestSummary.reset(new EndPointRequestSummaryAttribute("Sample Tag", "Summary Title", "PutItem Method Description",
			"application/json", "application/json"));
		attributes->RequestSummary["PutItem"] = RequestSummary.get();
		//Add Parameters
		ResponseParameter.reset(new EndPointRequestParameterAttribute(TAPIDocParameter::TParameterIn::Path, "item",
			"Path Parameter item Description", true, TAPIDoc::TPrimitiveType::spString, TAPIDoc::TPrimitiveFormat::None, TAPIDoc::TPrimitiveType::spString, "", ""));
		attributes->AddRequestParameter("PutItem", ResponseParameter.get());
		ResponseParameter.reset(new EndPointRequestParameterAttribute(TAPIDocParameter::TParameterIn::Body, "body",
			"Body Param Description", true, TAPIDoc::TPrimitiveType::spObject, TAPIDoc::TPrimitiveFormat::None, TAPIDoc::TPrimitiveType::spObject,
			"", "#/definitions/PutObject"));
		attributes->AddRequestParameter("PutItem", ResponseParameter.get());
		//Add Responses
		ResponseDetail.reset(new EndPointResponseDetailsAttribute(200, "OK", TAPIDoc::TPrimitiveType::spObject,
			TAPIDoc::TPrimitiveFormat::None, "", "#/definitions/ItemPutResponseObject"));
		attributes->AddResponseDetail("PutItem", ResponseDetail.get());
		ResponseDetail.reset(new EndPointResponseDetailsAttribute(404, "Not Found", TAPIDoc::TPrimitiveType::spNull,
			TAPIDoc::TPrimitiveFormat::None, "", ""));
		attributes->AddResponseDetail("PutItem", ResponseDetail.get());

Summary

Your RAD Server instance should now be generating beautiful Swagger documentation making it easier for 3rd parties to connect to and consume your REST APIs. Head over to the Custom API Documentation to get more information about automatically documenting your REST APIs in RAD Server.

Read more

Check out the articles in the DocWiki