apigenieconvert

convert

Convert a Swagger 2.0 document to OpenAPI 3.2, or a WSDL 1.1 document to OpenAPI 3.1.

(experimental, outside the 1.0 compatibility promise) convert has two sources, Swagger 2.0 and WSDL 1.1, 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.

Usage

apigenie convert --to openapi32 [...OPTIONS] SWAGGER_FILE
apigenie convert --to openapi31 [...OPTIONS] WSDL_FILE

Description

The command reads a Swagger 2.0 document or a WSDL 1.1 document and writes the equivalent OpenAPI document, to the standard output or to the file given with --output. Each source has one target: a Swagger document becomes OpenAPI 3.2 (--to openapi32), a WSDL document OpenAPI 3.1 (--to openapi31). The source is the one --from names; without --from, an input that starts with < (an XML document) is WSDL, and any other input Swagger.

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 to convert it as a whole. The convert command doesn’t report these approximations yet; apigenie-server and apigenie 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.

The other commands that need an OpenAPI document, such as the generators, read only OpenAPI 3.x: convert Swagger 2.0 documents first.

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). It serves in apigenie-server, which validates SOAP requests and answers SOAP envelopes. diff and the generators 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 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 specification: swagger2 (Swagger 2.0) or wsdl (WSDL 1.1). Without it, an XML input is WSDL and any other input Swagger 2.0.

–to

Required. The target specification and version: openapi32 (OpenAPI 3.2) for Swagger 2.0, and openapi31 (OpenAPI 3.1) for WSDL. 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:

apigenie convert --to openapi32 petstore.yaml

Convert to a JSON file:

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

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

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

For this Swagger 2.0 input:

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:

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:

<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):

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):

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>'