Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce Swagger Editor middleware #800

Merged
merged 12 commits into from
Nov 5, 2024
5 changes: 5 additions & 0 deletions .changeset/tender-candles-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/swagger-editor': major
---

Create swagger editor middleware for hono
25 changes: 25 additions & 0 deletions .github/workflows/ci-swagger-editor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: ci-swagger-editor
on:
push:
branches: [main]
paths:
- 'packages/swagger-editor/**'
pull_request:
branches: ['*']
paths:
- 'packages/swagger-editor/**'

jobs:
ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/swagger-editor
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"build:zod-openapi": "yarn workspace @hono/zod-openapi install && yarn workspace @hono/zod-openapi build",
"build:typia-validator": "yarn workspace @hono/typia-validator build",
"build:swagger-ui": "yarn workspace @hono/swagger-ui build",
"build:swagger-editor": "yarn workspace @hono/swagger-editor build",
"build:esbuild-transpiler": "yarn workspace @hono/esbuild-transpiler build",
"build:event-emitter": "yarn workspace @hono/event-emitter build",
"build:oauth-providers": "yarn workspace @hono/oauth-providers build",
Expand Down
40 changes: 40 additions & 0 deletions packages/swagger-editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Swagger Editor Middleware for Hono

This library, `@hono/swagger-editor` is the middleware for integrating Swagger Editor with Hono applications. The Swagger Editor is an open source editor to design, define and document RESTful APIs in the Swagger Specification.

## Installation

```bash
npm install @hono/swagger-editor
# or
yarn add @hono/swagger-editor
```

## Usage

You can use the `swaggerEditor` middleware to serve Swagger Editor on a specific route in your Hono application. Here's how you can do it:


```ts
import { Hono } from 'hono'
import { swaggerUI } from '@hono/swagger-ui'

const app = new Hono()

// Use the middleware to serve Swagger Editor at /swagger-editor
app.get('/swagger-editor', swaggerEditor({ url: '/doc' }))

export default app
```

## Options

Middleware supports almost all swagger-editor options. See full documentation: <https://swagger.io/docs/open-source-tools/swagger-editor/>

## Authors

- Ogabek Yuldoshev <https://github.com/OgabekYuldoshev>

## License

MIT
48 changes: 48 additions & 0 deletions packages/swagger-editor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@hono/swagger-editor",
"version": "0.0.0",
"description": "A middleware for using Swagger Editor in Hono",
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.cts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": [
"dist"
],
"scripts": {
"test": "vitest run",
"build": "tsup ./src/index.ts --format esm,cjs --dts",
"prerelease": "yarn build && yarn test",
"release": "yarn publish"
},
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/honojs/middleware.git"
},
"homepage": "https://github.com/honojs/middleware",
"peerDependencies": {
"hono": "*"
},
"devDependencies": {
"hono": "^3.11.7",
"tsup": "^7.2.0",
"vitest": "^0.34.5"
}
}
195 changes: 195 additions & 0 deletions packages/swagger-editor/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import type { Context } from 'hono'
import type { CustomSwaggerUIOptions } from './types'

const DEFAULT_VERSION = '4.13.1'

const CDN_LINK = 'https://cdn.jsdelivr.net/npm/swagger-editor-dist'

export const MODERN_NORMALIZE_CSS = `
*,
::before,
::after {
box-sizing: border-box;
}

html {
font-family:
system-ui,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji';
line-height: 1.15; /* 1. Correct the line height in all browsers. */
-webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */
tab-size: 4; /* 3. Use a more readable tab size (opinionated). */
}

body {
margin: 0;
}

b,
strong {
font-weight: bolder;
}

code,
kbd,
samp,
pre {
font-family:
ui-monospace,
SFMono-Regular,
Consolas,
'Liberation Mono',
Menlo,
monospace; /* 1 */
font-size: 1em; /* 2 */
}

small {
font-size: 80%;
}

sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}

sub {
bottom: -0.25em;
}

sup {
top: -0.5em;
}

table {
border-color: currentcolor;
}

button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}

button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
}

legend {
padding: 0;
}

progress {
vertical-align: baseline;
}

::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}

[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}

::-webkit-search-decoration {
-webkit-appearance: none;
}

::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}

summary {
display: list-item;
}

.Pane2 {
overflow-y: scroll;
}
`

function getUrl(version?: string) {
return `${CDN_LINK}@${version ? version : DEFAULT_VERSION}`
}

export interface SwaggerEditorOptions extends CustomSwaggerUIOptions {
version?: string
}

export function swaggerEditor(options: SwaggerEditorOptions = {}) {
const url = getUrl()

options.layout = 'StandaloneLayout'

const optionString = Object.entries(options)
.map(([key, value]) => {
if (typeof value === 'string') {
return `${key}:'${value}'`
}
if (Array.isArray(value)) {
return `${key}:${value.map((v) => `${v}`).join(', ')}`
}
if (typeof value === 'object') {
return `${key}:${JSON.stringify(value)}`
}

return `${key}: ${value}`
})
.join(',')

return async (c: Context) =>
c.html(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger Editor</title>
<style>
${MODERN_NORMALIZE_CSS}
</style>
<link href="${url}/swagger-editor.css" rel="stylesheet">
<link rel="icon" type="image/png" href="${url}/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="${url}/favicon-16x16.png" sizes="16x16" />
</head>

<body>
<div id="swagger-editor"></div>
<script src="${url}/swagger-editor-bundle.js"> </script>
<script src="${url}/swagger-editor-standalone-preset.js"> </script>
<script>
window.onload = function() {
const editor = SwaggerEditorBundle({
dom_id: '#swagger-editor',
presets: [
SwaggerEditorStandalonePreset
],
queryConfigEnabled: true,
${optionString}
})
window.editor = editor
}
</script>
</body>
</html>
`)
}
Loading