Или Приготовление колбасы
В этом посте я рассмотрю 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
Вот и все! Здесь я упустил некоторые детали, относящиеся к конкретному приложению. Я буду рад ответить на любые ваши вопросы, поэтому оставьте комментарий в комментариях.