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)
converthas two sources, Swagger 2.0 and WSDL 1.1, and its options and output can still change. It says so in its--helpand 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:
bodyandformDataparameters become request bodies, with a media type per entry ofconsumes(file uploads asmultipart/form-data). Global body parameters becomecomponents.requestBodies.- Other parameters get a
schemabuilt from their keywords, andcollectionFormatbecomesstyleandexplode. - Response schemas, headers and examples move into
content, with a media type per entry ofproduces. host,basePathandschemesbecomeservers: one URL per scheme,httpswhen no scheme is given, and a relative URL frombasePathalone when there is nohost.definitions,parameters,responsesandsecurityDefinitionsmove intocomponents, and references follow them (#/definitions/Petbecomes#/components/schemas/Pet).basicsecurity schemes becomehttpschemes withscheme: basic; theflow, URLs and scopes ofoauth2schemes move intoflows(accessCodebecomesauthorizationCode,applicationbecomesclientCredentials).- Schemas switch dialect:
type: filebecomes a binary string, discriminators become objects, booleanexclusiveMinimumandexclusiveMaximumbecome numeric bounds, andx-nullable: trueaddsnullto 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 onePOST, so each operation gets a path of its own. Forhttps://api.example.com/soap/greeter, the operationGreetisPOST /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-addressis the port’s address, andx-soap-actionthe 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/xmlbody (the SOAP 1.1 envelope) and anapplication/soap+xmlbody (the SOAP 1.2 envelope).apigenie-serveranswers 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. ItsBodyholds an element per body part of the message, and itsHeaderthesoap:headerparts. The Header also accepts header blocks that the WSDL doesn’t declare, such as WS-Addressing’s. - Responses: the output message is the
200response. A one-way operation answers202without content. - Faults: the
500response is a SOAP Fault, with the faults of the operation in itsdetail(SOAP 1.1) orDetail(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
XandXType. - Content models:
xs:sequenceandxs:allbecome objects withadditionalProperties: false.minOccursgivesrequired, and a repeated element becomes an array whose items repeat in the parent element. A requiredxs:choiceof required elements becomesoneOfof its alternatives. - Attributes and text: an attribute is a property with
xml: {attribute: true}. The text of an element with simple content is avalueproperty withxml: {x-nodeType: text}. - Facets and values:
nillableaddsnullto the type,defaultbecomesdefault, andfixedbecomesconst. Enumerations, lengths, bounds and patterns become their JSON Schema keywords. - Built-in types: each gets a type and a format; an
xs:intis{type: integer, format: int32}, anxs:dateTimeis{type: string, format: date-time}. - Namespaces: every
xmlobject with a namespace has a prefix, the one the WSDL declares when it is free. Elements are qualified aselementFormDefault(orform) says. - Descriptions:
xs:documentationbecomes 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:anyAttributeandxs:anyTypeaccept any content: the object getsadditionalProperties: trueandx-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:sequenceorxs:choicebecomes an array per element; the order across elements is lost. xs:decimalbecomes adouble, and integer types without bounds (xs:integer,xs:nonNegativeInteger…) anint64.totalDigitsis dropped, and so isfractionDigitsother than 0. Patterns using\i,\c,\p{Is...}or character class subtraction are dropped.xs:redefineandxs:overrideare read asxs:include.- A message part with a
typeinstead of anelementbecomes 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.versionis1.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-definedandoperation-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 anxs:choiceare told apart by their element, not by a property;schema-no-optional-nullable-properties: an optional nillable element can be absent orxsi: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>'