Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
JennieJi committed Apr 21, 2020
1 parent 8fb9f75 commit 8c90875
Show file tree
Hide file tree
Showing 14 changed files with 550 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
155 changes: 155 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
protobuf2swagger
===

Work in progress project for saving some life, update not garrenteed. Welcome for pull request :).

Main purpose is to convert [protobuf v2](https://developers.google.com/protocol-buffers/docs/proto) file to [openapi v3](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) JSON schema with NodeJS, and merge with some custom open api configurations.
Then you may render it easily with [SwaggerUI](https://github.com/swagger-api/swagger-ui).

# What is supported

- convert *enum*, *message* into components, paths will reference to the components schema
- basic types mapping to JS type *number*, *string*, *boolean* ( long types will be mapped to *string*)
- recognize fields:
- [OperationObject](https://swagger.io/specification/#operationObject).requestBody.$proto
Replace requestBody with a [Reference Object](https://swagger.io/specification/#referenceObject)
- [OperationObject](https://swagger.io/specification/#operationObject).responses.$proto
Replace responses['200'] with a [Reference Object](https://swagger.io/specification/#referenceObject)

# Install

`npm i -g protobuf2swagger`

# Cli Usage

`protobuf2swagger [config_file]`

| Argument | Description |
| --- | --- |
| config_file | Customize configuration file. Default to **protobuf2swagger.config.js** under current folder. |

For options may check `protobuf2swagger --help`. (Nothing there yet, seriously.)

## Config File

Example:
```javascript
module.exports = {
file: 'test.proto',
dist: 'apischema.json',
customSchema: { // Similar to openapi v3 format
info: {
title: 'API',
version: '1.0.0',
contact: {
name: 'Jennie Ji',
email: 'jennie.ji@hotmail.com',
url: 'jennieji.github.io'
},
},
tags: [{
name: 'test',
description: ''
}],
paths: {
'/api/test': {
get: {
requestBody: {
$proto: 'GetDataRequest', // Tell me the protobuf message name
},
responses: {
$proto: 'GetDataResponse', // Tell me the protobuf message name
}
}
}
},
components: {
securitySchemes: {
cookieAuth: {
type: 'apiKey',
in: 'cookie',
name: 'token'
}
}
},
security: [{
cookieAuth: []
}]
}
};
```

# Display with SwaggerUI

index.html (modified from swagger-ui-dist)

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>API Document</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.22.2/swagger-ui.css" >
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>

<body>
<div id="swagger-ui"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.22.2/swagger-ui-bundle.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.22.2/swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "./apischema.json", // Path to the generated schema JSON file
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
```

Serve with simple [express](https://www.npmjs.com/package/express) server:

```javascript
const express = require('express');
const app = express();

app.use(express.static(__dirname /* path to index.html */));
app.listen(3000);

console.info('Served at port 3000');
```
29 changes: 29 additions & 0 deletions bin/protobuf2swagger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env node
'use strict';

const path = require('path');
const program = require('commander');
const fs = require('fs');
const convert = require('../utils/convert.js');

program.version('0.0.0', '-v --version')
.arguments('[config_file]')
.usage('[config_file]')
.on('--help', () => {
console.log('\n');
console.log('config_file Customize configuration file. Default to protobuf2swagger.config.js under current folder.')
})
.parse(process.argv);

const [configPath] = program.args;
const cwd = process.cwd();
const DEFAULT_CONFIG_PATH = 'protobuf2swagger.config.js';
const config = require(path.resolve(cwd, configPath || DEFAULT_CONFIG_PATH));


(async () => {
const content = await convert(config);
const dist = config.dist ? path.resolve(cwd, config.dist) : cwd;
fs.writeFileSync(dist, JSON.stringify(content, null, 2));
console.info('Converted schema written into ', dist);
})();
102 changes: 102 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "protobuf2swagger",
"version": "0.0.0-alpha.1",
"description": "Convert protobuf to swagger open api v3 format",
"main": "index.js",
"bin": {
"protobuf2swagger": "./bin/protobuf2swagger.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/JennieJi/protobuf2swagger"
},
"keywords": [
"protobuf"
],
"author": "Jennie Ji <jennie.ji@hotmail.com>",
"license": "MIT",
"dependencies": {
"commander": "^2.20.0",
"protobufjs": "^6.8.8"
}
}
5 changes: 5 additions & 0 deletions utils/bakeRef.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function bakeRef(name) {
return `#/components/schemas/${name}`;
}

module.exports = bakeRef;
38 changes: 38 additions & 0 deletions utils/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const protobuf = require('protobufjs');
const fs = require('fs');
const processPaths = require('./processPaths.js');
const processComponents = require('./processComponents');

const OPENAPI_VERSION = '3.0.0';

async function convert({
file,
customSchema
}) {
// TODO: custom schema validate
const rootProto = await protobuf.parse(fs.readFileSync(file).toString(), {
alternateCommentMode: true
});
const { paths: rawPaths, components: rawComponents } = customSchema;
return {
...customSchema,
openapi: OPENAPI_VERSION,
paths: processPaths(rawPaths),
components: {
...(rawComponents || {}),
schemas: {
...processComponents(getProtoByPath(rootProto)),
...(rawComponents || {}).schemas,
}
}
};
}

function getProtoByPath(proto) {
return proto.package
.split('.')
.reduce((data, path) => data.get(path), proto.root)
.nested;
}

module.exports = convert;
10 changes: 10 additions & 0 deletions utils/enum2JSON.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function enum2JSON(proto) {
const { values, comments } = proto;
return {
type: "number",
enum: Object.values(values),
description: Object.keys(values).map(key => `${values[key]} - ${key} ${comments[key] ? `// ${comments[key]}` : ''}`).join('<br/>')
};
}

module.exports = enum2JSON;
Loading

0 comments on commit 8c90875

Please sign in to comment.