Skip to content

Commit 362625b

Browse files
stcarrezwing328
authored andcommitted
[Ada] Add Ada support for server code generator #6680 (#7256)
* Add Ada client petstore samples - Add script to generate Ada client support with swagger-codegen - Add files to build the Ada sample - Add main program to use the generated client samples API and connect to the server to perform some operations * Add some description for the samples * Update the documentation to explain how to build, how to use the generated Ada client code * Add server support for path parameters - Update postProcessOperations to scan each path parameter and emit a x-path-index vendor attribute to tell the index of the path parameter * Add and fix Ada server code package declaration - fix declaration of operations - generate a generic package that must be instantiated with the target server implementation and which provides the skeleton (deserialization and serialization of data) * Implement the Ada server side operations - extract body, query parameters, path parameters - serialize the result - register operations to the server according to the path/routes * Update the code generation to generate server Ada implementation code * Improvement of Ada server support: generate the swagger.json template file * Define toModelName operation to the creation of a model identifier * Add support for server permission generation - collect the security scopes in postProcessAuthMethod() method and make sure these scopes have unique identifiers. Some scopes correspond to URLs but others correspond to pseudo identifiers. * Use the #lambdaAdaComment filter to indent correctly a multi-line description * Fix model generation to support arrays * Update the generated GNAT project file * Refactoring and improvement of server code generation - Change the server generated code to pass a Context_Type object to allow the server implementation to get/set headers in the request/response and control what is put in some responses - Generate the security permissions based on the scopes that have been collected * Server code generation improvement - Fix generation of GNAT project - Generate the intermediate Ada packages if necessary - Generate the server main * Ada server main template * Ada server code improvement - Add support to generate server permission verification - Fix the GNAT project definition - Templates for Ada intermediate packages * Skeleton for the server side implementation * Generate an empty Ada server implementation * Templates for the Ada server implementation * Add a README.md file and a GNAT config.gpr file * New templates to document the generated Ada server * Add server configuration file for the Ada server * Fix the log message in the Ada server to report the correct URI to connect to * Generate the Ada server configuration file * Improvement of Ada code model to support nullable types * Update the Ada server templates * Refactor the Ada code generator - separate the Ada client and Ada server code generators - register the Ada server code generator under the name 'ada-server' keep 'ada' for the client Ada code generator - moved the common Ada code operation supports to the AbstractAdaCodegen * Improvement and cleanup of Ada client and server code - new template for the client main program - fix the GNAT project template for client or server programs - remove unused options to better use the --model-package option * Fix the GNAT project file name to use a lower case name Fix the default GNAT config Fix the headers of intermediate Ada package files * Regenerate the model and client Ada files * Update the Ada client sample to take into account the Nullable types * Regenerate some files with Ada Swagger Codegen * Ignore generation of petstore.gpr
1 parent 743bc65 commit 362625b

30 files changed

+1498
-419
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractAdaCodegen.java

Lines changed: 453 additions & 8 deletions
Large diffs are not rendered by default.

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AdaCodegen.java

