Comparison of code-generation tools
OpenAPI Generator
OpenAPI Usage
Given, some valid OpenAPI spec the following can be done.
generate server stub from open-api spec
docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli generate -i /local/petstore.yaml -g aspnetcore -o /local/out/aspnetcore
output mustache templates
docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli author template -g aspnetcore -o /local/template/aspnetcore
view list of mustache templates here:
create custom templates
docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli generate -i /local/petstore.yaml -g aspnetcore -o /local/out/aspnetcore -c /local/config.yaml
create custom generator
docker run --rm -v %CD%\local:/local openapitools/openapi-generator-cli meta -o /local/generators/my-aspnetcore -n my-aspnetcore -p com.my.aspnetcore
customize open-api template. below is an example of the controller.mustache
template
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;
using Newtonsoft.Json;
using .Attributes;
using ;
namespace
{
/// <summary>
///
/// </summary>
[Description("")]
[ApiController]
public class Controller : ControllerBase
{
/// <summary>
///
/// </summary>
/// <remarks></remarks>
/// <param name=""> (deprecated)</param>
/// <response code=""></response>
[]
[Route("}}")]
[Authorize(Policy = "")]
[Authorize(Roles = ",")]
[Consumes()]
[ValidateModelState]
[SwaggerOperation("")]
[SwaggerResponse(statusCode: , type: typeof(), description: "")]
[ProducesResponseType(statusCode: , type: typeof())]
[Obsolete]
public async Task<IActionResult> (, );
{
var = Request.Cookies[""];
//TODO: Uncomment the next line to return response or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(, default());
//TODO: Uncomment the next line to return response or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode();
string exampleJson = null;
exampleJson = "}";
//TODO: Change the data returned
return Task.FromResult<IActionResult>(new ObjectResult(example));
throw new NotImplementedException();
}
}
}
OpenAPI Findings
Pros:
- templating approach looks applicable to any required files, e.g. class, CI (dockerfile), config etc.
Cons:
- mustache files are hard to read, arbitrary in their layout and purpose.
-
lots of “tool” specific lang to learn and that lang is not .NET although that’s what it can output.
-
can’t seem to get arbitrary additional files generated in combination with the input data model from open-api spec. e.g., Validators file and types can be internal to “Controller” folder, or external, but then don’t have API data model applied i.e. can’t use enumerations of
-
templateTypes appear to be hard-coded to folder paths.
- even with a custom generator you appear to have very limited api to work with, e.g. you can only control the output folder for the “api” and “models”, amongst other small things.
Yeoman generator
Yeoman Usage
- install node with chocolatey
- install yo:
npm install -g yo
yo --version
# install a generator
npm install -g <generator-name>
# e.g.
npm install -g generator-webapp
# scaffold new project using installed generator
yo <generator-name>
# e.g.
yo webapp
# use sub-generators, e.g. add a controller
yo <generator-name>:<sub-generator-name>
# troubleshoot & debug
yo doctor
yo --generators
yo --help
yo --version
# create folder to hold generator, name is NB: generator-name, e.g. generator-sample
cd generator-name
# create package.json in folder
echo '' > package.json or npm init
npm install --save yeoman-generator
# create folders
# ├───package.json
# └───generators/
# ├───app/
# │ └───index.js
# └───router/
# └───index.js
npm link
yo <generator-name> # e.g. yo sample [doesn't need 'generator' prefixing it]
The available priorities are (in running order):
initializing - Your initialization methods (checking current project state, getting configs, etc) prompting - Where you prompt users for options (where you’d call this.prompt()) configuring - Saving configurations and configure the project (creating .editorconfig files and other metadata files) default - If the method name doesn’t match a priority, it will be pushed to this group. writing - Where you write the generator specific files (routes, controllers, etc) conflicts - Where conflicts are handled (used internally) install - Where installations are run (npm, bower) end - Called last, cleanup, say good bye, etc
Yeoman Sample
namespace <%= namespace %>.Controllers
{
[Route("api")]
[ApiController]
public class AccountController : ControllerBase
{
<%_ if (authenticationType === 'jwt') { _%>
private readonly ILogger<AccountController> _log;
<%_ if (cqrsEnabled) { _%>
private readonly IMediator _mediator;
<%_ } else { _%>
private readonly IMapper _userMapper;
private readonly IMailService _mailService;
private readonly UserManager<User> _userManager;
private readonly IUserService _userService;
<%_ } _%>
<%_ if (cqrsEnabled) { _%>
public AccountController(ILogger<AccountController> log, IMediator mediator)
{
_log = log;
_mediator = mediator;
}
<%_ } else { _%>
public AccountController(ILogger<AccountController> log, UserManager<User> userManager, IUserService userService,
IMapper userMapper, IMailService mailService)
{
_log = log;
_userMapper = userMapper;
_userManager = userManager;
_userService = userService;
_mailService = mailService;
}
<%_ } _%>
[HttpPost("register")]
[ValidateModel]
public async Task<IActionResult> RegisterAccount([FromBody] ManagedUserDto managedUserDto)
{
if (!CheckPasswordLength(managedUserDto.Password)) throw new InvalidPasswordException();
<%_ if (cqrsEnabled) { _%>
var user = await _mediator.Send(new AccountCreateCommand { ManagedUserDto = managedUserDto });
<%_ } else { _%>
var user = await _userService.RegisterUser(_userMapper.Map<User>(managedUserDto), managedUserDto.Password);
await _mailService.SendActivationEmail(user);
<%_ } _%>
return CreatedAtAction(nameof(GetAccount), user);
}
/// etc...
}
}
}
Yeoman Findings
Pros:
- well known tool, widely used in JS world
- API looks quite flexible and capable of generating full-on real-world APIs end-to-end e.g.:
- JHipster-DotNetCore
- WebApi sample
- The tool is capable of being run after initial generation.
- Applicable to a wide number of languages as it isn’t language specific, just a templating engine.
Cons:
- It’s in JS, and I’m more focused on .NET so the learning curve is greater.
- It is still “just” templating., i.e. very similar to T4 etc.
- Unlike OpenAPI generator, it’s “just” a generator, it’s not specifically going to generate OpenAPI spec, that would need to be written / combined.
T4 templates
T4 Usage
T4 Findings
Roslyn API
Roslyn Usage
Roslyn Findings
OpenAPI Docs
Yeoman Docs
T4 Docs
Roslyn Docs
Other References
- …