-
Notifications
You must be signed in to change notification settings - Fork 38
Spot Syntax
- @api
- @securityHeader
- @config
- @endpoint
- @request
- @headers
- @pathParams
- @queryParams
- @body
- @response
- @defaultResponse
- Types
- Supported HTTP Methods
- Documentation
- OpenAPI
- Servers: @oa3sesrver & @oa3serverVariables
Define an API. This is required and must only be defined once:
import { api } from "@airtasker/spot";
@api({ name: "My API" })
class MyAPI {}
Field | Description |
---|---|
name |
(required) Name of the API. Name of the providing service. The name must be unique to avoid clashes when working with multiple Spot contracts across different services. |
Define a global security header. This should be used within an @api
class:
import { api, securityHeader, String } from "@airtasker/spot";
@api({ name: "My API" })
class MyAPI {
@securityHeader
"security-header": String;
}
Security headers can only be string types.
Global configuration used for the API. This should be only be defined once on the @api
class:
import { api, config } from "@airtasker/spot";
/** My really cool API */
@api({ name: "My API" })
@config({
paramSerializationStrategy: {
query: {
array: "comma"
}
}
})
class MyAPI {}
Field | Description |
---|---|
paramSerializationStrategy.query.array |
Serialization strategy for query parameter array values. This can be ampersand (default) or comma
|
@endpoint({ ... })
class AnEndpoint {
@request
request(
@queryParams
queryParams: {
id: Int32[]
}
) {}
}
For id = [3,4,5]
:
-
ampersand (default):
?id=3&id=4&id=5
-
comma:
?id=3,4,5
Define a HTTP endpoint for the API. An endpoint describes a particular HTTP action on a URL path:
import {
body,
defaultResponse,
endpoint,
headers,
pathParams,
queryParams,
request,
response,
String
} from "@airtasker/spot";
/** Retrieve all users */
@endpoint({
method: "GET",
path: "/users",
tags: ["Users"]
})
class GetUsers {
@request
request(
@queryParams
queryParams: {
search?: String;
}
) {}
@response({ status: 200 })
response(@body body: UserListResponse) {}
}
/** Retrieve a user by their unique identifier */
@endpoint({
method: "GET",
path: "/users/:id",
tags: ["Users"]
})
class GetUser {
@request
request(
@pathParams
pathParams: {
/** Unique user identifier */
id: String;
}
) {}
@response({ status: 200 })
successResponse(@body body: UserResponse) {}
@response({ status: 404 })
notfoundResponse(@body body: ApiErrorResponse) {}
}
/** Create a user */
@endpoint({
method: "POST",
path: "/users",
tags: ["Users"]
})
class CreateUser {
@request
request(
@headers
headers: {
/** The authorization token */
Authorization: String;
},
@body body: CreateUserRequest
) {}
@response({ status: 201 })
successResponse(
@headers
headers: {
Location: String;
},
@body body: UserResponse
) {}
@response({ status: 401 })
unauthorizedResponse(@body body: ApiErrorResponse) {}
@defaultResponse
defaultResponse(@body body: ApiErrorResponse) {}
}
interface User {
firstName: String;
lastName: String;
}
type UserResponse = User;
type UserListResponse = User[];
interface CreateUserRequest {
firstName: String;
lastName: String;
}
interface ApiErrorResponse {
message: String;
}
Field | Description |
---|---|
method |
(required) HTTP method |
path |
(required) URL path |
tags |
List of tags used for endpoint grouping in documentation |
Define a request for endpoints that require headers, path params, query params or request bodies:
import {
body,
endpoint,
headers,
pathParams,
queryParams,
request,
String
} from "@airtasker/spot";
@endpoint({
method: "POST",
path: "/company/:companyId/users"
})
class CreateUser {
@request
request(
@headers
headers: {
Authorization: String;
},
@pathParams
pathParams: {
companyId: String;
},
@body body: CreateUserRequest
) {}
//...
}
@endpoint({
method: "GET",
path: "/users"
})
class GetUsers {
@request
request(
@queryParams
queryParams: {
search?: String;
}
) {}
//...
}
interface CreateUserRequest {
firstName: String;
lastName: String;
}
Define request or response headers:
import {
endpoint,
headers,
Int32,
request,
response,
String
} from "@airtasker/spot";
@endpoint({
method: "GET",
path: "/users"
})
class GetUsers {
@request
request(
@headers
headers: {
Authorization: String;
"Accept-Encoding": String;
}
) {}
@response({ status: 200 })
response(
@headers
headers: {
Age: Int32;
}
//...
) {}
//...
}
Define path parameters that appear in the path
provided in @endpoint()
. For example if the path is /users/:id
, the endpoint method must define a matching property within the @pathParams
decorated argument:
import { endpoint, pathParams, request, String } from "@airtasker/spot";
@endpoint({
method: "GET",
path: "/users/:id"
})
class GetUser {
@request
request(
@pathParams
pathParams: {
id: String;
}
//...
) {}
//...
}
Note: the name of the property must match the name of the path parameter.
Define query parameters:
import { endpoint, queryParams, request, String } from "@airtasker/spot";
@endpoint({
method: "GET",
path: "/users"
})
class GetUsers {
@request
request(
@queryParams
queryParams: {
search?: String;
}
//...
) {}
//...
}
Define request or response body:
import { body, endpoint, request, response } from "@airtasker/spot";
@endpoint({
method: "POST",
path: "/users"
})
class CreateUser {
@request
request(@body body: CreateUserBody) {}
@response({ status: 201 })
response(@body body: UserBody) {}
//...
}
interface CreateUserBody {
firstName: String;
lastName: String;
}
interface UserBody {
name: String;
}
Define a known response for the endpoint. Responses are identified by their HTTP status code. @response
can be used multiple times to define multiple responses:
import { body, endpoint, request, response, String } from "@airtasker/spot";
@endpoint({
method: "POST",
path: "/users"
})
class CreateUser {
@request
resquest(@body body: CreateUserRequest) {}
@response({ status: 201 })
successResponse(@body body: UserResponse) {}
@response({ status: 400 })
badRequestResponse(@body body: ApiError) {}
//...
}
interface CreateUserRequest {
firstName: String;
lastName: String;
}
interface UserResponse {
firstName: String;
lastName: String;
role: String;
}
interface ApiError {
message: String;
}
Field | Description |
---|---|
status |
(required) HTTP status code for the error |
Define a default response for the endpoint. The default response represents the default format of a response when no other specific @response
is matched (by status code). This can only be used once for an @endpoint
:
import {
body,
defaultResponse,
endpoint,
request,
response,
String
} from "@airtasker/spot";
@endpoint({
method: "POST",
path: "/users"
})
class CreateUser {
@request
request(@body body: CreateUserRequest) {}
@response({ status: 201 })
successResponse(@body body: UserResponse) {}
@defaultResponse
defaultResponse(@body body: ApiError) {}
//...
}
interface CreateUserRequest {
firstName: String;
lastName: String;
}
interface UserResponse {
firstName: String;
lastName: String;
role: String;
}
interface ApiError {
message: String;
}
Type | Description | Example |
---|---|---|
null |
The null value | details: null |
String |
A string value | name: String |
Number |
A floating point number (alias for Float ) |
percentage: Number |
Float |
A floating point number | percentage: Float |
Double |
A double precision floating point number | percentage: Double |
Integer |
An integer (alias for Int32 ) |
age: Integer |
Int32 |
A signed 32 bit integer | age: Int32 |
Int64 |
A signed 64 bit integer | numAtoms: Int64 |
boolean |
A boolean value | isAdmin: boolean |
Date |
A full-date as defined by https://tools.ietf.org/html/rfc3339#section-5.6
|
dateOfBirth: Date |
DateTime |
A date-time as defined by https://tools.ietf.org/html/rfc3339#section-5.6
|
createdAt: DateTime |
Constant | An exact value | role: "admin" |
Union | One-of |
role: "admin" | "member" , param: string | number
|
Array | Collection | nicknames: String[] |
Object | An object | person: { firstName: String; lastName: String; } |
Attributes can be marked as optional using TypeScript's standard optional ?
token:
@body
body: {
firstName: String;
lastName: String;
middleName?: String; // optional
}
HEAD, GET, POST, PUT, PATCH, DELETE
Documentation is provided via JSDoc. These are used where appropriate when generating other document formats such as OpenAPI:
✅ /** Description */
✅ /**
* Description
*/
🚫 // Description
🚫 /* Description */
import {
api,
body,
endpoint,
headers,
Int32,
pathParams,
queryParams,
request,
response,
securityHeader,
String
} from "@airtasker/spot";
/** My API description */
@api({ name: "My API" })
class MyAPI {
/** Security header description */
@securityHeader
"security-header": String;
}
/** My endpoint description */
@endpoint({
method: "GET",
path: "/users/:id/posts"
})
class MyEndpoint {
@request
request(
@pathParams
pathParams: {
/** Id path param description */
id: Int32;
},
@queryParams
queryParams: {
/** Visible query param description */
visible?: boolean;
},
@headers
headers: {
/** My header description */
"X-My-Header": String;
}
) {}
/** Ok response description */
@response({ status: 200 })
okResponse(
@body
body: ResponseBodyType
) {}
}
/** Response body type description */
interface ResponseBodyType {
/** Property description */
property: String;
}
Define an OpenAPI 3 Server. This may be defined multiple times:
import { api, oa3server, oa3serverVariables } from "@airtasker/spot";
@api({
name: "My API",
version: "1"
})
class Api {
/**
* The production API server
*/
@oa3server({ url: "https://{username}.gigantic-server.com:{port}/{basePath}" })
productionServer(
@oa3serverVariables
variables: {
/**
* this value is assigned by the service provider, in this example `gigantic-server.com`
*
* @default "demo"
*/
username: String,
/**
* @default "8443"
*/
port: "8443" | "443",
/**
* @default "v2"
*/
basePath: String
}
) {}
}