Lines changed: 46 additions & 300 deletions
Large diffs are not rendered by default.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package io.swagger.codegen.languages;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.Writer;
6+
7+
import com.samskivert.mustache.Mustache;
8+
import com.samskivert.mustache.Template;
9+
import io.swagger.codegen.*;
10+
11+
public class AdaServerCodegen extends AbstractAdaCodegen implements CodegenConfig {
12+
13+
public AdaServerCodegen() {
14+
super();
15+
}
16+
17+
@Override
18+
public CodegenType getTag() {
19+
return CodegenType.SERVER;
20+
}
21+
22+
@Override
23+
public String getName() {
24+
return "ada-server";
25+
}
26+
27+
@Override
28+
public String getHelp() {
29+
return "Generates an Ada server implementation (beta).";
30+
}
31+
32+
@Override
33+
public void processOpts() {
34+
super.processOpts();
35+
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
36+
packageName = (String) additionalProperties.get(CodegenConstants.PACKAGE_NAME);
37+
}
38+
String srcPrefix = "src" + File.separator;
39+
String serverPrefix = srcPrefix + "server" + File.separator + toFilename(modelPackage);
40+
String modelPrefix = srcPrefix + "model" + File.separator + toFilename(modelPackage);
41+
String implPrefix = srcPrefix + toFilename(modelPackage);
42+
supportingFiles.add(new SupportingFile("model-spec.mustache", null, modelPrefix + "-models.ads"));
43+
supportingFiles.add(new SupportingFile("model-body.mustache", null, modelPrefix + "-models.adb"));
44+
supportingFiles.add(new SupportingFile("server-skeleton-spec.mustache", null, serverPrefix + "-skeletons.ads"));
45+
supportingFiles.add(new SupportingFile("server-skeleton-body.mustache", null, serverPrefix + "-skeletons.adb"));
46+
supportingFiles.add(new SupportingFile("server-spec.mustache", null, implPrefix + "-servers.ads"));
47+
supportingFiles.add(new SupportingFile("server-body.mustache", null, implPrefix + "-servers.adb"));
48+
49+
supportingFiles.add(new SupportingFile("swagger.mustache", "web" + File.separator + "swagger", "swagger.json"));
50+
51+
if (additionalProperties.containsKey(CodegenConstants.PROJECT_NAME)) {
52+
projectName = (String) additionalProperties.get(CodegenConstants.PROJECT_NAME);
53+
} else {
54+
// default: set project based on package name
55+
// e.g. petstore.api (package name) => petstore_api (project name)
56+
projectName = packageName.replaceAll("\\.", "_");
57+
}
58+
String configBaseName = modelPackage.toLowerCase();
59+
supportingFiles.add(new SupportingFile("gnat-project.mustache", "", toFilename(projectName) + ".gpr"));
60+
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
61+
supportingFiles.add(new SupportingFile("config.gpr", "", "config.gpr"));
62+
supportingFiles.add(new SupportingFile("server-properties.mustache", "", configBaseName + ".properties"));
63+
64+
/*
65+
* Additional Properties. These values can be passed to the templates and
66+
* are available in models, apis, and supporting files
67+
*/
68+
additionalProperties.put("package", this.modelPackage);
69+
additionalProperties.put("packageConfig", configBaseName);
70+
additionalProperties.put("packageDir", "server");
71+
additionalProperties.put("mainName", "server");
72+
additionalProperties.put(CodegenConstants.PROJECT_NAME, projectName);
73+
74+
String names[] = this.modelPackage.split("\\.");
75+
String pkgName = names[0];
76+
additionalProperties.put("packageLevel1", pkgName);
77+
supportingFiles.add(new SupportingFile("package-spec-level1.mustache", null,
78+
"src" + File.separator + toFilename(names[0]) + ".ads"));
79+
if (names.length > 1) {
80+
String fileName = toFilename(names[0]) + "-" + toFilename(names[1]) + ".ads";
81+
pkgName = names[0] + "." + names[1];
82+
additionalProperties.put("packageLevel2", pkgName);
83+
supportingFiles.add(new SupportingFile("package-spec-level2.mustache", null,
84+
"src" + File.separator + fileName));
85+
}
86+
pkgName = this.modelPackage;
87+
supportingFiles.add(new SupportingFile("server.mustache", null,
88+
"src" + File.separator + toFilename(pkgName) + "-server.adb"));
89+
additionalProperties.put("packageName", toFilename(pkgName));
90+
91+
// add lambda for mustache templates
92+
additionalProperties.put("lambdaAdaComment", new Mustache.Lambda() {
93+
@Override
94+
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
95+
String content = fragment.execute();
96+
content = content.trim().replaceAll("\n$", "");
97+
writer.write(content.replaceAll("\n", "\n -- "));
98+
}
99+
});
100+
}
101+
102+
@Override
103+
public String apiFileFolder() {
104+
return outputFolder + "/" + apiPackage().replace('.', File.separatorChar);
105+
}
106+
107+
@Override
108+
public String modelFileFolder() {
109+
return outputFolder + "/model/" + modelPackage().replace('.', File.separatorChar);
110+
}
111+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# {{appDescription}} - Swagger Ada Server
2+
3+
## Overview
4+
5+
This Ada server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project.
6+
By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server,
7+
you can easily generate a server stub.
8+
9+
## Building
10+
11+
To build the server you will need the GNAT Ada compiler as well as
12+
the [Swagger Ada library](https://github.com/stcarrez/swagger-ada).
13+
14+
When the GNAT Ada compiler and Swagger Ada libraries are installed,
15+
run the following command:
16+
17+
```
18+
gprbuild -p -P{{projectName}}
19+
```
20+
21+
After the build is successfull, you will get the server binary
22+
in bin/{{packageName}}-server and you can start it as follows:
23+
```
24+
./bin/{{packageName}}-server
25+
```
26+
27+
## Structure of the server
28+
29+
The server consists of several Ada packages that are generated from
30+
the OpenAPI specification.
31+
32+
Source file | Package | Description
33+
------------ | ------------- | -------------
34+
src/{{packageName}}.ads|{{package}}|The server root package declaration
35+
src/{{packageName}}-servers.ads|{{package}}.Servers|The server declaration and instantiation
36+
src/{{packageName}}-servers.adb|{{package}}.Servers|The server implementation (empty stubs)
37+
src/server/{{packageName}}-skeletons.ads|{{package}}.Skeletons|The server skeleton declaration
38+
src/server/{{packageName}}-skeletons.adb|{{package}}.Skeletons|The server skeleton implementation
39+
src/server/{{packageName}}-models.ads|{{package}}.Skeletons|The server model types declaration
40+
src/server/{{packageName}}-models.adb|{{package}}.Skeletons|The server model types implementation
41+
src/{{packageName}}-server.adb|{{package}}.Server|The server main procedure
42+
43+
Files generated in **src/server** should not be modified. The server implementation
44+
files (**src/{{packageName}}-server.ads** and **src/{{packageName}}-server.adb**) should
45+
be modified to implement the server operations. You can also customize the server
46+
main procedure according to your needs.
47+
48+
## Server model
49+
50+
The server instance is represented by the **{{package}}.Servers.Server_Type** Ada type.
51+
The REST API will need an instance of it to make the operation call. Two server model
52+
exists:
53+
54+
* The instance per request model creates an instance of the server type for each request.
55+
* The shared instance model shares the same instance across all concurrent REST requests. This instance is protected using an Ada protected object which holds the server instance.
56+
57+
The choice of the server model is made at the compilation time by instantiating either
58+
the **{{package}}.Skeletons.Skeleton** package or the **{{package}}.Skeletons.Shared_Instance**
59+
package. Such instantiation is done in **src/{{packageName}}-server.ads** and the default
60+
is to use the **Shared_Instance**.
61+
62+
## Implementing a server operation
63+
64+
All you have to do is implement the server operation in the **src/{{packageName}}-servers.adb** file.
65+
The package already contains the operation with its parameters and you only have to replace
66+
the **null** instruction by real code.
67+
68+
# Documentation
69+
70+
## API Documentation
71+
72+
All URIs are relative to *{{basePath}}*
73+
74+
Method | HTTP request | Description
75+
------------- | ------------- | -------------
76+
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}[**{{nickname}}**]({{apiDocPath}}{{classname}}.md#{{nickname}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
77+
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
78+
79+
## Models
80+
{{#models}}{{#model}} - [{{package}}.Models.{{classname}}]({{modelDocPath}}{{classname}}.md)
81+
{{/model}}{{/models}}
82+
83+
## Authorization
84+
{{^authMethods}} All endpoints do not require authorization.
85+
{{/authMethods}}{{#authMethods}}{{#last}} Authentication schemes defined for the API:{{/last}}{{/authMethods}}
86+
{{#authMethods}}## {{{name}}}
87+
88+
{{#isApiKey}}- **Type**: API key
89+
- **API key parameter name**: {{{keyParamName}}}
90+
- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
91+
{{/isApiKey}}
92+
{{#isBasic}}- **Type**: HTTP basic authentication
93+
{{/isBasic}}
94+
{{#isOAuth}}- **Type**: OAuth
95+
- **Flow**: {{{flow}}}
96+
- **Authorization URL**: {{{authorizationUrl}}}
97+
- **Scopes**: {{^scopes}}N/A{{/scopes}}
98+
{{#scopes}} - **{{{scope}}}**: {{{description}}}
99+
{{/scopes}}
100+
{{/isOAuth}}
101+
102+
{{/authMethods}}

modules/swagger-codegen/src/main/resources/Ada/client-body.mustache

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package body {{package}}.Clients is
77
{{#operation}}
88

99
-- {{summary}}{{#vendorExtensions.x-has-notes}}
10-
-- {{unescapedNotes}}{{/vendorExtensions.x-has-notes}}
10+
-- {{#lambdaAdaComment}}{{unescapedNotes}}{{/lambdaAdaComment}}{{/vendorExtensions.x-has-notes}}
1111
procedure {{operationId}}
1212
(Client : in out Client_Type{{#hasParams}};{{/hasParams}}{{#allParams}}
1313
{{paramName}} : in {{^isFile}}{{^isString}}{{^isPrimitiveType}}{{^isContainer}}{{package}}.Models.{{/isContainer}}{{/isPrimitiveType}}{{/isString}}{{/isFile}}{{dataType}}{{#hasMore}};{{/hasMore}}{{/allParams}}{{#returnType}};
@@ -19,8 +19,9 @@ package body {{package}}.Clients is
1919
Reply : Swagger.Value_Type;
2020
{{/returnType}}
2121
begin
22-
Client.Set_Accept (({{#hasProduces}}{{#produces}}{{#vendorExtensions.x-has-uniq-produces}}1 => {{/vendorExtensions.x-has-uniq-produces}}Swagger.Clients.{{adaMediaType}}{{#hasMore}},
23-
{{/hasMore}}{{/produces}}{{/hasProduces}}));{{#hasBodyParam}}
22+
{{#hasProduces}}
23+
Client.Set_Accept (({{#produces}}{{#vendorExtensions.x-has-uniq-produces}}1 => {{/vendorExtensions.x-has-uniq-produces}}Swagger.Clients.{{adaMediaType}}{{#hasMore}},
24+
{{/hasMore}}{{/produces}}));{{/hasProduces}}{{#hasBodyParam}}
2425
Client.Initialize (Req, ({{#hasConsumes}}{{#consumes}}{{#vendorExtensions.x-has-uniq-consumes}}1 -> {{/vendorExtensions.x-has-uniq-consumes}}Swagger.Clients.{{adaMediaType}}{{#hasMore}},
2526
{{/hasMore}}{{/consumes}}{{/hasConsumes}}{{^hasConsumes}}1 => Swagger.Clients.APPLICATION_JSON{{/hasConsumes}}));{{#bodyParams}}{{#vendorExtensions.x-is-model-type}}
2627
{{package}}.Models.Serialize (Req.Stream, "{{baseName}}", {{paramName}});{{/vendorExtensions.x-is-model-type}}{{^vendorExtensions.x-is-model-type}}{{#isFile}}

modules/swagger-codegen/src/main/resources/Ada/client-spec.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package {{package}}.Clients is
1212
{{#operations}}
1313
{{#operation}}
1414
-- {{summary}}{{#vendorExtensions.x-has-notes}}
15-
-- {{unescapedNotes}}{{/vendorExtensions.x-has-notes}}
15+
-- {{#lambdaAdaComment}}{{unescapedNotes}}{{/lambdaAdaComment}}{{/vendorExtensions.x-has-notes}}
1616
procedure {{operationId}}
1717
(Client : in out Client_Type{{#hasParams}};{{/hasParams}}{{#allParams}}
1818
{{paramName}} : in {{^isFile}}{{^isString}}{{^isPrimitiveType}}{{^isContainer}}{{package}}.Models.{{/isContainer}}{{/isPrimitiveType}}{{/isString}}{{/isFile}}{{dataType}}{{#hasMore}};{{/hasMore}}{{/allParams}}{{#returnType}};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
with {{package}}.Clients;
2+
with {{package}}.Models;
3+
with Swagger;
4+
with Util.Http.Clients.Curl;
5+
with Ada.Text_IO;
6+
with Ada.Command_Line;
7+
with Ada.Calendar.Formatting;
8+
with Ada.Exceptions;
9+
procedure {{package}}.Client is
10+
11+
use Ada.Text_IO;
12+
13+
procedure Usage;
14+
15+
Server : constant Swagger.UString := Swagger.To_UString ("http://localhost:8080/v2");
16+
Arg_Count : constant Natural := Ada.Command_Line.Argument_Count;
17+
Arg : Positive := 1;
18+
19+
procedure Usage is
20+
begin
21+
Put_Line ("Usage: {{projectName}} {params}...");
22+
end Usage;
23+
24+
begin
25+
if Arg_Count <= 1 then
26+
Usage;
27+
return;
28+
end if;
29+
Util.Http.Clients.Curl.Register;
30+
declare
31+
Command : constant String := Ada.Command_Line.Argument (Arg);
32+
Item : constant String := Ada.Command_Line.Argument (Arg + 1);
33+
C : {{package}}.Clients.Client_Type;
34+
begin
35+
C.Set_Server (Server);
36+
Arg := Arg + 2;
37+
38+
exception
39+
when E : Constraint_Error =>
40+
Put_Line ("Constraint error raised: " & Ada.Exceptions.Exception_Message (E));
41+
42+
end;
43+
end {{package}}.Client;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
abstract project Config is
2+
for Source_Dirs use ();
3+
4+
type Yes_No is ("yes", "no");
5+
6+
type Library_Type_Type is ("relocatable", "static");
7+
8+
type Mode_Type is ("distrib", "debug", "optimize", "profile");
9+
Mode : Mode_Type := external ("MODE", "debug");
10+
11+
Coverage : Yes_No := External ("COVERAGE", "no");
12+
Processors := External ("PROCESSORS", "1");
13+
14+
package Builder is
15+
case Mode is
16+
when "debug" =>
17+
for Default_Switches ("Ada") use ("-g", "-j" & Processors);
18+
when others =>
19+
for Default_Switches ("Ada") use ("-g", "-O2", "-j" & Processors);
20+
end case;
21+
end Builder;
22+
23+
package compiler is
24+
warnings := ("-gnatwua");
25+
defaults := ("-gnat2012");
26+
case Mode is
27+
when "distrib" =>
28+
for Default_Switches ("Ada") use defaults & ("-gnatafno", "-gnatVa", "-gnatwa");
29+
30+
when "debug" =>
31+
for Default_Switches ("Ada") use defaults & warnings
32+
& ("-gnata", "-gnatVaMI", "-gnaty3abcefhiklmnprstxM99");
33+
34+
when "optimize" =>
35+
for Default_Switches ("Ada") use defaults & warnings
36+
& ("-gnatn", "-gnatp", "-fdata-sections", "-ffunction-sections");
37+
38+
when "profile" =>
39+
for Default_Switches ("Ada") use defaults & warnings & ("-pg");
40+
end case;
41+
42+
case Coverage is
43+
when "yes" =>
44+
for Default_Switches ("ada") use Compiler'Default_Switches ("Ada") &
45+
("-fprofile-arcs", "-ftest-coverage");
46+
when others =>
47+
end case;
48+
end compiler;
49+
50+
package binder is
51+
case Mode is
52+
when "debug" =>
53+
for Default_Switches ("Ada") use ("-E");
54+
55+
when others =>
56+
for Default_Switches ("Ada") use ("-E");
57+
58+
end case;
59+
end binder;
60+
61+
package linker is
62+
case Mode is
63+
when "profile" =>
64+
for Default_Switches ("Ada") use ("-pg");
65+
66+
when "distrib" =>
67+
for Default_Switches ("Ada") use ("-s");
68+
69+
when "optimize" =>
70+
for Default_Switches ("Ada") use ("-Wl,--gc-sections");
71+
72+
when others =>
73+
null;
74+
end case;
75+
76+
case Coverage is
77+
when "yes" =>
78+
for Default_Switches ("ada") use Linker'Default_Switches ("ada") &
79+
("-fprofile-arcs");
80+
when others =>
81+
end case;
82+
end linker;
83+
84+
package Ide is
85+
for VCS_Kind use "git";
86+
end Ide;
87+
88+
end Config;

0 commit comments

Comments
 (0)