Menu

OpenAPI (OAS) Annotations - Usage Guide

This guide explains how to add OpenAPI 3.0 (OAS) metadata to your Aussom-Server applications. Aussom-Server can render an openapi.yaml document for any hosted application, listing its endpoints, request and response shapes, and the schemas of the data classes it returns. The document is built by reading @Api-style annotations directly from your .aus source.

This guide is for developers who are new to Aussom-Server and want to publish a discoverable API definition for their app.


Building blocks

Before the annotation reference, here is the overall picture.

  1. An app is one Aussom class that extends AppBase. Each public method on that class becomes one HTTP path.
  2. The OAS info block comes from a class-level @Api(...) annotation plus the class doc comment.
  3. A path's HTTP verbs (GET, POST, PUT, etc.) are declared by stacking per-verb annotations on the route method: @ApiGetReq / @ApiGetResp, @ApiPostReq / @ApiPostResp, and so on.
  4. Schemas are regular Aussom classes referenced by name in those annotations. Aussom-Server auto-discovers any class you mention and emits it under components/schemas.
  5. Schema member metadata (type, required, ignored) is set with @Api, @ApiRequired, and @ApiIgnore on each member.

The renderer is AussomApiBuilder. It reads the AST of your app and walks those annotations. Anything it does not recognize is silently ignored.

Annotation argument values must be quoted strings. The Aussom annotation parser currently accepts only string-literal values for annotation arguments. Even values that look like a number or bool must be quoted: version = "1.0.0", required = "true", statusCode = "200". Bare literals (required = true, statusCode = 200) produce a parse error and the argument is dropped from the AST.


Enabling the /api endpoint

For an app to serve its OAS document at <appname>/api, the app must be configured with hostApiEndpoint: true in applications.yaml. For example:

applications:
- name: helloworld
  appDirectory: helloworld
  enabled: true
  hostApiEndpoint: true   # turns on /helloworld/api
  hostDocEndpoint: true   # optional: turns on /helloworld/doc
  hostResources: true
  publicHttpDirectory: public
  reloadOnFileChange: true
  logLevel: info

With that flag set, requesting GET /<appname>/api returns the OAS YAML.

The admin server always exposes /Admin/api and the document is built the same way from admininterface.aus.


Class-level: app info

The @Api annotation on the app class (and the class doc comment) populates the OAS info block. The class doc comment becomes description. The title is hardcoded to the app's class name.

/**
 * Hello-world app. Demonstrates the OAS annotations.
 */
@Api(
    version = "1.0.0",
    tos = "https://example.com/tos",
    contactName = "Author Name",
    contactUrl = "https://example.com",
    contactEmail = "author@example.com",
    licenseName = "MIT",
    licenseIdentifier = "MIT",
    licenseUrl = "https://opensource.org/license/mit/"
)
class helloworld : AppBase {
    /* ... */
}

Recognized arguments:

Argument Maps to
version info.version
tos info.termsOfService
contactName info.contact.name
contactUrl info.contact.url
contactEmail info.contact.email
licenseName info.license.name
licenseIdentifier info.license.identifier
licenseUrl info.license.url

Arguments not in this list (such as title, summary, description) are silently ignored on the class-level @Api. Use the class doc comment for the description and rely on the app name for the title.


Class-level: servers

Each @ApiServer annotation adds one entry to the OAS servers block. You can stack as many as you need.

@Api(version = "1.0.0")
@ApiServer(
    url = "https://api.example.com",
    desc = "Production server."
)
@ApiServer(
    url = "https://staging.example.com",
    desc = "Staging server.",
    var = "{ basePath: { default: 'v1' } }"
)
class helloworld : AppBase { /* ... */ }

Recognized arguments:

Argument Maps to
url servers[].url
desc servers[].description
var servers[].variables

Method-level: paths and operations

Each public method on the app class becomes one path under paths. The path is /<methodName>, except for index which becomes /.

The path's description is the method's doc comment. Per-verb operations (get:, post:, etc.) are declared with stacked annotations. For each verb you can supply at most one request annotation and one response annotation per status code.

/**
 * Get one item by id, or create a new one.
 */
@ApiGetReq(
    queryParam = "string",
    queryParamName = "id",
    queryParamDesc = "Item id to fetch."
)
@ApiGetResp(
    statusCode = "200",
    desc = "Item found.",
    content = "application/json:Item"
)
@ApiGetResp(
    statusCode = "404",
    desc = "Item not found.",
    content = "application/json:ApiError"
)
@ApiPostReq(
    body = "application/json:Item",
    required = "true",
    desc = "New item payload."
)
@ApiPostResp(
    statusCode = "201",
    desc = "Item created.",
    content = "application/json:Item"
)
public item(req) {
    /* ... */
}

Request annotations

One per verb. The verb name is part of the annotation name.

  • @ApiGetReq
  • @ApiPostReq
  • @ApiPutReq
  • @ApiPatchReq
  • @ApiDeleteReq
  • @ApiOptionsReq
  • @ApiHeadReq
  • @ApiTraceReq

