swagger-editor Documentation

JavaScript hacktoberfest & open-source repository by @swagger-api. AI agent rules & system prompts with 9.4k stars.

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy swagger-editor on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name swagger-editor -p 8080:8080 --restart always swagger-api/swagger-editor:latest

File: README.md




โฐ๏ธ Looking for the next generation version of Swagger Editor?

Only SwaggerEditor@5 supports OpenAPI 3.1.0.
SwaggerEditor@4 will not receive OpenAPI 3.1.0 support and is considered deprecated at this point.


๐Ÿ•ฐ๏ธ Looking for the older version of Swagger Editor? Refer to the 2.x or 3.x branches.


Swagger Editor lets you edit OpenAPI API definitions (OpenAPI 2.0 and OpenAPI 3.0.3)
in JSON or YAML format inside your browser and to preview documentations in real time.
Valid OpenAPI definitions can then be generated and used with the full Swagger tooling (code generation, documentation, etc).

As a brand-new version, written from the ground up, there are some known issues and unimplemented features. Check out the Known Issues section for more details.

This repository publishes to two different NPM modules:

  • swagger-editor is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
  • swagger-editor-dist is a dependency-free module that includes everything you need to serve Swagger Editor in a server-side project, or a web project that can't resolve npm module dependencies.

If you're building a single-page application, using swagger-editor is strongly recommended, since swagger-editor-dist is significantly larger.

Anonymized analytics

Swagger Editor uses Scarf to collect anonymized installation analytics. These analytics help support the maintainers of this library and ONLY run during installation. To opt out, you can set the scarfSettings.enabled field to false in your project's package.json:

text
// package.json
{
  // ...
  "scarfSettings": {
    "enabled": false
  }
  // ...
}

Alternatively, you can set the environment variable SCARF_ANALYTICS to false as part of the environment that installs your npm packages, e.g., SCARF_ANALYTICS=false npm install.

Helpful scripts

Any of the scripts below can be run by typing npm run <script name> in the project's root directory.

Developing

Script name Description
dev Spawn a hot-reloading dev server on port 3200.
deps-check Generate a size and licensing report on Swagger Editors's dependencies.
lint Report ESLint style errors and warnings.
lint-errors Report ESLint style errors, without warnings.
lint-fix Attempt to fix style errors automatically.
watch Rebuild the core files in /dist when the source code changes. Useful for npm link.

Building

Script name Description
build Build a new set of JS and CSS assets, and output them to /dist.
build:bundle Build swagger-editor-bundle.js only (commonJS).
build:core Build swagger-editor.(js|css) only (commonJS).
build:standalone Build swagger-editor-standalone-preset.js only (commonJS).
build:stylesheets Build swagger-editor.css only.
build:es:bundle Build swagger-editor-es-bundle.js only (es2015).
build:es:bundle:core Build swagger-editor-es-bundle-core.js only (es2015).

Testing

Script name Description
test Run unit tests in Node, run Cypress end-to-end tests, and run ESLint in errors-only mode.
test:unit-mocha Run Mocha-based unit tests in Node.
test:unit-jest Run Jest-based unit tests in Node.
e2e Run end-to-end browser tests with Cypress.
lint Run ESLint test
test:artifact Run list of bundle artifact tests in Jest
test:artifact:umd:bundle Run unit test that confirms swagger-editor-bundle exports as a Function
test:artifact:es:bundle Run unit test that confirms swagger-editor-es-bundle exports as a Function
test:artifact:es:bundle:core Run unit test that confirms swagger-editor-es-bundle-core exports as a Function

Running locally

Prerequisites

  • git, any version
  • Node.js >=20.3.0 and npm >=9.6.7 are the minimum required versions that this repo runs on, but we always recommend using the latest version of Node.js.
shell
$ npm i --legacy-peer-deps

If you have Node.js and npm installed, you can run npm start to spin up a static server.

Otherwise, you can open index.html directly from your filesystem in your browser.

If you'd like to make code changes to Swagger Editor, you can start up a Webpack hot-reloading dev server via npm run dev.

Browser support

Swagger Editor works in the latest versions of Chrome, Safari, Firefox, and Edge.

Known Issues

To help with the migration, here are the currently known issues with 3.X. This list will update regularly, and will not include features that were not implemented in previous versions.

Docker

Running the image from DockerHub

There is a docker image published in docker.swagger.io registry.

To use this, run the following:

text
docker pull docker.swagger.io/swaggerapi/swagger-editor
docker run -d -p 80:8080 docker.swagger.io/swaggerapi/swagger-editor

This will run Swagger Editor (in detached mode) on port 80 on your machine, so you can open it by navigating to http://localhost in your browser.

  • You can provide a URL pointing to an API definition (may not be available if some security policies such as CSP or CORS are enforced):
