Или Приготовление колбасы

В этом посте я рассмотрю swagger-codegen и swagger-codegen-cli. В частности, я собираюсь внести изменения в код, который генерирует скелет сервера Node.js Swagger Editor. Это потребует изменений как в Java-коде, так и в шаблонах Mustache.

Шаблоны

Проще говоря, генератор кода Swagger считывает вашу спецификацию API и генерирует внутреннюю модель данных. Эта модель данных используется для заполнения шаблонов, которые могут давать код для различных языков и платформ.

Система шаблонов, используемая swagger-codegen, - Moustache. Шаблоны усов не логичны: в самом шаблоне нет условных операторов. Это означает, что структура данных должна легко повторяться и не содержать сюрпризов.

В исходном коде swagger-codegen шаблоны для сервера Node.js можно найти по адресу

swagger-codegen/modules/swagger-codegen/src/main/resource/nodejs

Давайте сначала посмотрим на сгенерированный код для PetService.js:

'use strict';
exports.findPets = function(args, res, next) {
  /**
   * parameters expected in the args:
  * tags (List)
  * limit (Integer)
  **/
    var examples = {};
  examples['application/json'] = [ {
  "name" : "aeiou",
  "id" : 123456789,
  "tag" : "aeiou"
} ];
  if(Object.keys(examples).length > 0) {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(examples[Object.keys(examples)[0]] || {}, null, 2));
  }
  else {
    res.end();
  }
  
}

Экспортируется сервис для поиска домашних животных, который позже будет вызван кодом контроллера в Pet.js; имя метода службы определяется определением API:

/pets:
    get:
      tags:
        - pet
      description: Returns all pets from the system that the user has access to
      operationId: findPets

operationId определяет вызываемый метод контроллера (в Pets.js), который передается методу службы:

var Pet = require('./PetService');
module.exports.findPets = function findPets (req, res, next) {
  Pet.findPets(req.swagger.params, res, next);
};

Глядя на сервисный метод findPets, мне действительно не нравится, как данные-заглушки загромождают скелет метода. Что, если я хочу сохранить заглушку (и условно вернуть ее) после того, как я написал настоящую реализацию? Для большой и длинной заглушки JSON возвращаемые данные будут затмевать код реализации.

Что я могу сделать, так это переписать шаблон, чтобы поместить все образцы данных-заглушек в нижнюю часть файла PetService.js, чтобы моя реализация доминировала в верхней части листинга кода.

Взглянув на исходный шаблон (PetService.mustache), мы можем увидеть, как был сгенерирован код:

'use strict';

