How to create YAML documentation using EMS documentation attributes

342 views Asked by At

I have just started experimenting with RAD Server. One of the features that make the investment attractive is the self documenting API’s feature, which would save a lot of work in supporting external partners connecting to our REST interfaces, and obviate the need to maintain a separate interface specification document, that would invariably falls behind the development.

I have followed the the tutorial in the wiki for creating one's first EMS resource all the way to deployment to a test EMS server. This works fine, without a hitch.

However, when I reach the custom API documentation tutorial, it just simply does not work.

I have looked at Stephen Ball's post on SWAGGER / YAML AND SELF DOCUMENTING RESTFUL API’S. Unfortunately he uses the RAD Studio EMS Sample project, which works perfectly even for me. But the minute I try to apply the same attributes to my own EMS package, it does not work.

When I call htt://localhost:8080/api/apidoc.yaml the server returns:

{
  "error":"Error",
  "description":"Error: No Responses defined for: get "
}

The EMS development server has the following corresponding log entries:

{"Request":{"Resource":"API","Endpoint":"GetAPIYAMLFormat","Method":"GET","User":"(blank)","Time":"2017/08/11 12:59:46 AM","Thread":1732}}
{"Error":{"Type":"HTTP","Code":"500","Reason":"Error","Error":"","Description":"Error: No Responses defined for: get ","Thread":1732}}

Here is a snippet of my code from the tutorial:

unit Unit1;

// EMS Resource Unit

interface

uses
  System.SysUtils, System.Classes, System.JSON,
  EMS.Services, EMS.ResourceAPI,
  EMS.ResourceTypes, APIDocumentationEndPointObjectsDefinitions;

type
  [ResourceName('Test')]
  [EndPointObjectsYAMLDefinitions(YAMLDefinitions)]
  [EndPointObjectsJSONDefinitions(JSONDefinitions)]

  {$METHODINFO ON}
  TTestResource = class
  published
    // Declare the function
    function MakeJSON(I: Integer): TJSONObject; //It takes an integer as a parameter and returns a JSON Object.
    [EndPointRequestSummary('Items', 'Get items', 'Used to retrieve all the items', 'application/json', '')]
    [EndPointRequestParameter(TAPIDocParameter.TParameterIn.Path, 'Test', 'Path Parameter item Description', false, TAPIDoc.TPrimitiveType.spString, TAPIDoc.TPrimitiveFormat.None, TAPIDoc.TPrimitiveType.spString, '', '')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '#/definitions/items')]
    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [ResourceSuffix('{item}')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  end;
  {$METHODINFO OFF}

implementation
const
  TestValues: array [0 .. 2] of string = ('a', 'b', 'c'); // It creates an array of string values.

procedure TTestResource.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  LJSON: TJSONArray;
  I: Integer;
begin
  LJSON := TJSONArray.Create;
  for I := Low(TestValues) to High(TestValues) do
    LJSON.Add(MakeJSON(I)); //[{"index":0,"value":"a"},{"index":1,"value":"b"},{"index":2,"value":"c"}]
  AResponse.Body.SetValue(LJSON, True) // True causes AResponse to free JSON
end;

procedure TTestResource.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  I: Integer;
begin
  if not TryStrToInt(ARequest.Params.Values['item'], I) then //{"index":I,"value":value}
    AResponse.RaiseBadRequest('Index expected');
  if (I < 0) or (I >= Length(TestValues)) then
    AResponse.RaiseBadRequest('Index out of range');
  AResponse.Body.SetValue(MakeJSON(I), True);
  // True causes AResponse to free JSON
end;

function TTestResource.MakeJSON(I: Integer): TJSONObject;
begin
  Result := TJSONObject.Create;
  Result.AddPair('index', TJSONNumber.Create(I)); //Adds to the JSON object a pair {"index": I}, the index number.
  Result.AddPair('value', TJSONString.Create(TestValues[I])); //Adds to the the JSON object a pair {"value":String}, the string corresponding to the index number.

end;

procedure Register;
begin
  RegisterResource(TypeInfo(TTestResource));
end;

initialization
  Register;
end.

It looks as though there is something in the sample project that is missing in the code generated by the RAD Studio EMS package wizard.

I am wondering if anyone has been able to use the new EMS documentation attributes to create YAML documentation from their own EMS package - not the sample project supplied with RAD Studio?

Has anybody else experienced this? Is it possible that the get method may not have been fully implemented. Is there a fix for this (I have just updated to RAD Studio 10.2.1)?

2

There are 2 answers

0
FMXExpress On

You must include EndPointRequestSummary and EndPointResponseDetails above every endpoint procedure otherwise the apidoc.yaml and apidoc.json files will return an error. In your case you have MakeJSON, Get, and GetItem.

Here are two sample lines from the APIDocAttributes sample project.

[EndPointRequestSummary('Sample Tag', 'Summary Title', 'Get Method Description', 'application/json', '')]
[EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '')]

