OpenAPI | Serveur | Code-First

Dans l'approche Code-First, la classe Delphi est la source de vérité : les méthodes, paramètres et attributs RTTI décrivent le contrat de l'API, et la spécification OpenAPI 3.0 en est générée au démarrage. Une fois générée, la spécification est chargée dans le serveur avec LoadFromString et la table de routage est construite comme si elle provenait d'un fichier.

Code-First nécessite Delphi XE7 ou ultérieur car il s'appuie sur le nouveau RTTI pour inspecter les attributs. La fonctionnalité réside dans l'unité sgcHTTP_OpenAPI_Server_CodeFirst et est protégée par la directive de compilation {$IFDEF DXE7}.

Définition du service

Annotez une classe Delphi avec des attributs pour décrire l'API. Les corps des méthodes sont des stubs : la logique réelle va dans l'événement OnRequest du serveur ; les méthodes n'existent que pour que le scanner RTTI puisse lire leurs signatures.


[sgcServiceContract('Task Manager API', 'Demo', '1.0.0')]
[sgcRoute('/api/v1')]
TTaskManagerService = class
public
  [sgcHttpGet][sgcRoute('/tasks')]
  [sgcSummary('List all tasks')]
  [sgcTag('Tasks')]
  [sgcResponse(200, 'A list of tasks')]
  procedure ListTasks([sgcFromQuery] const status: string); virtual;

  [sgcHttpPost][sgcRoute('/tasks')]
  [sgcSummary('Create a new task')]
  [sgcResponse(201, 'Task created')]
  procedure CreateTask([sgcFromBody] const body: string); virtual;

  [sgcHttpGet][sgcRoute('/tasks/{taskId}')]
  [sgcResponse(200, 'Task')]
  [sgcResponse(404, 'Not found')]
  procedure GetTask([sgcFromPath][sgcRequired] const taskId: Integer); virtual;
end;

Attributs disponibles

Les attributs suivants sont reconnus par le scanner. Les attributs au niveau classe décrivent le service dans son ensemble ; les attributs au niveau méthode décrivent une opération unique ; les attributs au niveau paramètre décrivent d'où provient la valeur.

sgcServiceContract(name, description, version) : niveau classe. Remplit le bloc info OpenAPI.

sgcRoute(path) : chemin de base au niveau classe, ou route au niveau méthode relative au chemin de base de la classe.

sgcHttpGet, sgcHttpPost, sgcHttpPut, sgcHttpDelete, sgcHttpPatch, sgcHttpHead, sgcHttpOptions : niveau méthode. Déclarent le verbe HTTP de l'opération.

sgcSummary(text) : niveau méthode. Résumé court affiché dans la spécification et la Swagger UI.

sgcDescription(text) : niveau méthode. Description longue pour la spécification.

sgcTag(name) : niveau méthode. Regroupe les opérations sous la même étiquette dans la Swagger UI.

sgcResponse(code, description) : niveau méthode. Déclare une réponse avec son code de statut HTTP et sa description. Peut être répété pour déclarer plusieurs réponses.

sgcFromPath, sgcFromQuery, sgcFromBody, sgcFromHeader : niveau paramètre. Déclarent d'où le paramètre est lu.

sgcRequired : niveau paramètre. Marque le paramètre comme obligatoire ; les paramètres manquants sont signalés par la validation lorsque ValidateRequired est activé.

Génération et chargement de la spécification

Utilisez TsgcOpenAPICodeFirstScanner pour générer la spécification OpenAPI 3.0 à partir de la classe annotée, puis chargez le résultat dans le serveur.


uses
  sgcHTTP_OpenAPI_Server_CodeFirst, sgcHTTPServer_OpenAPI;

var
  oScanner: TsgcOpenAPICodeFirstScanner;
  oServer: TsgcHTTPServer_OpenAPI;
  vSpec: string;
begin
  oScanner := TsgcOpenAPICodeFirstScanner.Create;
  try
    vSpec := oScanner.GenerateSpec(TTaskManagerService);
  finally
    oScanner.Free;
  end;

  oServer := TsgcHTTPServer_OpenAPI.Create(nil);
  oServer.LoadFromString(vSpec);
  oServer.Bindings.Add.Port := 8081;
  oServer.OnRequest := MyOnRequest;
  oServer.Active := True;
end;

Gestion des opérations

Le scanner dérive l'operationId du nom de la méthode : la méthode ListTasks devient l'opération ListTasks. Effectuez le dispatch dans OnRequest en utilisant le même operationId.


procedure TForm1.MyOnRequest(Sender: TObject; const aOperationId: string;
  const aContext: TsgcOpenAPIServerContext; var Handled: Boolean);
begin
  if aOperationId = 'ListTasks' then
    aContext.RespondJSON(200, '[]')
  else if aOperationId = 'CreateTask' then
    aContext.RespondJSON(201, '{"id":1}')
  else if aOperationId = 'GetTask' then
    aContext.RespondJSON(200, '{"id":' +
      IntToStr(aContext.PathParamAsInteger('taskId')) + '}')
  else
    aContext.RespondError(404, 'Not Found', 'Unknown operation');

  Handled := True;
end;

Démo

Un exemple code-first complet est fourni sous Demos/30.Server/01.CodeFirst/.