# convert

Source: https://apigenie.pl/docs/reference/cli/convert

Convert a Swagger 2.0 document to OpenAPI 3.2, a WSDL 1.1 document to OpenAPI 3.1, or a JSON
Schema document of a newer draft to draft-07, draft-04 or the Schema Object dialect of OpenAPI 3.0.

> **(experimental, outside the 1.0 compatibility promise)** `convert` has three sources, Swagger
> 2.0, WSDL 1.1 and JSON Schema, and its options and output can still change. It says so in its
> `--help` and on the first line it prints, on the standard error. See the
> [experimental commands](/docs/reference/cli/overview#experimental-commands).

## Usage

```sh
apigenie convert --to openapi32 [...OPTIONS] SWAGGER_FILE
apigenie convert --to openapi31 [...OPTIONS] WSDL_FILE
apigenie convert --to draft7|draft4|openapi30 [...OPTIONS] SCHEMA_FILE
```

## Description

The command reads a Swagger 2.0 document, a WSDL 1.1 document or a JSON Schema document and
writes the converted document, to the standard output or to the file given with `--output`. A
Swagger document becomes OpenAPI 3.2 (`--to openapi32`), a WSDL document OpenAPI 3.1
(`--to openapi31`), and a JSON Schema document a draft-07 (`--to draft7`), draft-04
(`--to draft4`) or OpenAPI 3.0 schema (`--to openapi30`). The source is the one `--from` names;
without `--from`, an input that starts with `<` (an XML document) is WSDL, a document with a
`swagger` member Swagger 2.0, and any other input a JSON Schema.

## Swagger 2.0 input

The command reads a Swagger 2.0 document (`swagger: "2.0"`, JSON or YAML) and writes the
equivalent OpenAPI 3.2 document. OpenAPI 3.x documents are not accepted as input: they are read as
Swagger documents and rejected (`Missing required property 'swagger'`).

The document is checked first. When it isn't a valid Swagger 2.0 document, the command lists the
problems with their lines and columns and writes nothing.

The conversion changes the structure of the document, not only its references:

- `body` and `formData` parameters become request bodies, with a media type per entry of
  `consumes` (file uploads as `multipart/form-data`). Global body parameters become
  `components.requestBodies`.
- Other parameters get a `schema` built from their keywords, and `collectionFormat` becomes `style`
  and `explode`.
- Response schemas, headers and examples move into `content`, with a media type per entry of
  `produces`.
- `host`, `basePath` and `schemes` become `servers`: one URL per scheme, `https` when no scheme is
  given, and a relative URL from `basePath` alone when there is no `host`.
- `definitions`, `parameters`, `responses` and `securityDefinitions` move into `components`, and
  references follow them (`#/definitions/Pet` becomes `#/components/schemas/Pet`).
- `basic` security schemes become `http` schemes with `scheme: basic`; the `flow`, URLs and scopes
  of `oauth2` schemes move into `flows` (`accessCode` becomes `authorizationCode`, `application`
  becomes `clientCredentials`).
- Schemas switch dialect: `type: file` becomes a binary string, discriminators become objects,
  boolean `exclusiveMinimum` and `exclusiveMaximum` become numeric bounds, and `x-nullable: true`
  adds `null` to the type.
- Specification extensions (`x-...`) and everything else without a counterpart to convert are
  copied as they are.

Some parts of a Swagger 2.0 document have no exact counterpart in OpenAPI 3.2 and are approximated
or dropped: a `collectionFormat` of `tsv` (or one other than `csv` in a path or a header, or other
than `multi` in a form field), `allowEmptyValue` of a form field, an operation's own `schemes`,
form parameters next to a body parameter, and `x-nullable` without a `type`. A reference to
another document is copied unchanged, so that document has to be converted too; bundle the
document first with [`apigenie bundle`](/docs/reference/cli/bundle) to convert it as a whole.
The `convert` command doesn't report these approximations yet;
[`apigenie-server`](/docs/reference/server#swagger-20-documents) and
[`apigenie test`](/docs/reference/cli/test) do (see below).

The output keeps the order of the input's keys. Comments of a YAML input are not kept.

### Swagger 2.0 in apigenie-server and apigenie test

`apigenie-server`, its validating proxy (`--proxy-to`), and `apigenie test --spec` take Swagger 2.0
documents directly: they convert them to OpenAPI 3.2 in memory, as `convert --to openapi32` does,
each time the document is read, and write no file. Locations stay those of the Swagger file: the
lines of warnings, the lines and JSON pointers of `x-apigenie-mock` problems, and the `pointer` of
each operation in the contract reports. What the conversion can only approximate is told once at
startup as `info:` lines with the Swagger file's line and JSON pointer (`{"event":"info",...}` with
`apigenie test --formatter json`). See
[Swagger 2.0 documents](/docs/reference/server#swagger-20-documents).

The other commands that need an OpenAPI document, such as the
[generators](/docs/reference/generator), read only OpenAPI 3.x: convert Swagger 2.0
documents first.

## JSON Schema input

The command reads a JSON Schema document written against draft 2019-09 or 2020-12 (JSON or YAML)
and writes the same schema in an older dialect: draft-07 (`--to draft7`), draft-04
(`--to draft4`), or the Schema Object of OpenAPI 3.0 (`--to openapi30`), to paste into
`components.schemas` of an OpenAPI 3.0 document. A draft-07 document goes through the same pass on
its way to draft-04: the rewrite is driven by the keywords the document uses, not by its
`$schema`. The document is not validated first.

### Composition: allOf and unevaluatedProperties

The older dialects have no `unevaluatedProperties`, and `additionalProperties: false` next to an
`allOf` would reject every property the members declare. So a schema that extends others with
`allOf` and closes itself with `unevaluatedProperties` becomes one flat object schema:

```yaml
# 2020-12
$defs:
  Base: {type: object, properties: {id: {type: string}}, required: [id]}
type: object
allOf:
  - $ref: '#/$defs/Base'
  - properties: {name: {type: string}}
unevaluatedProperties: false
```

```yaml
# draft-07
$schema: http://json-schema.org/draft-07/schema#
definitions:
  Base: {type: object, properties: {id: {type: string}}, required: [id]}
type: object
properties:
  id: {type: string}
  name: {type: string}
required: [id]
additionalProperties: false
```

The members are the `allOf` elements, the schemas that local `$ref` members point to (chains of
references and nested `allOf` followed, each schema merged once), and the schema itself (a `$ref`
with siblings counts as a member too). Their `properties`, `required`, `patternProperties`,
`propertyNames`, `minProperties`, `maxProperties` and `dependentRequired` are merged; a member's
`additionalProperties` schema becomes the result's, since it evaluates the extra properties;
otherwise `unevaluatedProperties` does. The members' titles and descriptions are not carried
over, and the definitions stay for the other schemas that reference them. A property two members
define differently is kept as an `allOf` of both definitions, and reported.

A member that can't be merged keeps the `allOf` as it is, drops `unevaluatedProperties` (the
result then accepts more than the source, which is the lesser loss) and says why on the standard
error: `additionalProperties: false` or `unevaluatedProperties` of its own, `anyOf`, `oneOf`,
`not`, `if`, `dependentSchemas`, `enum`, `const`, a `type` other than `object`, a
`discriminator`, a reference to another document or to an anchor, or a cycle.

### The other keywords

| Source keyword | draft-07 | draft-04 | OpenAPI 3.0 |
| --- | --- | --- | --- |
| `$schema` | the draft-07 URI | the draft-04 URI | dropped |
| `$defs` and `#/$defs/` references | `definitions` | `definitions` | `definitions`, reported: move them to `components.schemas` |
| `$id` | kept | `id` | dropped |
| `unevaluatedProperties` alone | `additionalProperties` | same | same |
| `unevaluatedItems` after a tuple | `additionalItems` | same | dropped |
| `prefixItems` + `items` | tuple `items` + `additionalItems` | same | `items` accepting any element schema, `maxItems` for a closed tuple |
| `dependentRequired`, `dependentSchemas` | `dependencies` | same | dropped |
| `$dynamicRef`, `$recursiveRef` | `$ref` | same | same |
| `const` | kept | `enum` | `enum` |
| numeric `exclusiveMinimum` / `exclusiveMaximum` | kept | `minimum` / `maximum` + boolean | same |
| `true` / `false` schemas | kept | `{}` / `{not: {}}` | same |
| `contains`, `propertyNames`, `if` / `then` / `else` | kept | dropped | dropped |
| `examples` | kept | dropped | the first as `example` |
| `contentEncoding: base64`, `contentMediaType` | kept | dropped | `format: byte` / `binary` |
| `type: [T, 'null']`, a `{type: 'null'}` alternative | kept | kept | `type: T`, `nullable: true` |
| `type` with several names | kept | kept | `anyOf` of single-type schemas |
| `patternProperties` | kept | kept | `^name$` patterns become `properties`, others dropped |
| `$ref` with siblings | kept | kept | wrapped in `allOf` |

`$anchor`, `$dynamicAnchor`, `$vocabulary`, `minContains`, `maxContains` and `contentSchema`
have no counterpart in any target. Everything dropped or approximated is reported on the
standard error, one line per keyword with its JSON pointer in the source, so that the document
on the standard output stays usable. Specification extensions (`x-...`), annotations and the
keywords the target has are copied as they are, in the order of the input.

## WSDL 1.1 input

The command reads a WSDL 1.1 document whose SOAP 1.1 or SOAP 1.2 bindings are document/literal
over HTTP, with the XML Schemas of its types, and writes an OpenAPI 3.1 document. The result lints
without errors with the `recommended` ruleset (see [Linting](#linting-the-result)). It serves in
[`apigenie-server`](/docs/reference/server), which validates SOAP requests and answers
SOAP envelopes. [`diff`](/docs/reference/cli/diff) and the
[generators](/docs/reference/generator) read it like any other OpenAPI document.

The WSDL documents it imports (`wsdl:import`) and the schemas (`xs:import` and `xs:include` with a
`schemaLocation`) are read from files, relative to the document that names them. Remote documents
(`http:` and `https:`) are not fetched: download them and point the `location` or
`schemaLocation` at the copies. A schema that isn't fetched is reported, and the elements, types
and attributes of its namespace then accept any value.

### Operations

Each operation of a SOAP binding becomes a `POST` operation:

- **Path:** the path of the port's `soap:address`, followed by the operation's name. The
  operations of a port share one address in SOAP, but an OpenAPI path has one `POST`, so each
  operation gets a path of its own. For `https://api.example.com/soap/greeter`, the operation
  `Greet` is `POST /soap/greeter/Greet`. The address's origin is the server. A binding that no
  port uses gets `/{binding}/{operation}` and no server.
- **Extensions:** `x-soap-address` is the port's address, and `x-soap-action` the operation's
  SOAPAction.
- **Tag and operationId:** the tag is the port type. The operationId is the operation's name,
  prefixed with the port type (or the port) when several operations have that name.
- **Description:** the operation's `wsdl:documentation`, or a sentence naming the operation when
  it has none.
- **SOAP 1.1 and 1.2:** a binding of each version for one port type at one address makes one
  operation, with a `text/xml` body (the SOAP 1.1 envelope) and an `application/soap+xml` body
  (the SOAP 1.2 envelope). `apigenie-server` answers in the media type of the request.

### Bodies and responses

Request and response bodies are SOAP envelopes. Their schemas carry the
[`xml` objects](/docs/reference/server#xml-bodies) that map the envelope's XML to the
schema and back:

- **Envelope:** the root is `soap:Envelope`. Its `Body` holds an element per body part of the
  message, and its `Header` the `soap:header` parts. The Header also accepts header blocks that
  the WSDL doesn't declare, such as WS-Addressing's.
- **Responses:** the output message is the `200` response. A one-way operation answers `202`
  without content.
- **Faults:** the `500` response is a SOAP Fault, with the faults of the operation in its
  `detail` (SOAP 1.1) or `Detail` (SOAP 1.2).

The global elements and types the messages use become schemas in `components`:

- **Elements and types:** an element and a type of the same name are `X` and `XType`.
- **Content models:** `xs:sequence` and `xs:all` become objects with `additionalProperties:
  false`. `minOccurs` gives `required`, and a repeated element becomes an array whose items repeat
  in the parent element. A required `xs:choice` of required elements becomes `oneOf` of its
  alternatives.
- **Attributes and text:** an attribute is a property with `xml: {attribute: true}`. The text of
  an element with simple content is a `value` property with `xml: {x-nodeType: text}`.
- **Facets and values:** `nillable` adds `null` to the type, `default` becomes `default`, and
  `fixed` becomes `const`. Enumerations, lengths, bounds and patterns become their JSON Schema
  keywords.
- **Built-in types:** each gets a type and a format; an `xs:int` is `{type: integer, format:
  int32}`, an `xs:dateTime` is `{type: string, format: date-time}`.
- **Namespaces:** every `xml` object with a namespace has a prefix, the one the WSDL declares when
  it is free. Elements are qualified as `elementFormDefault` (or `form`) says.
- **Descriptions:** `xs:documentation` becomes the description.

### Limits

What has no exact counterpart is approximated, and told on the standard error as a warning with
its file, line and column. Warnings with the same text are told once, followed by "(and N more
like this)".

- `xs:any`, `xs:anyAttribute` and `xs:anyType` accept any content: the object gets
  `additionalProperties: true` and `x-apigenie-lint-disable: [schema-no-additional-properties]`.
- Substitution groups aren't followed: only the head element is accepted where it is used.
  Abstract elements and types are accepted as they are declared.
- A type extending another one copies its content. An element of a derived type (`xsi:type`)
  isn't accepted in place of its base.
- Mixed content: the text between child elements isn't described.
- A list type becomes a string of space-separated values.
- A repeated `xs:sequence` or `xs:choice` becomes an array per element; the order across elements
  is lost.
- `xs:decimal` becomes a `double`, and integer types without bounds (`xs:integer`,
  `xs:nonNegativeInteger`...) an `int64`.
- `totalDigits` is dropped, and so is `fractionDigits` other than 0. Patterns using `\i`, `\c`,
  `\p{Is...}` or character class subtraction are dropped.
- `xs:redefine` and `xs:override` are read as `xs:include`.
- A message part with a `type` instead of an `element` becomes an element in no namespace.
- HTTP and MIME bindings are left out. So are operations in rpc style or with encoded use, next to
  document/literal ones.
- WS-Policy, WS-Addressing, WS-Security and other WS-* extensions aren't read, and neither are
  MIME attachments (MTOM, SwA).
- WSDL has no version: `info.version` is `1.0.0`.

These documents are refused, with exit status 2:

- documents that aren't well-formed XML, and WSDL 2.0 documents;
- documents with a missing local import, or with a reference to something that isn't defined;
- documents without a document/literal SOAP binding over HTTP: rpc/encoded and rpc/literal
  services, and SOAP over other transports. The message names the binding and its style and use.

The importer converts WSDL to OpenAPI only: it doesn't generate SOAP clients or servers, and it
doesn't write WSDL.

### Linting the result

The converted document has no errors with the `recommended` ruleset. Some warnings stay, because
they concern what WSDL doesn't describe or names WSDL sets:

- security and rate limits: `operation-security-defined` and `operation-rate-limiting-defined`;
- the length of strings: `schema-constraints`;
- a binding without an address: `servers-defined`;
- large services: `paths-max-count`;
- the address's case: `paths-well-formed`;
- date element names: `schema-datetime-suffix`.

Two more come from XML Schema constructs:

- `schema-discriminator-defined`: the alternatives of an `xs:choice` are told apart by their
  element, not by a property;
- `schema-no-optional-nullable-properties`: an optional nillable element can be absent or
  `xsi:nil`, which differ in XML.

## Options

### --from

The source: `swagger2` (Swagger 2.0), `wsdl` (WSDL 1.1) or `jsonschema` (a JSON Schema document).
Without it, an XML input is WSDL, a document with a `swagger` member Swagger 2.0, and any other
input a JSON Schema.

### --to

Required. The target: `openapi32` (OpenAPI 3.2) for Swagger 2.0, `openapi31` (OpenAPI 3.1) for
WSDL, and `draft7`, `draft4` or `openapi30` (the Schema Object of OpenAPI 3.0) for a JSON Schema.
Another pair exits with status 2.

### --format

Serialization of the output: `json` or `yaml`. Defaults to the format of the input, and to YAML for
WSDL.

### -o, --output

Path of the converted document. Without it, the document is written to the standard output.

## Exit status

| Status | Meaning |
| ------ | ------- |
| 0 | The document was converted |
| 2 | Invalid arguments (a missing `--to`, an unknown target or format, a target the source doesn't convert to), the input file can't be read, it isn't a valid Swagger 2.0 document, or the WSDL document can't be converted (the problems are listed on the standard error) |

## Examples

Convert a YAML document and print the result:

```sh
apigenie convert --to openapi32 petstore.yaml
```

Convert to a JSON file:

```sh
apigenie convert --to openapi32 --format json -o petstore.openapi.json petstore.yaml
```

Downgrade a 2020-12 schema to draft-07, keeping the notes apart from the result:

```sh
apigenie convert --to draft7 -o order.draft7.json order.schema.json 2> notes.txt
```

Convert a WSDL document, lint the result and serve it:

```sh
apigenie convert --to openapi31 -o greeter.yaml greeter.wsdl
apigenie lint greeter.yaml
apigenie-server greeter.yaml
```

For this Swagger 2.0 input:

```yaml
swagger: "2.0"
info: {title: Pets, version: "1.0"}
host: api.example.com
basePath: /v1
schemes: [https, http]
consumes: [application/json]
produces: [application/json]
securityDefinitions:
  basic: {type: basic}
paths:
  /pets:
    post:
      operationId: addPet
      parameters:
        - {name: body, in: body, required: true, schema: {$ref: '#/definitions/Pet'}}
      responses:
        "201": {description: Created, schema: {$ref: '#/definitions/Pet'}}
definitions:
  Pet:
    type: object
    properties:
      name: {type: string, x-nullable: true}
      photo: {type: file}
```

the command writes:

```yaml
openapi: 3.2.0
info:
  title: Pets
  version: "1.0"
servers:
- url: https://api.example.com/v1
- url: http://api.example.com/v1
paths:
  /pets:
    post:
      operationId: addPet
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
        required: true
components:
  schemas:
    Pet:
      type: object
      properties:
        name:
          type:
          - string
          - "null"
        photo:
          type: string
          format: binary
  securitySchemes:
    basic:
      type: http
      scheme: basic
```

For this WSDL input:

```xml
<definitions name="Greeter" targetNamespace="urn:example:greeter"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:g="urn:example:greeter">
  <types>
    <xs:schema targetNamespace="urn:example:greeter" elementFormDefault="qualified">
      <xs:element name="Greet">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="times" type="xs:int" minOccurs="0"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="GreetResponse">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="greeting" type="xs:string" maxOccurs="unbounded"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
  </types>
  <message name="GreetIn"><part name="parameters" element="g:Greet"/></message>
  <message name="GreetOut"><part name="parameters" element="g:GreetResponse"/></message>
  <portType name="Greeter">
    <operation name="Greet">
      <documentation>Greets someone.</documentation>
      <input message="g:GreetIn"/>
      <output message="g:GreetOut"/>
    </operation>
  </portType>
  <binding name="GreeterSoap" type="g:Greeter">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="Greet">
      <soap:operation soapAction="urn:example:greeter/Greet"/>
      <input><soap:body use="literal"/></input>
      <output><soap:body use="literal"/></output>
    </operation>
  </binding>
  <service name="GreeterService">
    <port name="GreeterPort" binding="g:GreeterSoap">
      <soap:address location="https://api.example.com/soap/greeter"/>
    </port>
  </service>
</definitions>
```

the command writes (the 500 response and the shared `Soap11Header` and `Soap11Fault` components
left out):

```yaml
openapi: 3.1.0
info:
  title: GreeterService
  summary: The SOAP service GreeterService, converted from WSDL
  description: The operations of the SOAP service GreeterService. Each operation takes and returns a SOAP envelope; its path is the address of the service followed by the name of the operation.
  version: 1.0.0
  x-wsdl-target-namespace: urn:example:greeter
servers:
- url: https://api.example.com
tags:
- name: Greeter
  description: The operations of the port type Greeter.
paths:
  /soap/greeter/Greet:
    post:
      tags:
      - Greeter
      description: Greets someone.
      operationId: Greet
      requestBody:
        required: true
        content:
          text/xml:
            schema:
              type: object
              properties:
                Header:
                  $ref: "#/components/schemas/Soap11Header"
                Body:
                  type: object
                  xml:
                    namespace: http://schemas.xmlsoap.org/soap/envelope/
                    prefix: soap
                  properties:
                    Greet:
                      $ref: "#/components/schemas/Greet"
                      xml:
                        name: Greet
                        namespace: urn:example:greeter
                        prefix: g
                  required:
                  - Greet
                  additionalProperties: false
              required:
              - Body
              additionalProperties: false
              xml:
                name: Envelope
                namespace: http://schemas.xmlsoap.org/soap/envelope/
                prefix: soap
      responses:
        "200":
          description: The response of Greet.
          content:
            text/xml:
              schema:
                type: object
                properties:
                  Header:
                    $ref: "#/components/schemas/Soap11Header"
                  Body:
                    type: object
                    xml:
                      namespace: http://schemas.xmlsoap.org/soap/envelope/
                      prefix: soap
                    properties:
                      GreetResponse:
                        $ref: "#/components/schemas/GreetResponse"
                        xml:
                          name: GreetResponse
                          namespace: urn:example:greeter
                          prefix: g
                    required:
                    - GreetResponse
                    additionalProperties: false
                required:
                - Body
                additionalProperties: false
                xml:
                  name: Envelope
                  namespace: http://schemas.xmlsoap.org/soap/envelope/
                  prefix: soap
      x-soap-action: urn:example:greeter/Greet
      x-soap-address: https://api.example.com/soap/greeter
components:
  schemas:
    Greet:
      type: object
      properties:
        name:
          type: string
          xml:
            namespace: urn:example:greeter
            prefix: g
        times:
          type: integer
          format: int32
          xml:
            namespace: urn:example:greeter
            prefix: g
      required:
      - name
      additionalProperties: false
      xml:
        name: Greet
        namespace: urn:example:greeter
        prefix: g
    GreetResponse:
      type: object
      properties:
        greeting:
          type: array
          items:
            type: string
            xml:
              namespace: urn:example:greeter
              prefix: g
          minItems: 1
      required:
      - greeting
      additionalProperties: false
      xml:
        name: GreetResponse
        namespace: urn:example:greeter
        prefix: g
```

`apigenie-server greeter.yaml` then answers this request with a `GreetResponse` envelope, and
a request without `name` with a 422 problem (`body/Body/Greet: required property 'name' is
missing`):

```sh
curl -X POST http://127.0.0.1:4010/soap/greeter/Greet -H 'Content-Type: text/xml' \
  --data '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>
    <g:Greet xmlns:g="urn:example:greeter"><g:name>Ada</g:name></g:Greet>
  </soap:Body></soap:Envelope>'
```
