Interface OpenapiSupport
OpenAPI Specification (OAS) Support
If you allow access to your server-side DataSources via theRESTHandler servlet
, the Smart GWT server can
also generate
a standard OpenAPI
specification
of the REST interface supported by RestHandler
. This allows any client system
that supports OpenAPI to access the operations supported by your DataSources. Because details
like field types
and validators
) are automatically
translated to OpenAPI, the OpenAPI specification (OAS) of your DataSource operations is much
more specific and detailed than the general RestDataSource
protocol spec, and
can allow automatically generated UIs or automatically generated communication stubs to be
much richer and easier to use.
Very often, a reasonably simple DataSource expresses more than can be easily translated to
the current OpenAPI specification. In such cases, efforts are made to use the OpenAPI
extensions
mechanism to provide that level of detail. Validators are one common area of interest.
It is worthwhile to inspect the raw YAML output at least once before relying solely on
visual tooling, unless said tooling
supports vendor extensions. ReDoc,
for example, is able to render the generated spec well, and in fact powers the example
specification (see link below), but leaves out important details found in the extensions,
like the RESTHandler's JSON prefix
and
suffix
.
Tooling
The generated specification makes extensive use of remote references to make the spec more readable without tooling. Unfortunately, a number of popular tools do not yet support the use of this OAS feature. Postman, for example, has at least one issue that you can watch for progress. In the meantime, we've found that the hosted Swagger tools seem to work well, if you make allowances for Cross-Origin Resource Sharing. One very simple way of doing so with Swagger Editor, for example, is to enable a CORS filter (perhaps temporarily) like the one provided by Jetty:<filter> <filter-name>cross-origin</filter-name> <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class> </filter> <filter-mapping> <filter-name>cross-origin</filter-name> <url-pattern>/restapi/*</url-pattern> </filter-mapping>allowing the Swagger Editor to access the YAML generated for you by Smart GWT, through the use of its
url
query parameter. e.g.,
http://editor.swagger.io/?url=http://localhost:8080/api/Customer.yaml
Usage
Configure the RESTHandler endpoint as usual (refer to server javadoc for details), and submit a GET request there for theopenapi.yaml
resource.
For example, if your servlet is configured to respond to requests at /restapi
<servlet-mapping> <servlet-name>RESTHandler</servlet-name> <url-pattern>/restapi/</url-pattern> </servlet-mapping>then a GET request to
/restapi/openapi.yaml
will yield the automatically
generated documentation, based on your application's DataSource (ds.xml) configurations.
This specification is generated dynamically, so that each new request for the specification
includes any changes made to new or updated DataSources since the last request.
Alternatively, save the file to disk, with modifications if desired, and serve it statically
from another location if that's more in line with your requirement.
Structure
By design,openapi.yaml
by default includes a reference to every path exposed
by every DataSource.ds.xml file found in your project. The paths exposed by your DataSource
are determined by the rules documented in the
RESTHandler servlet's
SimplifiedREST protocol.
For an Order
DataSource with only default operationBindings, these references would look something like
the following:
paths: /: post: summary: DataSource-agnostic POSTMessage protocol # and so on... remainder clipped for brevity /RESTDataSource/Order/fetch: $ref: Order.yaml#/paths/~1RESTDataSource~1Order~1fetch /RESTDataSource/Order/add: $ref: Order.yaml#/paths/~1RESTDataSource~1Order~1add /RESTDataSource/Order/update: $ref: Order.yaml#/paths/~1RESTDataSource~1Order~1update /RESTDataSource/Order/remove: $ref: Order.yaml#/paths/~1RESTDataSource~1Order~1remove /RESTDataSource/Order/batch: $ref: Order.yaml#/paths/~1RESTDataSource~1Order~1batch /Order: $ref: Order.yaml#/paths/~1Order /Order/{orderId}: $ref: Order.yaml#/paths/~1Order~1%7BorderId%7DThe first path in the above listing documents RESTHandler's singular AdvancedREST endpoint, described by the
RESTDataSource's
postMessage
protocol and found in
our example configuration at /restapi/
. As documented elsewhere, this should
normally be the endpoint preferred by all but the simplest of integrations.
On the other hand, it is also the endpoint most difficult to document effectively,
due in part to a handful of restrictions found in the OAS 3.x specification. One such
restriction is documented in an open issue around
request/response correlation,
which in short points out that there is no good way to correlate a variation of some request
to the matching variation on the response. In the case of the AdvancedREST endpoint,
of course, request and response formats both depend entirely on the values provided in
dataSource
, operationType
, and operationId
arguments.
Ideally, we could document the inputs and outputs of a resource like
/restapi/?dataSource=Order&operationType=fetch&operationId=fetchByCustomer
separately from one like
/restapi/?dataSource=Order&operationType=fetch&operationId=fetchByUser
,
but this is expressly
disallowed.
In the absence of any spec-compliant mechanism like
supporting interdependencies between query parameters
then, we also provide simplified variations of the AdvancedREST endpoint on each
DataSource's specification, seen alongside the other SimplifiedREST endpoints with the
RESTDataSource
path. These 'SimplifiedPOST' endpoints do allow for Criteria
& AdvancedCriteria, as well as batching of multiple operations against the same
DataSource.
You can easily view all of the operations for a single DataSource by making the request to
{id}.yaml
instead of openapi.yaml, where {id}
is the ID of any
DataSource in your project. Building on the examples above, a request for
/restapi/Order.yaml
would include every path except '/'. Again, prefer
the RestHandler's AdvancedREST endpoint to SimplifiedREST, with the caveat that
SimplifiedREST documentation my be more explicit until a future version of the OAS spec
addresses some of the issues discussed here.
A full example specification is too lengthy to include in documentation, so it is left to
the reader to experiment with their own DataSources (sample DataSources are included with
the SDK and in Maven archetypes
or with the
example specification
bundled in the SDK (link below), using this documentation as guidance. Most of what is
generated for you can be pretty easily traced back to your DataSource -
description
, field names,
required/optional attributes, and
type mappings are all pretty straightforward, and the rest of it really should work the
way you might expect it to. A few specific examples include:
- A field's valueMaps are expressed as an enum and appended to the
description
, complete withi18n translations
as applicable, when a locale can be derived from the servlet request or is specified explicitly with a 'locale' query parameter (e.g., ?locale=es). -
A note about authorization constraints is also appended, when any
Declarative Security
rules are found (rules themselves are not disclosed, by design). - Any operationBinding with an explicit operationId is exposed on its own
path, where its binding-specific
criteria
,description
,outputs
, etc. are respected.
DataSource
<DataSource serverType="sql" schema="PUBLIC" dbName="ClassicModels" ID="Order" tableName="orders"> <serverObject className="com.example.classicmodels.OrderOperations" /> <fields> <field name="orderNumber" type="sequence" primaryKey="true" /> <field name="orderDate" type="date" required="true" /> <field name="requiredDate" type="date" required="true" /> <field name="shippedDate" type="date" /> <field name="status" type="enum" length="15" required="true"> <valueMap> <value>In Process</value> <value>Shipped</value> <value>Cancelled</value> <value>Disputed</value> <value>Resolved</value> <value>On Hold</value> </valueMap> </field> <field name="comments" type="text" length="16777216" /> <field name="customerNumber" title="Customer" type="integer" required="true" foreignKey="Customer.customerNumber" displayField="customerName" /> <field name="customerName" includeFrom="Customer.customerName" hidden="true" /> </fields> <operationBindings> <binding operationType="add" operationId="addWithDiscountCode" serverMethod="addWithDiscountCode" /> <binding operationType="add" operationId="addWithManualAdjustment" serverMethod="addWithManualAdjustment" /> </operationBindings> </DataSource>
Paths
/Order: $ref: Order.yaml#/paths/~1Order /Order/{orderId}: $ref: Order.yaml#/paths/~1Order~1%7BorderId%7D /Order/add/addWithDiscountCode: $ref: Order.yaml#/paths/~1Order~1add~1addWithDiscountCode /Order/add/addWithManualAdjustment: $ref: Order.yaml#/paths/~1Order~1add~1addWithManualAdjustment
Customization
A complete specification will normally require at least some content that cannot be derived, so most users will at minimum want to replace default values for things like application title, description, and version number attributes.
The simplest means for this kind of minimal customization is through
server.properties
configuration. Example
values for supported properties include:
openapi.info.version: 1.0.0 openapi.info.title: New Application openapi.info.description: A short description of New Application ## default value derived from servlet context & httpRequest openapi.servers.servletUrl: http://localhost:8080/restapiYou may also use configuration to control which DataSources are exposed to your specification. Three strategies are supported:
- Exclusion: Set an 'apidoc' attribute value to false on any DataSource definition to
exclude it from the list of documented DataSource operations.
<DataSource ID="Order" tableName="orders" apidoc="false">
- Blacklisting: Exclude a (comma-separated) list of DataSources through server.properties
configuration:
openapi.ds.blacklisted: Order, OrderDetail
- Whitelisting: Exclude everything but a (comma-separated) list of
DataSources, also through server.properties configuration:
openapi.ds.whitelisted: Order, OrderDetail
openapi.ds.dynamics: DYN_Environment, DYN_Status
request.setAttribute("openapi.ds.dynamics", "DynamicOrder$Foo_0123, DynamicOrderItem$Foo_0123,");Finally, a DataSource is automatically excluded from the specification under the following circumstances:
- It is marked with serverOnly="true"
- It is marked with requires="false"
- It is marked with its type="component"
- It has no fields (and inherits no fields)
- It is configured for exclusion with an 'apidoc' attribute value of false.
<binding apidoc="false" operationType="add" operationId="addWithDiscountCode" serverMethod="addWithDiscountCode" />
- The operationType is 'update' or 'remove' and (neither of these are common scenarios):
- No primaryKeys have been defined AND the binding is not configured to
allowMultiUpdate
- OR there are no non-key fields at all
- No primaryKeys have been defined AND the binding is not configured to
com.isomorphic.openapi
package of the isomorphic-core-rpc module. Any of these
templates may be overridden by placing a copy in a location known to the RESTHandler servlet
(again, refer to server javadoc), but this kind of thing should normally be considered the
last course of action.