Recognized arguments:

Argument Purpose
queryParams Name of a parameter class (or Class:f1,f2 to pick fields). The class is rendered under components/parameters and referenced from the operation.
queryParam Type of a single inline query param. One of string, int, double, bool.
queryParamName Name of the inline query param.
queryParamDesc Description of the inline query param.
body Request body. Format: <contentType>:<ClassName>. See "Content type syntax" below.
required "true" to mark the body required. String, not bool.
desc Request body description.

Use queryParams (a class ref) when you have many query params and want them documented as one parameter component. Use queryParam / queryParamName / queryParamDesc for a single inline param.

Response annotations

One per verb, per status code. Stack one per response code you document.

  • @ApiGetResp, @ApiPostResp, @ApiPutResp, @ApiPatchResp, @ApiDeleteResp, @ApiOptionsResp, @ApiHeadResp, @ApiTraceResp

Recognized arguments:

Argument Purpose
statusCode HTTP status ("200", "404", etc.). String.
desc Response description shown under the status code.
content Response content. Format: <contentType>:<ClassName>. See below.

Content type syntax

The content = argument on response annotations and body = on request annotations both use the same mini-syntax:

Form Meaning
application/json:Item Single object, schema is Item.
application/json[]:Item Array of Item. Note the [] is on the content type, not the class.
application/json:A,B One value that is either schema A or B (rendered under oneOf).
`application/json:A text/xml:B`

Pitfall: application/json:Item[] does not work. The [] must be on the content type (application/json[]:Item).


Schemas: data classes

Any plain Aussom class referenced from a body = or content = argument is auto-discovered, walked, and emitted under components/schemas. The class doc comment becomes the schema description; each member's doc comment becomes the property description.

/**
 * One item in the catalog.
 */
class Item {
    /**
     * Item id.
     */
    @Api(type = "string")
    @ApiRequired
    public id = "";

    /**
     * Display name.
     */
    @Api(type = "string")
    @ApiRequired
    public name = "";

    /**
     * Optional notes.
     */
    @Api(type = "string")
    public notes = "";

    /**
     * Inner object reference. Pulls Detail into components/schemas
     * automatically because of value="Detail".
     */
    @Api(type = "object", value = "Detail")
    public detail = null;

    /**
     * Internal field hidden from the API doc.
     */
    @ApiIgnore
    public auditId = "";
}

Member annotations

