Initial Commit

This commit is contained in:
Luke Hagar
2023-05-05 12:50:58 -05:00
commit 93b098e6de
23 changed files with 2579 additions and 0 deletions

13
.eslintignore Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

20
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

13
.prettierignore Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

99
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,99 @@
{
"prettier.documentSelectors": [
"**/*.svelte"
],
"tailwindCSS.classAttributes": [
"class",
"accent",
"active",
"background",
"badge",
"bgBackdrop",
"bgDark",
"bgDrawer",
"bgLight",
"blur",
"border",
"button",
"buttonAction",
"buttonBack",
"buttonClasses",
"buttonComplete",
"buttonDismiss",
"buttonNeutral",
"buttonNext",
"buttonPositive",
"buttonTextCancel",
"buttonTextConfirm",
"buttonTextNext",
"buttonTextPrevious",
"buttonTextSubmit",
"caretClosed",
"caretOpen",
"chips",
"color",
"cursor",
"display",
"element",
"fill",
"fillDark",
"fillLight",
"flex",
"gap",
"gridColumns",
"height",
"hover",
"invalid",
"justify",
"meter",
"padding",
"position",
"regionBackdrop",
"regionBody",
"regionCaption",
"regionCaret",
"regionCell",
"regionCone",
"regionContent",
"regionControl",
"regionDefault",
"regionDrawer",
"regionFoot",
"regionFooter",
"regionHead",
"regionHeader",
"regionIcon",
"regionInterface",
"regionInterfaceText",
"regionLabel",
"regionLead",
"regionLegend",
"regionList",
"regionNavigation",
"regionPage",
"regionPanel",
"regionRowHeadline",
"regionRowMain",
"regionTrail",
"ring",
"rounded",
"select",
"shadow",
"slotDefault",
"slotFooter",
"slotHeader",
"slotLead",
"slotMessage",
"slotMeta",
"slotPageContent",
"slotPageFooter",
"slotPageHeader",
"slotSidebarLeft",
"slotSidebarRight",
"slotTrail",
"spacing",
"text",
"track",
"width"
]
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Luke Hagar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
# openapi-definition-generator
JSON to OpenAPI Definition generator

39
package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "swagger-definition-generator",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@skeletonlabs/skeleton": "^1.2.5",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte3": "^4.0.0",
"json-to-pretty-yaml": "^1.2.2",
"postcss": "^8.4.23",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"svelte-check": "^3.0.1",
"tailwindcss": "^3.3.2",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.2.0",
"yaml": "^2.2.2"
},
"type": "module"
}

6
postcss.config.cjs Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

9
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
}

12
src/app.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="skeleton">
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
</body>
</html>

2
src/app.postcss Normal file
View File

@@ -0,0 +1,2 @@
/*place global styles here */
html, body { @apply h-full overflow-hidden; }

49
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,49 @@
<script lang="ts">
// The ordering of these imports is critical to your app working properly
import '@skeletonlabs/skeleton/themes/theme-skeleton.css';
// If you have source.organizeImports set to true in VSCode, then it will auto change this ordering
import '@skeletonlabs/skeleton/styles/all.css';
// Most of your app wide CSS should be put in this file
import '../app.postcss';
import { AppShell, AppBar } from '@skeletonlabs/skeleton';
</script>
<!-- App Shell -->
<AppShell>
<svelte:fragment slot="header">
<!-- App Bar -->
<AppBar>
<svelte:fragment slot="lead">
<strong class="text-xl">Swagger Definition Objects Generator</strong>
</svelte:fragment>
<svelte:fragment slot="trail">
<a
class="btn btn-sm variant-ghost-surface"
href="https://discord.gg/EXqV7W8MtY"
target="_blank"
rel="noreferrer"
>
Discord
</a>
<a
class="btn btn-sm variant-ghost-surface"
href="https://twitter.com/SkeletonUI"
target="_blank"
rel="noreferrer"
>
Twitter
</a>
<a
class="btn btn-sm variant-ghost-surface"
href="https://github.com/skeletonlabs/skeleton"
target="_blank"
rel="noreferrer"
>
GitHub
</a>
</svelte:fragment>
</AppBar>
</svelte:fragment>
<!-- Page Route Content -->
<slot />
</AppShell>