{{#operations}}
{{#operation}}
exports.{{nickname}} = function(args, res, next) {
  /**
   * parameters expected in the args:
  {{#allParams}}* {{paramName}} ({{dataType}})
  {{/allParams}}**/
  {{^returnType}}// no response value expected for this operation
  {{/returnType}}
  {{#returnType}}
  var examples = {};
  {{#examples}}examples['{{contentType}}'] = {{{example}}};
  {{/examples}}
  if(Object.keys(examples).length > 0) {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(examples[Object.keys(examples)[0]] || {}, null, 2));
  }
  else {
    res.end();
  }
  {{/returnType}}
  {{^returnType}}res.end();{{/returnType}}
}

{{/operation}}
{{/operations}}

Часто бывает сложно (если не невозможно) взглянуть на шаблон и узнать, какие данные передаются. В этом случае, однако, мы можем сделать достаточно выводов, изучив шаблон и сгенерированный в настоящее время код, чтобы попытаться переместить данные примера. в конец списка. Все, что нужно сделать, это удалить код шаблона данных примера в первой итерации через операции и создать вторую итерацию через операции и сгенерировать там данные примера. Некоторая логика поиска заглушек должна измениться, но это несущественно. Новый шаблон выглядит так:

'use strict';
var useStub = require('./utils').useStub;
{{#operations}}
var adapter = require('./{{classname}}ServiceAdapter');
{{#operation}}
exports.{{nickname}} = function(args, res, next) {
  /**
   * parameters expected in the args:
  {{#allParams}}* {{paramName}} ({{dataType}})
  {{/allParams}}**/
  {{^returnType}}// no response value expected for this operation
  {{/returnType}}
  {{#returnType}}
if (useStub("{{nickname}}") || !adapter.{{nickname}}) {
      res.setHeader('Content-Type', 'application/json');
      res.send(examples.{{nickname}});
  }
  else {
    adapter.{{nickname}}(args, res)
    .then((result) => {
        res.status(result.status).send(result.body);
    })
    .catch((e) => {
        res.status(e.status).send(e.message);
    });
  }
  {{/returnType}}
  {{^returnType}}res.end();{{/returnType}}
}
{{/operation}}
{{/operations}}
/*
 * EXAMPLE DATA FOLLOWS
 */
var examples = {};
{{#operations}}
{{#operation}}
{{#examples}}examples.{{nickname}} = {{{example}}};
{{/examples}}
{{/operation}}
{{/operations}}

В этой реализации у меня есть адаптер службы, который вызывается (если он существует), если не установлен флаг, который заставил бы функцию useStub () вернуть значение true. Все данные примера перемещены в конец, и на них ссылается псевдоним функции (например, findPets).

Создание собственных исходных файлов

Все хорошо, но я также хочу создавать тесты, и мне нужен один тест для каждой службы (каждый с несколькими экспортированными методами службы), и я хочу, чтобы тестовый код был в отдельных файлах. Для этого мне нужно внести небольшие изменения в Java-часть swagger-codegen.

Подобно структуре папок шаблона, Java-код для Node.js можно найти по адресу:

swagger-codegen/modules/swagger-codegen/src/main/java

Пакет Java - io.swagger.codegen. Если вы перейдете в папку / languages ​​этого пакета, вы найдете всю Java для каждой платформы, которая поддерживается редактором Swagger Editor (посредством swagger-codegen). Нас интересует NodeJSServerCodegen.java, а именно эти строки:

writeOptional(outputFolder, new SupportingFile("package.mustache", "", "package.json"));
        writeOptional(outputFolder, new SupportingFile("README.mustache", "", "README.md"));
        if (System.getProperty("noservice") == null) {
            apiTemplateFiles.put(
               "service.mustache",   // the template to use
               "Service.js");       // the extension for each file to write
        }

Обратите внимание, что здесь есть SupportingFiles и apiTemplateFiles. Эти два типа файлов отправляют разные структуры данных в шаблоны Mustache. Какие структуры данных? Рад, что вы спросили… с Mustache (в отличие от его кузена Handlebars) нет простого способа выгрузить входящую структуру данных из самого шаблона. Однако в документации упоминаются флаги, которые можно передать интерфейсу командной строки swagger-codegen-cli, который выведет структуру данных на консоль:

При создании библиотек вы можете использовать следующие флаги отладки:

[debugSwagger] печатает спецификацию чванства в интерпретации кодогенератора.

[debugModels] печатает модели, переданные в механизм шаблонов.

[debugOperations] печатает операции, переданные в механизм шаблонов

[debugSupportingFiles] печатает дополнительные данные, переданные в механизм шаблонов.

Модель операций [debugOperations] - это то, что отправляется в apiTemplateFiles; он содержит только данные о работе API. SupportingFiles содержит почти все, что определено в спецификации API [debugSupportingFiles].

Для тестов нам нужны только операции, поэтому мы можем написать шаблон test.mustache и добавить его в apiTemplateFiles:

apiTemplateFiles.put(
       "test.mustache",   // the template to use
       "ServiceTest.js");   // the extension for each file to write

Мой тестовый шаблон может выглядеть примерно так:

{{#operations}}
var service = require('../controllers/{{classname}}Service');
var chai = require('chai');
var _ = require('lodash');
// these tests are run directly against the services (not the http controllers), so 
// we need a mock response object. Use the "res" variable as a parameter to the service method under test
var responseMock = require('./mocks.js').response;
var res = new responseMock();
chai.should();
describe("{{classname}}-tests", function() {
    {{#operation}}
    it('test for {{nickname}}', function(done){
        // test assertions go here
    });
    {{/operation}}
});
{{/operations}}

Единственный недостаток заключается в том, что в отличие от класса, используемого для создания SupportingFiles, я не могу указать папку вывода для тестов. Это не большая проблема - я могу переместить тестовые файлы во вложенную папку, как только они будут созданы.

Конечно, после того, как вы изменили код генерации кода, вам нужно будет перекомпилировать его с помощью maven: mvn clean package

Когда вы это сделаете, будет создан исполняемый jar-файл swagger-codegen-cli как /modules/swagger-codegen-cli/target/swagger-codegen-cli.jar. Чтобы создать новый сгенерированный код, вы запускаете файл jar из командной строки следующим образом:

java -jar swagger-codegen-cli.jar generate \
-i ../swagger-server/api/swagger.yaml \
-l nodejs-server \
-t ./resources/nodejs \
-o ../swagger-server

Вот и все! Здесь я упустил некоторые детали, относящиеся к конкретному приложению. Я буду рад ответить на любые ваши вопросы, поэтому оставьте комментарий в комментариях.