text
docker run -d -p 80:8080 -e URL="https://petstore3.swagger.io/api/v3/openapi.json" docker.swagger.io/swaggerapi/swagger-editor
  • You can provide your own json or yaml definition file from your local host:
text
docker run -d -p 80:8080 -v $(pwd):/tmp -e SWAGGER_FILE=/tmp/swagger.json docker.swagger.io/swaggerapi/swagger-editor

Note: When both URL and SWAGGER_FILE environment variables are set, URL has priority and SWAGGER_FILE is ignored.

  • You can specify a different base url via BASE_URL variable for accessing the application - for example if you want the application to be available at http://localhost/swagger-editor/:
text
docker run -d -p 80:8080 -e BASE_URL=/swagger-editor docker.swagger.io/swaggerapi/swagger-editor
  • You can specify a different port via PORT variable for accessing the application, default is 8080.
text
docker run -d -p 80:80 -e PORT=80 docker.swagger.io/swaggerapi/swagger-editor
  • You can specify Google Tag Manager ID via GTM variable for tracking the usage of the swagger-editor.
text
docker run -d -p 80:8080 -e GTM=GTM-XXXXXX docker.swagger.io/swaggerapi/swagger-editor

You can also customize the different endpoints used by the Swagger Editor with the following environment variables. For instance, this can be useful if you have your own Swagger generator server:

Environment variable Default value
URL_SWAGGER2_GENERATOR https://generator.swagger.io/api/swagger.json
URL_OAS3_GENERATOR https://generator3.swagger.io/openapi.json
URL_SWAGGER2_CONVERTER https://converter.swagger.io/api/convert

If you want to run the Swagger Editor locally without the Codegen features (Generate Server and Generate Client) you can set the above environment variables to null (URL_SWAGGER2_CONVERTER=null).

Building and running an image locally

To build and run a docker image with the code checked out on your machine, run the following from the root directory of the project:

text
# Install npm packages (if needed)
npm install

# Build the app
npm run build

# Build an image
docker build -t swagger-editor .

# Run the container
docker run -d -p 80:8080 swagger-editor

You can then view the app by navigating to http://localhost in your browser.

Documentation

Using older version of React

[!IMPORTANT]
By older versions we specifically refer to React >=17 <18.

By default swagger-editor@4 npm package comes with latest version of React@18.
It's possible to use swagger-editor@4 npm package with older version of React.

Let's say my application integrates with swagger-editor@4 npm package and uses [email protected].

npm

In order to inform swagger-editor@4 npm package that I require it to use my React version, I need to use npm overrides.

json
{
  "dependencies": {
    "react": "=17.0.2",
    "react-dom": "=17.0.2"
  },
  "overrides": {
    "swagger-editor": {
      "react": "$react",
      "react": "$react-dom",
      "react-redux": "^8"
    }
  }
}

[!NOTE]
The React and ReactDOM override are defined as a reference to the dependency. Since react-redux@9 only supports React >= 18, we need to use react-redux@8.

yarn

In order to inform swagger-editor@4 npm package that I require it to use my specific React version, I need to use yarn resolutions.