356
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,356 @@
<script lang="ts">
import { onMount } from 'svelte';
//@ts-ignore
import { stringify } from 'yaml';
let inJSON: any;
let inputJSON = '';
let outSwagger = '';
let requestExamples: boolean;
let noInt: boolean;
let yamlOut: boolean;
let tabCount: number;
let indentator: string;
let nullType: string;
let parseErr: Error | null;
let timeOut: any;
const trigger = (evt: Event) => {
clearTimeout(timeOut);
timeOut = setTimeout(() => convert(), 100);
};
const convert = () => {
localStorage.setItem('inputJSON', inputJSON);
try {
inJSON = JSON.parse(inputJSON);
parseErr = null;
} catch (e: any) {
parseErr = e;
return;
}
//For recursive functions to keep track of the tab spacing
tabCount = 0;
indentator = '\n';
// ---- Begin definitions ----
outSwagger = '{';
changeIndentation(1);
//For each object inside the JSON
for (let obj in inJSON) {
// ---- Begin schema scope ----
outSwagger += indentator + '"' + obj + '": {';
conversorSelection(inJSON[obj]);
outSwagger += indentator + '},';
// ---- End schema scope ----
}
//Remove last comma
outSwagger = outSwagger.substring(0, outSwagger.length - 1);
// ---- End definitions ----
changeIndentation(tabCount - 1);
outSwagger += indentator + '}';
outSwagger = format(outSwagger);
};
function changeIndentation(count: number) {
/*
Assign 'indentator' a string beginning with newline and followed by 'count' tabs
Updates variable 'tabCount' with the number of tabs used
Global variables updated:
-indentator
-tabCount
*/
let i;
if (count >= tabCount) {
i = tabCount;
} else {
i = 0;
indentator = '\n';
}
for (; i < count; i++) {
indentator += '\t';
}
//Update tabCount
tabCount = count;
}
function conversorSelection(obj: any) {
/*
Selects which conversion method to call based on given obj
Global variables updated:
-outSwagger
*/
changeIndentation(tabCount + 1);
if (typeof obj === 'number') {
//attribute is a number
convertNumber(obj);
} else if (Object.prototype.toString.call(obj) === '[object Array]') {
//attribute is an array
convertArray(obj);
} else if (typeof obj === 'object') {
//attribute is an object
convertObject(obj);
} else if (typeof obj === 'string') {
//attribute is a string
convertString(obj);
} else if (typeof obj === 'boolean') {
// attribute is a boolean
outSwagger += indentator + '"type": "boolean"';
} else {
// not a valid Swagger type
alert('Property type "' + typeof obj + '" not valid for Swagger definitions');
}
changeIndentation(tabCount - 1);
}
function convertNumber(num: number) {
/*
Append to 'outSwagger' string with Swagger schema attributes relative to given number
Global variables updated:
-outSwagger
*/
if (num % 1 === 0 && !noInt) {
outSwagger += indentator + '"type": "integer",';
if (num < 2147483647 && num > -2147483647) {
outSwagger += indentator + '"format": "int32"';
} else if (Number.isSafeInteger(num)) {
outSwagger += indentator + '"format": "int64"';
} else {
outSwagger += indentator + '"format": "unsafe"';
}
} else {
outSwagger += indentator + '"type": "number"';
}
if (requestExamples) {
//Log example if checkbox is checked
outSwagger += ',' + indentator + '"example": "' + num + '"';
}
}
//date is ISO8601 format - https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14
function convertString(str: string) {
/*
Append to 'outSwagger' string with Swagger schema attributes relative to given string
Global variables updated:
-outSwagger
*/
let regxDate = /^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/,
regxDateTime =
/^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]).([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]{1,3})?(Z|(\+|\-)([0-1][0-9]|2[0-3]):[0-5][0-9])$/;
outSwagger += indentator + '"type": "string"';
if (regxDateTime.test(str)) {
outSwagger += ',';
outSwagger += indentator + '"format": "date-time"';
} else if (regxDate.test(str)) {
outSwagger += ',';
outSwagger += indentator + '"format": "date"';
}
if (requestExamples) {
//Log example if checkbox is checked
outSwagger += ',' + indentator + '"example": "' + str + '"';
}
}
function convertArray(obj: any[]) {
/*
Append to 'outSwagger' string with Swagger schema attributes relative to given array
Global variables updated:
-outSwagger
*/
let schema: any = {};
let examples = new Set();
for (const entry of obj) {
for (const key of Object.keys(entry)) {
if (!Object.keys(schema).includes(key)) {
examples.add(entry);
schema[key] = entry[key];
}
}
}
outSwagger += indentator + '"type": "array",';
// ---- Begin items scope ----
outSwagger += indentator + '"items": {';
conversorSelection(schema);
outSwagger += indentator + '}';
// ---- End items scope ----
// ---- Begin example scope ----
if (requestExamples) {
outSwagger += ',' + indentator + '"example": ' + JSON.stringify([...examples], null, '\t');
}
}
function convertObject(obj: any) {
/*
Append to 'outSwagger' string with Swagger schema attributes relative to given object
Global variables updated:
-outSwagger
*/
//Convert null attributes to given type
if (obj === null) {
outSwagger += indentator + '"type": "' + nullType + '",';
outSwagger += indentator + '"format": "nullable"';
return;
}
// ---- Begin properties scope ----
outSwagger += indentator + '"type": "object",';
outSwagger += indentator + '"properties": {';
changeIndentation(tabCount + 1);
//For each attribute inside that object
for (var prop in obj) {
// ---- Begin property type scope ----
outSwagger += indentator + '"' + prop + '": {';
conversorSelection(obj[prop]);
outSwagger += indentator + '},';
// ---- End property type scope ----
}
changeIndentation(tabCount - 1);
if (Object.keys(obj).length > 0) {
//At least 1 property inserted
outSwagger = outSwagger.substring(0, outSwagger.length - 1); //Remove last comma
outSwagger += indentator + '}';
} else {
// No property inserted
outSwagger += ' }';
}
}
function format(value: string) {
/*
Convert JSON to YAML if yaml checkbox is checked
Global variables updated:
NONE
*/
value = JSON.stringify(JSON.parse(value), null, '\t');
if (yamlOut) {
return stringify(JSON.parse(value));
} else {
return value;
}
}
onMount(() => {
let tempJSON = localStorage.getItem('inputJSON');
if (tempJSON !== null && tempJSON !== '') {
inputJSON = tempJSON;
} else {
inputJSON = `{
"numbersMock": {
"smallInt": -20,
"bigInt": 2147483647,
"unsafeInt": 9999999999999999,
"notInt": 12.2
},
"stringsMock": {
"stringTest": "Hello World",
"isoDate": "1999-12-31",
"isoDateTime": "1999-12-31T23:59:59Z"
},
"objectsMock": {
"child": {"child": true},
"childList": [{"child": true}],
"childMatrix": [[{"child": true}]],
"nullable": null
}
}`;
}
convert();
});
</script>
<svelte:head>
<meta charset="UTF-8" />
<title>Swagger Generator</title>
</svelte:head>
<p class="text-center p-8 relative">
Add your JSON mock to generate Swagger definitions.
{#if parseErr && inputJSON != ''}
<aside class="alert variant-filled-warning absolute m-4 center inset-0">
<h3>Error in JSON</h3>
<p>{parseErr}</p>
</aside>
{/if}
</p>
<div class="flex flex-row justify-between p-2 gap-2">
<textarea
id="JSON"
rows="35"
cols="85"
class="grow textarea"
placeholder="Type your JSON"
contenteditable
on:input={trigger}
on:paste
bind:value={inputJSON}
/>
<textarea
readonly
id="Swagger"
rows="35"
cols="85"
class="grow textarea"
placeholder="Here is your Swagger"
bind:value={outSwagger}
/>
</div>
<div class="flex flex-row justify-center px-4 gap-24">
<label class="label">
Convert null values to:
<select bind:value={nullType} on:change={() => convert()} class="select" id="nullType">
<option value="string" selected>String</option>
<option value="number">Number</option>
<option value="integer">Integer</option>
<option value="boolean">Boolean</option>
</select>
</label>
<div>
<label>
Add values as examples:
<input
bind:checked={requestExamples}
on:change={() => convert()}
class="checkbox"
type="checkbox"
id="requestExamples"
/>
</label>
<label>
Convert integer values to number:
<input
bind:checked={noInt}
on:change={() => convert()}
class="checkbox"
type="checkbox"
id="noInt"
/>
</label>
<label>
Output as YAML:
<input
bind:checked={yamlOut}
on:change={() => convert()}
class="checkbox"
type="checkbox"
id="yamlOut"
/>
</label>
</div>
</div>
<p class="text-center pt-4">
Feel like collaborating? Clone the repository at <a
target="_blank"
href="https://github.com/Roger13/SwaggerGenerator">GitHub</a
>
</p>

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

18
svelte.config.js Normal file
View File

@@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

16
tailwind.config.cjs Normal file
View File

@@ -0,0 +1,16 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: [
'./src/**/*.{html,js,svelte,ts}',
require('path').join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
],
theme: {
extend: {}
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
...require('@skeletonlabs/skeleton/tailwind/skeleton.cjs')()
]
};

17
tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
vite.config.ts Normal file
View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});

1859
yarn.lock Normal file

File diff suppressed because it is too large Load Diff