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.
Before the annotation reference, here is the overall picture.
AppBase. Each public
method on that class becomes one HTTP path.@Api(...) annotation
plus the class doc comment.@ApiGetReq / @ApiGetResp, @ApiPostReq / @ApiPostResp, and so on.components/schemas.@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.
/api endpointFor 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.
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.
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 |
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) {
/* ... */
}
One per verb. The verb name is part of the annotation name.
@ApiGetReq@ApiPostReq@ApiPutReq@ApiPatchReq@ApiDeleteReq@ApiOptionsReq@ApiHeadReq@ApiTraceReqRecognized 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. UsequeryParam/queryParamName/queryParamDescfor a single inline param.
One per verb, per status code. Stack one per response code you document.
@ApiGetResp, @ApiPostResp, @ApiPutResp, @ApiPatchResp,
@ApiDeleteResp, @ApiOptionsResp, @ApiHeadResp, @ApiTraceRespRecognized 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. |
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).
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 = "";
}
| 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@Apiannotation does not have a recognizedrequiredargument; usingrequired = trueproduces a parser error and is dropped. Always use the separate@ApiRequiredannotation instead.
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"ortype = "integer"does not map to the matching OAS type. Those names are not in the table and the field will render astype: null. Use the Aussom names (bool,int) from the table.
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.
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:
@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.components/parameters with an in: query location.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 = "";
}
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.)bool, int,
double, list, map, string, object. Writing boolean or
integer produces type: null.application/json[]:ClassName, not
application/json:ClassName[]. The [] belongs on the content type.body = / content = / value = will not show
up under components/schemas even if it exists in source.@ApiType, schema classes use @Api. These
are read in different code paths; using the wrong one means the
metadata is dropped silently.@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.This section lists every annotation the OAS builder reads, grouped by where it can be placed.
| Annotation | Purpose |
|---|---|
@Api(...) |
OAS info block. |
@ApiServer(...) |
One entry under servers. Stackable. |
| 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. |
| Annotation | Purpose |
|---|---|
@Api(type = ?, value = ?) |
Override OAS type, reference inner class. |
@ApiRequired |
Mark the field required. |
@ApiIgnore |
Exclude the field from the schema. |
| Annotation | Purpose |
|---|---|
@ApiType(type = ?, value = ?) |
Override OAS type, reference inner class. |
@ApiRequired |
Mark the param required. |
@ApiIgnore |
Exclude the param. |
Once your annotations are in place:
Set hostApiEndpoint: true in applications.yaml for the app, or
make sure the admin server is running for the Admin app.
Restart the server.
Fetch the document:
curl http://<host>:<port>/<appname>/api
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.