json
{
  "dependencies": {
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "resolutions": {
    "swagger-editor/react": "17.0.2",
    "swagger-editor/react-dom": "17.0.2",
    "swagger-editor/react-redux": "^8"
  }
}

[!NOTE]
The React and ReactDOM resolution cannot be defined as a reference to the dependency. Unfortunately yarn does not support aliasing like $react or $react-dom as npm does. You'll need to specify the exact versions.

Security contact

Please disclose any security-related issues or vulnerabilities by emailing [email protected], instead of using the public issue tracker.


File: docs/customization/plug-points/editor-monaco-language-apidom.md

editor-monaco plug points

Configuring web worker capabilities

Worker accepts configuration for a language service via ApiDOM Context configuration object.
By default, this configuration looks like this:

js
{
  validatorProviders: [],
  completionProviders: [],
  performanceLogs: false,
  logLevel: apidomLS.LogLevel.WARN,
  completionContext: {
    maxNumberOfItems: 100,
    enableLSPFilter: false,
  },
}

If you want to override the default ApiDOM Context configuration object, you need to pass apiDOMContext option to the EditorMonacoLanguageApiDOM plugin.

js
EditorMonacoLanguageApiDOM({
  createData: {
    apiDOMContext: {
      completionContext: {
        enableLSPFilter: true, // enables "strict" word filtering (instead of default Monaco fuzzy matching; https://github.com/swagger-api/apidom/pull/2954)
      },
    },
  },
});

NOTE: note that the provided ApiDOM Context configuration object is merged with default ApiDOM Context configuration object
using deep-extend npm package.

Extending web worker capabilities

editor-monaco-language-apidom comes with implementation of apidom language.
The plugin comes with apidom.worker utilizing ApiDOM capabilities.
apidom.worker can be extended in two ways: dynamic and static.

Dynamic extension

Dynamic extension happens during runtime, and we recommend to use it only for simple use-cases.

First thing we need to do is to pass a customApiDOMWorkerPath option to the EditorMonacoLanguageApiDOM plugin.

js
EditorMonacoLanguageApiDOM({
  createData: {
    customApiDOMWorkerPath: 'https://example.com/index.js',
  },
});

customApiDOMWorkerPath is a URL (absolute or relative) of the extending module. The apidom.worker
loads it via dynamic import() at runtime.

The module must export a customApiDOMWorkerFactory function:

https://example.com/index.js

js
export const customApiDOMWorkerFactory = (ApiDOMWorkerClass, toolbelt) => {
  return ApiDOMWorkerClass;
};

The function receives two arguments:

  • ApiDOMWorkerClass - the class that implements the editor capabilities
  • toolbelt - an object containing various library exports

Here is a simple example demonstrating changing the log level of language service:

js
export const customApiDOMWorkerFactory = (ApiDOMWorkerClass, toolbelt) => {
  const { apidomLS } = toolbelt;

  class ApiDOMWorkerLogLevelErrorClass extends ApiDOMWorkerClass {
    static apiDOMContext = {
      ...ApiDOMWorkerClass.apiDOMContext,
      logLevel: apidomLS.LogLevel.ERROR,
    };
  }

  return ApiDOMWorkerLogLevelErrorClass;
};

Note: Classic (non-module) worker consumers may still use the globalThis.customApiDOMWorkerFactory
assignment pattern โ€” it is supported as a fallback when a named export is absent.

Static extension

Static extension requires a bundler (Vite, webpack, etc.) to build a custom worker entry point.

my-custom-apidom.worker.js

js
import { initialize, makeCreate, ApiDOMWorker } from 'swagger-editor/apidom.worker';

class ApiDOMWorkerExtended extends ApiDOMWorker {
  // implementation of extensions
}

const create = makeCreate(ApiDOMWorkerExtended);

initialize((ctx, createData) => create(ctx, createData));

export { initialize, create, makeCreate, ApiDOMWorkerExtended as ApiDOMWorker };

Point your bundler at my-custom-apidom.worker.js as the apidom.worker entry and configure
MonacoEnvironment.getWorker to serve the built file. For example, with webpack:

js
entry: {
    app: './index.js',
    'apidom.worker': './my-custom-apidom.worker.js',
    'editor.worker': 'swagger-editor/editor.worker',
  }

Passing data to web workers

Often when extending web worker capabilities it is the case that we need to pass additional
data to web worker. These data may include any arbitrary data compatible with
the structured clone algorithm.

Let's consider the following use-case. We need to extend the apidom.worker in a way that
it will be fetching data on demand from authorized REST endpoint.

Dynamic extension

EditorMonacoLanguageApiDOM plugin configuration.

js
EditorMonacoLanguageApiDOM({
  createData: {
    authToken: 'c32d8b45-92fe-44f6-8b61-42c2107dfe87',
    customApiDOMWorkerPath: 'https://example.com/index.js',
  },
});

https://example.com/index.js

js
export const customApiDOMWorkerFactory = (ApiDOMWorkerClass, toolbelt) => {
  const { apidomLS } = toolbelt;

  class ApiDOMWorkerLogLevelErrorClass extends ApiDOMWorkerClass {
    static apiDOMContext = {
      ...ApiDOMWorkerClass.apiDOMContext,
      logLevel: apidomLS.LogLevel.ERROR,
    };

    async loadData() {
      // createData passed as plugin option is available in worker as this._createData
      const { authToken } = this._createData;

      return await fetch(`https://example.com/data?authToken=${authToken}`)
    }
  }

  return ApiDOMWorkerLogLevelErrorClass;
};

Static extension

Whenever you extend the ApiDOMWorker class you will have _createData public property available.

my-custom-apidom.worker.js

js
import { initialize, makeCreate, ApiDOMWorker } from 'swagger-editor/apidom.worker';

class ApiDOMWorkerExtended extends ApiDOMWorker {
  async loadData() {
    // createData passed as plugin option is available in worker as this._createData
    const { authToken } = this._createData;

    return await fetch(`https://example.com/data?authToken=${authToken}`)
  }
}

const create = makeCreate(ApiDOMWorkerExtended);

initialize((ctx, createData) => create(ctx, createData));

export { initialize, create, makeCreate, ApiDOMWorkerExtended as ApiDOMWorker };

File: docs/customization/plug-points/README.md

Plug points


File: docs/architecture.md

High level architecture of SwaggerEditor@5

This document briefly describes high level architecture of SwaggerEditor@5.

Plugin architecture

SwaggerEditor@5 is composed of number of plugins. These plugins are divided into four
distinct categories

  1. Plugins providing editor implementations
  2. Plugins providing preview of editor content
  3. Editor implementation support plugins
  4. Generic features plugins

Plugins providing editor implementations

These plugins include editor-textarea and editor-moanco.

editor-textarea is a base plugin that other editor implementations build on.
This plugin provides basic editing experience using <textarea /> HTML tag.

editor-monaco builds on top of editor-textarea and provide advanced editing
experience using Monaco Editor.

Plugins providing preview of editor content

These plugins include editor-preview-swagger-ui, editor-preview-asyncapi and others.
The single responsibility of editor preview plugin is to render the editor content (text)
to a set of UI components.

Editor implementation support plugins

These plugins include editor-persistence, editor-read-only and others.
Plugins in this category are responsible to support plugins providing editor implementation
with additional enhanced capabilities in generic way.

Generic features plugins

These plugins include dialogs, modals, layout and others. Each plugin in this
category provide features or enhancements to plugins in previous three categories.

Future architectural changes

This section describes opportunities to make the architecture of SwaggerEditor@5 better.

State management

Currently, editor content (text) is stored in SwaggerUI spec plugin. This causes various issues
which manifests in decreased user experience of editing text. Every time the editor content
changes the spec plugin will try to parse it as JSON or YAML, resolve it and store it in
redux state. This is absolutely not necessary. We can store editor content in editor implementation plugin
and have preview plugins work as finite state machines and detect if they should process the editor content in any way.
When this architectural change is implemented, the typing lag on big amount of editor content (text)
will significantly decrease.


File: docs/migration.md

Migrate from SwaggerEditor@4 to SwaggerEditor@5

Table of Contents

Introduction

SwaggerEditor@5 is a ground-up rewrite of SwaggerEditor@4. However, SwaggerEditor@5 is still fundamentally based on the SwaggerUI plugin system, and continues to be developed within the React ecosystem. For users without custom plugins or other forked customizations, SwaggerEditor@5 is intended to be mostly drop-in compatible with the existing SwaggerEditor@4.

SwaggerEditor@5 introduces integration with ApiDOM and Microsoft's Monaco Editor. ApiDOM replaces the parser from SwaggerClient, as well as existing custom validation from SwaggerEditor@4. Monaco Editor replaces Ace Editor. Additional documentation on features and usage of ApiDOM and Monaco Editor can be found within their respective documention. Also available are the ApiDOM Playground and Monaco Editor Playground

This migration guide is intended to highlight setup changes and key customization features for SwaggerEditor@5.

Development

SwaggerEditor@5 is using Create React App as it's building infrastructure. Therefore there are a few minor differences
to develop SwaggerEditor@5

Start script with hot-reloading

new:

text
$ npm start

old:

text
$ npm run dev

Static build script

new:

text
$ npm run build:app
$ npm run build:app:serve

old:

text
$ npm start

Local & remote definitions

Only files inside public can be used from public/index.html. JS files must be put inside src.

new:

text
<SwaggerEditor url={url} />

old:
In dev-helpers/index.html, add a url key like this:

text
const editor = SwaggerEditorBundle({
  dom_id: '#swagger-editor',
  url: "some-local-or-remote-path-to-definition.yaml" # define path here
  layout: 'StandaloneLayout',
  presets: [
    SwaggerEditorStandalonePreset
  ]
})

Linting

Automatic linting checks remain part of the commit process. SwaggerEditor@5 now generally follows the eslint recommendations from the AirBnb and React teams.

Dependencies

SwaggerEditor@5 is now based on swagger-ui-react instead of swagger-ui. In addition, unlike SwaggerEditor@4, SwaggerEditor@5 does not have a dependency on SwaggerClient.

Testing

Unit tests are now covered by jest and @testing-library. Unit tests are now co-located next to their component. Component tests follow the @testing-library philosophy to avoid including implementation details, so that changes to implementation but not functionality doesn't break existing tests.

E2E tests are now covered by Playwright and are located in the test/playwright directory.

Design

Plugins

SwaggerEditor@5 maintains its core as an extension of SwaggerUI's plugin system. SwaggerEditor@5 exports itself as a fully realized set of plugins, each of which can be extended and wrapped as needed. This even includes the new Monaco Editor feature as a plugin!

Compared to SwaggerEditor@4, there is also no change to precedence with regards to the order of loading plugins.

SwaggerEditor@5 provides a modals plugin for a unified modal system. The modals plugin is further extended via the dialogs plugin which provides a set of alert and confirm modal dialogs.

Any existing custom plugins for SwaggerEditor@4 that modified the behavior of, or directly interfaced with, the Ace Editor will likely need to be heavily refactored. Custom validation rules should be migrated to ApiDOM as needed.

plugins available at release

  • dialogs
  • dropzone
  • editor-monaco
  • editor-persistence
  • editor-preview-asyncapi
  • editor-preview-swagger-ui
  • editor-read-only
  • editor-spec-origin
  • editor-textarea
  • layout
  • modals
  • topbar

plugins not yet migrated

At the time of this writing there are two plugins from SwaggerEditor@4 that have not been migrated:

  1. jump-to-path. Note: expect to leverage ApiDOM and editor-monaco plugin to replace AST and Ace Editor.
  2. topbar-insert. Note: expect to leverage and wrap components of various existing plugins: topbar, editor-monaco, modals, and dialogs.

Layout

SwaggerEditor@5 comes with a plugin for a "core" layout. The "core" layout provides plug points that are analogous to "panes" and/or "bars" in other IDEs. These plug points make it easier for developers to create plugins that customize their UX with as minimal changes to the "core" layout as possible.

The "core" layout includes contains EditorPane, EditorPreviewPane, and Topbar. These three components represent the basic UX wireframe for SwaggerEditor@5. In addition, the EditorPane is provided with four surrounding bars (top, bottom, left, right) that can each be customized with their own wrapped implementations.

Using itself as a reference design, SwaggerEditor@5 provides implementation of two different EditorPane (editor-monaco and editor-textarea), and two different EditorPreviewPane (editor-preview-swagger-ui and editor-preview-asyncapi). The editor-monacoplugin also further extends the baseEditorPaneBarTopandEditorPaneBarBottom` with its own wrapped version of each.


File: docs/migration-legacy-summary.md

Swagger Editor Legacy (v3/v4) Migration Summary

Topbar Features

[x] Import from URL
[x] Import from File
[x] Save As Json
[x] Save As Yaml
[x] Convert from Json to Yaml, then download
[x] Convert from Yaml to Json, then download
[x] Convert from Json to Yaml, in editor only
[x] Clear Editor (moved from File Menu to Edit Menu)
[x] Convert from OAS2 to OAS3
[x] Generate Servers list - OAS2
[x] Generate Servers list - OAS3.0
[x] Generate Clients list - OAS2
[x] Generate Clients list - OAS3.0
[x] Display/Hide menu items based on combination of json/yaml, oas2/oas3
[x] NEW: consistent use of styled Modal components throughout (react-modal)
[x] NEW: Load default OAS2/OAS3/OAS3.1/AsyncApi2 definition; w/keyboard shortcuts
[x] Removed: topbar-insert (plugin); no replacement planned

Editor Features

[x] NEW: Monaco Editor
[x] NEW: validation from apidom-ls library
[x] Removed: Ace Editor
[x] Removed: internal validation from swagger-editor v3/v4
[x] Drag-n-drop local file to editor (react-dropzone)
[x] Persist definition on browser refresh/reload (localStorage)
[x] Display validation pane results

Visual UI Features

[x] SwaggerUI v4; React 17

Implementation Changes

[x] React Hooks support, e.g. menu item display toggle, modal system
[x] Behavioral testing of components, e.g. not the implementation details
[x] Simplify and reorganize presets, layouts, and plugins
[x] Extract menu action methods from Components to Actions
[x] Extract menu logic helpers to Actions
[x] Extract shared menu logic helpers to utils/editor-converter
[x] Replace fetch with axios http helpers to utils/topbar-http
[x] deprecate plugins with direct implementation: Convert to OAS3, Import File
[x] Removed: unnecessary state where possible
[x] Removed: window.alert and window.confirm popups
[x] Removed: swagger-client library dependency
[x] Tests: testing-library + jest (unit), playwright (e2e)

Possible additional/expanded docs/contributing topics

[x] Separation of actions from components. tldr: easier to test, possibly more re-usable
[x] React Hooks. tldr: modern practices
[x] testing-library. tldr: focus on user behaviors, more durable/maintainable tests
[x] reorganizing the presets. e.g. what happened to standalone and other plugins? The new recommended structure. tldr: multi-spec, multi-layouts

List of migrated legacy methods

Note: likely to remove this section from final doc

Extract menu action methods from React to Actions
[x] importFromURL
[x] saveAsYaml
[x] saveAsJson
[x] convertToYaml
[x] downloadGeneratedFile
[x] importFile
[x] onDocumentLoad prop - removed. handled now in actions. Theoretically, we could expose as a user-overwritable function. Maybe SH needed it?
[x] updateEditorContent prop - will be removed, and handled in actions.
[x] clearEditor

Deprecate methods from React
[x] saveAsText
[x] handleResponse

Extract menu logic helpers to Actions
[x] getGeneratorUrl
[x] instantiateGeneratorClient
[x] shouldReInstantiateGeneratorClient (new)

Extract menu logic helpers to utils-converter
[x] hasParserErrors
[x] getFileName
[x] getDefinitionLanguage
[x] getDefinitionVersion

Create (axios) http method to utils-http
[x] getDefinitionFromUrl
[x] getGenerator2Definition
[x] postPerformOasConversion

Remove unnecessary state:
[x] swaggerClient
[x] definitionVersion

Remove use of alert and confirm via new modal system
[x] alert
[x] confirm

Additional notes (remove from final doc)

[x] swagger-ui redux state
[x] connect monaco state to swagger-ui redux state
[x] connect actions with monaco state, e.g. updateEditorContent
[x] init oas spec via swagger-ui
[x] connect & sync generic-editor updates to swagger-ui
[x] init oas spec via import File (finish action) - Json
[x] init oas spec via import File (finish action) - Yaml
[x] init oas spec via import Url (finish action)
[x] CSS/Less/Saas styling
[x] remove use of mock data in topbarActions. (mock fixtures not removed yet)
[x] try react-modal lib instead of creating internal version
[x] modify generic-editor plugin to have a default editor placeholder (instead of calling GenericEditorContainer directly)
[x] add disposables array, and ability to dispose()
[x] make generator servers/clients toggleable; enable oas3.1/others when supported
[x] File Menu dropdown - add json/yaml detection to display appropriate link
[x] Edit menu dropdown - render convert to OAS3 only if currently 'isSwagger2`
[x] Edit menu dropdown - render convert to Yaml only if currently 'json'
[x] onboard - default definition should be in YAML format

Resolved Issues (remove from final doc)

[x] fix exploding styling/rendering when using in-browser (ctrl+f) find text within monaco; monaco's find+replace feature USING P2M/M2P FIXES THIS ISSUE
[x] warning: overlapping semantic tokens THIS IS APIDOM-LS ISSUE
[x] fix editor configuration/onChange to always word-wrap.
[x] remove "dev mode" case when editor content is undefined
[x] remove mock data/fixtures/configuration from actions.js
[x] in codeActionUi, sometimes recieve an Uncaught promise TypeError: d.dispose is not a funtion
[x] asyncapi support: anywhere we should be detecting async api (or any other supported spec); e.g. clearEditor
[x] handle apidom parser throw, when unable to detect langugage. e.g. empty string, or oas2
[x] load default (oas3). note the topbar generate server/client exists. -> clear all. user may type random string. note that the topbar generate server/client did NOT disappear
[x] load default -> import OAS2. note the topbar generate server/client exists. -> clear all. user may type random string. note that the topbar generate server/client did NOT disappear


File: docs/migration-tasks-todo.md

Migration Status

Known Issues

[ ] handle case(s) when specSelectors.method returns undefined

Integration Tasks

[ ] match and extend configurability options for editor & monaco

Backlog

[ ] update "minimal" specs/fixtures for clearEditor; oas2, oas3, oas3_1, asycapi2
[ ] memory performance benchmarks
[ ] if refresh empty monaco, should MAY load a default definition
[ ] jump-to-path (plugin)

Ideas

[ ] topbar: topbar-insert (plugin); this should be integrated into the Topbar plugin
[ ] topbar: update generator (plugin?) for oas3.1, asyncapi, etc. support when available
[ ] monaco: update syntax highlighting as needed
[ ] monaco - additional initial style and configuration of monaco editor, as needed

Test Coverage Ideas

[ ] should add unit tests with bad/invalid urls; generator, import Url
[ ] should add unit tests when both swagger2 and oas3 flags set to same value (both true, both false)
[ ] should add unit tests allowing exclusion of both swagger2 and oas3, e.g. future asynapi, graphql, etc.
[ ] e2e: monaco-editor features


File: docs/monaco-worker.md

Monaco Client (React)

  • setup a Monaco instance
    • setup monaco environment use of language worker(s) to use
    • instantiate web workers
    • register providers via Adapters
  • create a Monaco instance
    Adapter
  • an adapter for each service or provider
  • generally expect a provide[Description] method that matches monaco interface
  • offloads language service functions to language web worker
  • run and/or return a monaco editor "effect". e.g. hover, completion, based on web worker result
  • not required to use Typescript
    WebWorker
  • called via an adapter
  • call a language service method in separate thread
  • recommended use by Monaco
    LanguageService
  • called via a web worker
  • any lib, in our case, apidom
  • examples include typescript/javascript, json, css

notes:

  • some minor naming differences across libs

File: docs/swagger-client.md

Swagger-client is used as middleware to transform generator responses,
which also includes function methods alongside data objects.

fetch GET call to SwaggerClient(url, [options]) will return response.apis methods.

  • reminder to attach .catch() to any response.apis.[method] call
text
response.apis.clients:
- clientOptions: ฦ’(parameters)
- downloadFile: ฦ’(parameters)
- generateClient: ฦ’(parameters)
- getClientOptions: ฦ’(parameters)

response.apis.servers:
- downloadFile: ฦ’(parameters)
- generateServerForLanguage: ฦ’(parameters)
- getServerOptions: ฦ’(parameters)
- serverOptions: ฦ’(parameters)

Summary

In order to download a generated definition, legacy swagger-editor made two calls to swagger-client.

  1. Given OAS3 or Swagger2/OAS2 definition, swagger-editor makes a request to Generator3 or Generator2 respectively.

Generator3/Generator2 returns a response that includes it's own definition, which swagger-client, as middleware, translates the defintion included in Generator's response into a more usable response.apis.clients format. This format also includes new execute methods for swagger-editor to call.

  1. swagger-editor calls the appropriate execute method that was created by swagger-client to download the generated definition. Note, Generator3 returns the Blob directly, while Generator2 returns a json object containing an URI link to the Blob.

Additionally, legacy swagger-editor saved these methods to React state for general purpose use, e.g. downloadFile vs clientOptions.

In this new version of swagger-editor, use of swagger-client has been deprecated, so these methods are no longer available or saved. Equivalent functionality to directly interface with Generator3/Generator2 now exists in topbarActions. This change also should now always yield a deterministic final result.


File: docs/wasm-bundling.md

WASM Bundling in ESM/UMD Library Builds

The apidom.worker.js bundle contains three WASM binaries that must be fully
self-contained โ€” no runtime HTTP fetches โ€” for the worker to function inside a
Webpack-bundled consumer (swagger-editor-plus and similar).

All three WASMs are handled by the inlineAllWasms Vite plugin defined in
vite.config.shared.js and used by both vite.config.esm.js and vite.config.umd.js.
See the Plugin ordering section for how the three hooks work together.

The three binaries

Binary Source How it is loaded at runtime
tree-sitter.wasm web-tree-sitter (emscripten) Emscripten's own locateFile() / fetch() machinery
tree-sitter-yaml.wasm @swagger-api/apidom-parser-adapter-yaml-1-2 web-tree-sitter Language.load(arg)
tree-sitter-json.wasm @swagger-api/apidom-parser-adapter-json web-tree-sitter Language.load(arg)

`tree-sitter.wasm` โ€” emscripten runtime binary

The problem

Naively inlining tree-sitter.wasm as a data URI (e.g. by replacing the
'tree-sitter.wasm' string literal in the bundle) breaks emscripten's fetch
interception. Tools like @rollup/plugin-wasm patch globalThis.fetch with an
endsWith('tree-sitter.wasm') guard:

js
globalThis.fetch = (...args) =>
  isString(args[0]) && args[0].endsWith('tree-sitter.wasm')
    ? realFetch(tree_sitter_default, ...)
    : realFetch(...args);

If the literal is replaced with a ~250 KB data URI, the endsWith condition
becomes url.endsWith('<250KB data URI>'). A string always ends with itself, so
when emscripten then calls fetch(dataUri), the broken branch fires and passes
the loader function as the URL argument to realFetch, producing a request to
http://โ€ฆ/function%20tree_sitter_default(Sn)%20{โ€ฆ}.

The fix โ€” `renderChunk` hook in `inlineAllWasms`

Instead of replacing the 'tree-sitter.wasm' literal, inject the binary into
emscripten's Module object before any fetch is attempted. Emscripten reads
Module.wasmBinary into a local variable during module init, and
getBinaryPromise() checks that variable first:

js
function getBinaryPromise(path) {
  return wasmBinary          // โ† truthy โ†’ fetch is never called
    ? Promise.resolve().then(() => getBinarySync(path))
    : readAsync(path)...;
}

The renderChunk hook in inlineAllWasms matches the emscripten module
init line (present in Rollup's output before esbuild minifies it):

text
var Module = typeof Module != "undefined" ? Module : {};

and injects Module['wasmBinary'] = new Uint8Array(atob("โ€ฆ").split("").map(โ€ฆ))
immediately after it. By the time emscripten reaches getBinaryPromise, the
local wasmBinary is already set and fetch() is never called.


Grammar WASMs โ€” `Language.load()` binaries

The problem

The apidom yaml/json adapter packages import their grammar WASMs as ES modules:

js
// @swagger-api/apidom-parser-adapter-yaml-1-2/src/lexical-analysis/browser.mjs
import treeSitterYaml from '../../wasm/tree-sitter-yaml.wasm';
// ...
await Language.load(treeSitterYaml);

Without intervention, a WASM plugin such as @rollup/plugin-wasm would convert
each .wasm import into an async loader function:

js
function tree_sitter_yaml_default(imports) {
  return _loadWasmModule(0, null, ``, imports);
}

Language.load() in web-tree-sitter accepts either a URL string or a
Uint8Array. It does NOT accept a function. When given a function it calls
fetch(fn), which stringifies the function and tries to fetch:

text
http://โ€ฆ/function%20tree_sitter_yaml_default(Sn)%20{โ€ฆ}

Why a plain `load` hook does not work (historical context)

@rollup/plugin-wasm has both a load hook and a transform hook:

js
// @rollup/plugin-wasm โ€” simplified
load(id) {
  if (!/\.wasm$/.test(id)) return null;
  // reads file, emits asset
},
transform(code, id) {
  if (code && /\.wasm$/.test(id)) {
    // replaces module content with the loader function code
  }
}

If a preceding plugin's load hook returns valid JavaScript for a .wasm id,
@rollup/plugin-wasm's transform hook still fires (the id still ends with
.wasm) and overwrites the JS with the loader function. The fix must prevent
the transform hook from seeing the .wasm id at all.

The fix โ€” `inlineAllWasms` in `vite.config.shared.js`

All three WASMs are handled by a single inlineAllWasms Vite plugin (shared
between vite.config.esm.js and vite.config.umd.js via vite.config.shared.js).
It combines three hooks:

js
const inlineAllWasms = () => {
  const treeSitterBase64 = readFileSync('node_modules/web-tree-sitter/tree-sitter.wasm').toString('base64');
  return {
    name: 'inline-all-wasms',
    enforce: 'pre',

    // Redirect every .wasm import to a virtual id that does NOT end with '.wasm',
    // bypassing any downstream plugin that checks id.endsWith('.wasm').
    async resolveId(id, importer) {
      if (!id.endsWith('.wasm')) return null;
      const resolved = await this.resolve(id, importer, { skipSelf: true });
      if (!resolved) return null;
      return '\0wasm-inline:' + resolved.id + ':inline';
    },

    // Export the raw bytes as a Uint8Array. Language.load(bytes) accepts this directly.
    // For tree-sitter.wasm the exported value is unused โ€” emscripten reads Module.wasmBinary.
    load(id) {
      if (!id.startsWith('\0wasm-inline:')) return null;
      const filePath = id.slice('\0wasm-inline:'.length, -':inline'.length);
      const base64 = readFileSync(filePath).toString('base64');
      return `const bytes=new Uint8Array(atob("${base64}").split("").map(function(c){return c.charCodeAt(0)}));export default bytes;`;
    },

    // Inject Module['wasmBinary'] into the final bundle at emscripten's module
    // init line so getBinaryPromise() short-circuits before fetch() is called.
    renderChunk(code) {
      const updated = code.replace(
        /var Module\s*=\s*typeof Module\s*!=\s*["']undefined["']\s*\?\s*Module\s*:\s*\{\}/,
        (match) => `${match};Module['wasmBinary']=new Uint8Array(atob("${treeSitterBase64}").split("").map(function(c){return c.charCodeAt(0)}))`
      );
      return updated !== code ? { code: updated } : null;
    },
  };
};

inlineAllWasms must be a Vite plugin (in the plugins array), not a
Rollup plugin (in rollupOptions.plugins). Rollup plugins run after Vite's
transform pipeline, so placing it in rollupOptions.plugins would not give it
priority over other WASM-handling plugins.


Verification after a build

sh
# Check tree-sitter.wasm is inlined (Module.wasmBinary injection present)
grep -c "wasmBinary" dist/esm/apidom.worker.js   # โ†’ โ‰ฅ1
grep -c "wasmBinary" dist/umd/apidom.worker.js   # โ†’ โ‰ฅ1

# Check grammar WASMs are Uint8Arrays, not loader functions
grep -c "tree_sitter_yaml_default\|tree_sitter_json_default" dist/esm/apidom.worker.js  # โ†’ 0
grep -c "tree_sitter_yaml_default\|tree_sitter_json_default" dist/umd/apidom.worker.js  # โ†’ 0

# Language.load should receive bytes$N (Uint8Array), not a function
grep "Language.load(" dist/esm/apidom.worker.js  # โ†’ Language.load(bytes$1)