npm package: @pnmougel/monaco-rest-client
Description: A rest client for monaco supporting open api spec
Author: Pierre-Nicolas Mougel
Latest version: 0.0.29

# Monaco Rest client

This package provides a rest client for the [monaco editor](https://microsoft.github.io/monaco-editor/).

## Demo

[Try the demo](http://demo-client-rest.pierre-nicolas.fr/)

## Configuration

You can configure the rest client language with the following options :

| param | default | type | description |
|---|---|---|---|
| `validate` | `true` | `boolean` | If set to `false`, disable schema validation |
| `allowTrailingComma` | `true` | `boolean` | If set to `false`, trailing commas will generate error |
| `schemaDef` | `undefined` | `object` | Allows to define custom schema. See bellow. |
| `singleRequest` | `false` | `boolean` | If `true`, only a single request is allowed. |
| `allowComments` | `true` | `boolean` | If `false`, comments will generate errors. |
| `openApiSpec` | `undefined` | `OpenAPIObject` | The OpenApi specification to use |
| `customOptions` | `{}` | `object` | Custom options that can be used by custom resolvers |
| `customPathCompletion` | `undefined` | `CustomPathCompletionResolver` | Function used to resolve custom path param |
| `customQueryCompletion` | `undefined` | `CustomQueryCompletionResolver` | Function used to resolve custom query param |
| `customBodyCompletion` | `undefined` | `CustomBodyCompletionResolver` | Function used to resolve custom body param |
| `features` |  | `object` | Allows to disable monaco features. By default, all features are enabled. |

### Configure the rest client

You can configure the rest client either at startup with the `setupRestCLient` function. 

```typescript
const options: RestClientOptions = {...}
// At creation
const restClient = setupRestClient(monaco, options)

// Update options
restClient.options = options
```

When the options are updated, the webworker will also be restarted.

#### Set configuration option for a specific model

If you have multiple monaco editor in the same page and you want them to have different options, you can use `setModelOptions` to define custom options for a specific model.

```typescript
// Setup editor model
const modelUri = 'inmemory://model.json'
const model = monaco.editor.createModel('...', 'rest-client-lang', modelUri);

// Default language options
const restClient = setupRestClient(monaco, {})

// Options that will be used only for the provided model
restClient.setModelOptions(modelUri, {})
```

### Custom completions

You can provide custom function to add custom completions items to the language, for example to propose custom completion of path parameters.

You can define the functions `customPathCompletion`, `customQueryCompletion` and `customBodyCompletion` respectively for the path, query parameter and body contexts

Each of these function must return `Promise<CustomCompletionItem[]>` or `CustomCompletionItem[]`. The array can be empty if there is no completion to provide.

```typescript
export interface CustomCompletionItem {
    // Label displayed in the completion result
    label: string;
    // Optional description for the item
    description?: string;
    // Integer corresponding to the completion icon
    kind: number;
    // Optional type that will be displayed at the right of the label and in the infobox
    type?: string;
}
```

#### Custom path completion

```typescript
const options: RestClientOptions = {
  customPathCompletion: (
    // Name of the path parameter without the surrounding `{` and `}`
    paramName: string,
    // The schema corresponding to the path parameter
    paramSchema: any,
    // The `customOptions` parameter (see below "Provide options to custom completer")
    options: any) => {}
}
```

#### Custom body completion

```typescript
const options: RestClientOptions = {
  customBodyCompletion: (
    // 'key' if the completion corresponds to an object 'key', 'value' otherwise.
    kind: 'key' | 'value',
    // The schema corresponding to completion
    schema: any,
    // Object describing the current endpoint. 
    ctx: CustomBodyCompletionContext,
    // The `customOptions` parameter (see below "Provide options to custom completer")
    options: any) => {}
}
```

#### Custom query param completion

```typescript
const options: RestClientOptions = {
  customQueryCompletion: (
    // 'key' if the completion corresponds to an query param key
    // 'value'  if the completion corresponds to an query param value
    kind: 'key' | 'value',
    // Name of the query key if kind is `key`
    paramName: string,
    // The schema corresponding to completion
    paramSchema: any,
    // The `customOptions` parameter (see below "Provide options to custom completer")
    options: any) => {}
}
```

#### Example

```typescript
const options: RestClientOptions = {
  customPathCompletion: (paramName: string, paramSchema: any, options: any) => {
    if(paramName === 'index') {
      return [{
        label: 'Test 1',
        description: paramSchema.description,
        kind: 1,
      }, {
        label: 'Test 2',
        description: paramSchema.description,
        kind: 2,
      }]
    } else {
      return []
    }
  }
}
```

#### Provide options to custom completer

The completion functions will be run in the webworker context, thus **you will not be able to access any of your client application context** (e.g., `window`). If you want to pass options to these functions, use the `customOptions` parameter.

```typescript
window.serverUrl = 'https://...'

const options: RestClientOptions = {
  customOptions: {
    test: 'https://...'
  },
  customPathCompletion: (paramName: string, paramSchema: any, options: any) => {
    // This will be generate an error at runtome. 
    // `window` is not defined in the webworker context
    fetch(window.serverUrl)
    
    // This will work
    fetch(options.serverUrl)
  }
}
```

### Define custom schema

Instead of allowing the user to write an endpoint (e.g., `GET /_search`) you can force the language to validate only against a JSON schema. The behavior of the language will then be very similar to the monaco bundled `json` language.

There is three ways to define a custom schema :
 - Provide the path and method refering to a schema in the specification

```typescript
/**
 * The schema used for the json will be the schema corresponding of the `GET /_search` endpoint
 */
const options: RestClientOptions = {
  schemaDef: {
    type: 'path',
    path: '_search',
    method: 'GET'
  }
}
```
- Provide a ref to a schema in the specification

```typescript
/**
 * The schema used for the json will be the schema resolved for the ref `#/components/search/Request` in the specification
 */
const options: RestClientOptions = {
  schemaDef: {
    type: 'ref',
    $ref: '#/components/search/Request'
  }
}
```
- Provide a completely custom schema not necessarily existing in the specification.

```typescript
/**
 * The schema used for the json will be the custom schema { type: 'object', properties: {...}}
 */
const options: RestClientOptions = {
  schemaDef: {
    type: 'schema',
    schema: {
      type: 'object',
      properties: {...}
    }
  }
}
```


### Disable monaco features

You can disable specific monaco features using the `features` parameter.

```typescript
const options: RestClientOptions = {
  features: {
    // Disable completion
    completionItems: false,
    // Disable tooltip on hover
    hovers: false,
    // Disable folding
    foldingRanges: false,
    // Disable request validation
    diagnostics: false,
    // Disable request formating
    rangeFormatting: false,
    // Disable code lens (e.g., run action)
    codeLens: false,
    // Disable tokenization (if this is disabled, most of the other features are likely to not run correctly)
    tokens: false,
  }
}
```

## Install & usage

To use this package you MUST use `monaco-editor-core` and not `monaco-editor` 

```shell
npm i @pnmougel/monaco-rest-client monaco-editor-core
```

### Webpack config

In the webpack config, add an entry point for the web worker.

```js
module.exports = {
  entry: {
    // Rest client webworker
    'rest-client.worker': '@pnmougel/monaco-rest-client/dist/rest-client.worker.js',
    // Monaco webworker
    'editor.worker': 'monaco-editor-core/esm/vs/editor/editor.worker.js',
    // Other entrypoints
  },
  output: {
    filename: (chunkData) => {
      switch (chunkData.chunk.name) {
        case 'editor.worker':
          return 'editor.worker.js';
        case 'rest-client.worker':
          return "rest-client.worker.js"
        default:
          return '[name].bundle.js'
      }
    },
  },
}
```

### Vue-cli fix

In the `optimization.splitChunks.cacheGroups` section of your webpack config ensure that the `chunks` parameter of each entry is either not set or set to `async`. If you are using `vue-cli` it is likely to be the case.

You can check the generated webpack config for vue with the command `vue inspect`.

To fix this behavior enable `chainWebpack` in `vue.config.js`

```js
{
  ...
  chainWebpack: config => {
    const splitChunks = config.optimization.get('splitChunks')
    Object.entries(splitChunks.cacheGroups).forEach(([key, value]) => {
      value.chunks = 'async'
    })
    config.optimization.splitChunks(splitChunks)
  }
}
```

### Application setup

Load the webworkers in the entry point of your application and setup the rest client language

```js
import { restClientLanguageId, setupRestClient } from '@pnmougel/monaco-rest-client'
import * as monaco from 'monaco-editor-core'

(window as any).MonacoEnvironment = {
    getWorker: (moduleId: string, label: string) => {
        if (label === restClientLanguageId) {
            return new Worker('./rest-client.worker.js')
        }
        return new Worker('./editor.worker.js')
    },
}

const restClient = setupRestClient(monaco, {
  allowTrailingComma: true,
  openApiSpec: esSpec,
  features: {
    codeLens: true,
  },
  customOptions: {},
  customPathCompletion,
  customBodyCompletion,
})
```

### Enable an open api spec for an editor

```js
import { restClientLanguageId } from '@pnmougel/monaco-rest-client'

const openApiSpec: {
  // Your open api specification
}
const editor = monaco.editor.create(...)
restClient.setOpenApiSpec(openApiSpec, editor.getModel())
```

### Register a query runner

```js
// Register the editor action to run queries
restClient.registerRunQueryAction(editor)
restClient.setQueryRunner(editor.getModel().uri.toString(), (query) => {
  console.log(query)
})
```

## NPM commands

### Deploy the package

```
npm publish
```