So your code could look something like this:

  TTestResource = class
  published
    // Declare the function
    [EndPointRequestSummary('Sample Tag', 'Summary Title', 'Get Method Description', 'application/json', '')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '')]
    function MakeJSON(I: Integer): TJSONObject; //It takes an integer as a parameter and returns a JSON Object.

    [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);

    [ResourceSuffix('{item}')]
    [EndPointRequestSummary('Sample Tag', 'Summary Title', 'Get Method Description', 'application/json', '')]
    [EndPointResponseDetails(200, 'Ok', TAPIDoc.TPrimitiveType.spObject, TAPIDoc.TPrimitiveFormat.None, '', '')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  end;
0
Torbins On

It will work if you run EMSDevServer.exe without any load, like this:

{"ConfigLoaded":{"Filename":"C:\Users\Public\Documents\Embarcadero\EMS\emsserver.ini","Thread":924}}
{"DBConnection":{"InstanceName":"gds_db","Filename":"C:\Users\Public\Documents\Embarcadero\EMS\emsserver.ib","Thread":924}}
{"Licensing":{"Licensed":false,"DefaultMaxUsers":5,"Thread":924}}
{"RegResource":{"Resource":"Version","Endpoints":["GetVersion"],"Thread":924}}
{"RegResource":{"Resource":"API","Endpoints":["API","GetAPIYAMLFormat EndPoint","GetAPIYAMLFormat","GetAPIJSONFormat"],"Thread":924}}
{"RegResource":{"Resource":"Users","Endpoints":["GetUsers","GetUser","GetUserFields","GetUserGroups","SignupUser","LoginUser","AddUser","UpdateUser","DeleteUser"],"Thread":924}}
{"RegResource":{"Resource":"Groups","Endpoints":["GetGroups","GetGroup","GetGroupFields","AddGroup","UpdateGroup","DeleteGroup"],"Thread":924}}
{"RegResource":{"Resource":"Installations","Endpoints":["GetInstallations","GetChannels","GetInstallationFields","GetInstallation","AddInstallation","UpdateInstallation","DeleteInstallation"],"Thread":924}}
{"RegResource":{"Resource":"Push","Endpoints":["Send"],"Thread":924}}
{"RegResource":{"Resource":"Edgemodules","Endpoints":["GetModules","GetModule","GetResources","GetModuleResources","GetModulesFields","GetResourcesFields","GetModuleResource","RegisterModule","RegisterModuleResource","UpdateModule","UpdateModuleResource","UnregisterModule","UnregisterModuleResource","GetResourceEndpoint","GetResourceEndpointItem","PutResourceEndpoint","PutResourceEndpointItem","PostResourceEndpoint","PostResourceEndpointItem","PatchResourceEndpoint","PatchResourceEndpointItem","DeleteResourceEndpoint","DeleteResourceEndpointItem"],"Thread":924}}
{"Request":{"Resource":"API","Endpoint":"GetAPIJSONFormat","Method":"GET","User":"(blank)","Time":"06.11.2017 15:42:28","Thread":8672}}

With any load it does not run, probably it is a bug.