Annotation What it does
@Api(type = ?) Override the field's OAS type. See "Recognized type names" below.
@Api(value = ?) Reference one or more class names so they get pulled into components/schemas. Pipe-separated for multi (`Class1
@ApiRequired Mark this field as required in the schema's required: list.
@ApiIgnore Skip this field. It will not appear in the schema.

The schema's required: list is built only from members that carry @ApiRequired. The @Api annotation does not have a recognized required argument; using required = true produces a parser error and is dropped. Always use the separate @ApiRequired annotation instead.

Recognized type names

The string passed to @Api(type = "...") is mapped through this table:

type = OAS type:
"string" string
"int" integer
"double" number
"bool" boolean
"list" array
"map" object
"object" / "obj" object
anything else null (broken)

Pitfall: writing type = "boolean" or type = "integer" does not map to the matching OAS type. Those names are not in the table and the field will render as type: null. Use the Aussom names (bool, int) from the table.

Pulling nested schemas in

When a field is itself a typed object, set both type and value so the referenced class is auto-included:

/**
 * Snapshot of the KPI counters.
 */
@Api(type = "object", value = "AdminKpi")
public kpi = null;

Without value, the property renders as type: object but the nested class is not added to components/schemas.


Parameter classes (queryParams)

When a route has many query parameters, define a parameter class and reference it with queryParams = "ClassName":

/**
 * Filters for /search.
 */
class searchFilters {
    /**
     * Free-text query.
     */
    @ApiType(type = "string")
    @ApiRequired
    public q = "";

    /**
     * Page size.
     */
    @ApiType(type = "int")
    public pageSize = 25;
}

/**
 * Search the catalog.
 */
@ApiGetReq(queryParams = "searchFilters")
@ApiGetResp(statusCode = "200", content = "application/json[]:Item")
public search(req) { /* ... */ }

A few notes on parameter classes:

  • Use @ApiType(type = ?, value = ?) on parameter-class members, not @Api(type = ...). Schema classes use @Api; parameter classes use @ApiType. They are recognized in different code paths.
  • @ApiRequired and @ApiIgnore work the same way on parameter classes as they do on schema classes.
  • queryParams = "ClassName:f1,f2" lets you reuse a parameter class but pick only the listed fields for this particular endpoint.
  • Each parameter is rendered under components/parameters with an in: query location.

End-to-end example

Putting it all together. This is a compact, self-contained app that exercises every annotation family.

/**
 * Toy catalog app.
 */
@Api(
    version = "1.0.0",
    contactName = "Sample Author",
    contactUrl = "https://example.com",
    contactEmail = "sample@example.com",
    licenseName = "MIT",
    licenseUrl = "https://opensource.org/licenses/MIT"
)
@ApiServer(url = "https://api.example.com", desc = "Production")
class catalog : AppBase {
    /**
     * GET /catalog/items?limit=N - List items.
     */
    @ApiGetReq(
        queryParam = "int",
        queryParamName = "limit",
        queryParamDesc = "Maximum items to return."
    )
    @ApiGetResp(
        statusCode = "200",
        desc = "List of items.",
        content = "application/json[]:Item"
    )
    public items(req) {
        /* ... */
    }

    /**
     * GET  /catalog/item?id=X - Get one item.
     * POST /catalog/item       - Create one item.
     */
    @ApiGetReq(
        queryParam = "string",
        queryParamName = "id",
        queryParamDesc = "Item id."
    )
    @ApiGetResp(
        statusCode = "200",
        desc = "Item found.",
        content = "application/json:Item"
    )
    @ApiGetResp(
        statusCode = "404",
        desc = "Item not found.",
        content = "application/json:ApiError"
    )
    @ApiPostReq(
        body = "application/json:Item",
        required = "true",
        desc = "New item."
    )
    @ApiPostResp(
        statusCode = "201",
        desc = "Item created.",
        content = "application/json:Item"
    )
    public item(req) {
        /* ... */
    }
}

/**
 * One item in the catalog.
 */
class Item {
    /**
     * Item id.
     */
    @Api(type = "string")
    @ApiRequired
    public id = "";

    /**
     * Display name.
     */
    @Api(type = "string")
    @ApiRequired
    public name = "";

    /**
     * Price in cents.
     */
    @Api(type = "int")
    public priceCents = 0;
}

/**
 * Standard error envelope.
 */
class ApiError {
    /**
     * HTTP status code.
     */
    @Api(type = "int")
    @ApiRequired
    public code = 0;

    /**
     * Human-readable message.
     */
    @Api(type = "string")
    @ApiRequired
    public message = "";
}

Common pitfalls

  • required = true on a member is not a thing. Use the separate @ApiRequired annotation. The @Api annotation only reads type and value. (And remember the rule from "Building blocks": annotation argument values must be quoted strings, so even a hypothetical required = "true" would still be ignored here.)
  • Type names are Aussom names, not OAS names. Use bool, int, double, list, map, string, object. Writing boolean or integer produces type: null.
  • Array content syntax. Use application/json[]:ClassName, not application/json:ClassName[]. The [] belongs on the content type.
  • Schemas only appear if referenced. A schema class that no endpoint mentions in body = / content = / value = will not show up under components/schemas even if it exists in source.
  • Parameter classes use @ApiType, schema classes use @Api. These are read in different code paths; using the wrong one means the metadata is dropped silently.
  • Method-level @Api(summary = ..., description = ...) is ignored. Use the method doc comment for description, and per-verb desc = on the response/request annotations to document the operation.

Annotation reference

This section lists every annotation the OAS builder reads, grouped by where it can be placed.

On the app class

Annotation Purpose
@Api(...) OAS info block.
@ApiServer(...) One entry under servers. Stackable.

On a route method

Annotation Purpose
@ApiGetReq(...) GET request: query params or body.
@ApiPostReq(...) POST request.
@ApiPutReq(...) PUT request.
@ApiPatchReq(...) PATCH request.
@ApiDeleteReq(...) DELETE request.
@ApiOptionsReq(...) OPTIONS request.
@ApiHeadReq(...) HEAD request.
@ApiTraceReq(...) TRACE request.
@ApiGetResp(...) (one per status code) GET response.
@ApiPostResp(...) POST response.
@ApiPutResp(...) PUT response.
@ApiPatchResp(...) PATCH response.
@ApiDeleteResp(...) DELETE response.
@ApiOptionsResp(...) OPTIONS response.
@ApiHeadResp(...) HEAD response.
@ApiTraceResp(...) TRACE response.

On a schema-class member

Annotation Purpose
@Api(type = ?, value = ?) Override OAS type, reference inner class.
@ApiRequired Mark the field required.
@ApiIgnore Exclude the field from the schema.

On a parameter-class member

Annotation Purpose
@ApiType(type = ?, value = ?) Override OAS type, reference inner class.
@ApiRequired Mark the param required.
@ApiIgnore Exclude the param.

Verifying your OAS

Once your annotations are in place:

  1. Set hostApiEndpoint: true in applications.yaml for the app, or make sure the admin server is running for the Admin app.

  2. Restart the server.

  3. Fetch the document:

    curl http://<host>:<port>/<appname>/api
    
  4. Check the admin / app log file under logs/ for any AussomApiBuilder error lines such as Attempting to build class '<X>' but no doc found for this class. Those are emitted at error level when a referenced schema class has no doc comment, or is missing entirely.

For a richer view, paste the YAML into editor.swagger.io - it will render the operations and schemas, and surface any malformed references.