Message
@message
extern dec message(target: Model, name?: valueof string);把一個 model 標記為 AsyncAPI message。每個被標記的 model 會成為 components.messages 的一筆,其 payload 指向該 model 的 schema。
target 必須是 Model。payload 只是單一 scalar 的訊息,必須把該 scalar 包進一個 model 裡。
@message
model OrderCreated {
orderId: string;
amount: float64;
}components:
messages:
OrderCreated:
payload:
$ref: "#/components/schemas/OrderCreated"
schemas:
OrderCreated:
type: object
properties:
orderId:
type: string
amount:
type: number
format: double
required:
- orderId
- amount選填引數可覆寫 key:
@message("order.created.v1")
model OrderCreated {
orderId: string;
}要注意:
- 只有被觸及的 model 會輸出。
components.schemas只收 message 能觸及的 model(直接引用或透過屬性間接引用)。沒有任何 message 引用到的 model 不會出現。 - message key 不帶 namespace 前綴,schema key 會帶。
namespace Sales裡的@message model Ev會產出 message keyEv與 schema keySales.Ev。當某個 message key 剛好等於另一個型別的 schema key 時,emitter 會回報message-key-shadows-schema-key。
@contentType
extern dec contentType(target: Model, contentType: valueof string);設定 message payload 的媒體型態(media type)。沒有標記時不輸出這個欄位,改由文件層級的 defaultContentType 生效。
@message
@contentType("application/avro")
model OrderCreated {
orderId: string;
}components:
messages:
OrderCreated:
name: OrderCreated
contentType: application/avro
payload:
$ref: "#/components/schemas/OrderCreated"emitter 原樣輸出這個字串。它不解析媒體型態,也不會因此改變 payload schema。
每個 model 只套用一次。一個 message 只有一個 content type,所以第二次套用回報 duplicate-content-type-decorator。
媒體型態不可以是空字串。空白的媒體型態沒有指出任何格式。emitter 回報 empty-content-type 並丟棄這次套用。這個 message 接著退回文件層級的 defaultContentType。
@header
extern dec header(target: ModelProperty);把 message model 的一個欄位標記為 message header。emitter 會把每個被標記的欄位從 payload schema 抽出來,集中放進該 message 的 headers schema。payload 只留沒有被標記的欄位。
@message
model OrderCreated {
@header
correlationId: string;
@header
retryCount?: int32;
orderId: string;
}components:
messages:
OrderCreated:
name: OrderCreated
headers:
type: object
properties:
correlationId:
type: string
retryCount:
type: integer
format: int32
required:
- correlationId
payload:
$ref: "#/components/schemas/OrderCreatedPayload"
schemas:
OrderCreatedPayload:
type: object
properties:
orderId:
type: string
required:
- orderId要注意:
- 這個 decorator 不收名稱引數。若 header 的 key 不是合法的 TypeSpec 識別字,用
@encodedName指定,寫法與改 payload 欄位名相同。 - 只有
@messagemodel 的頂層欄位會被抽出。payload 更深層的標記會回報nested-header-ignored,該欄位留在 payload。headers 本身要有巢狀結構時,改用@headers。 extends與...在這裡行為不同。展開語法...Base把屬性複製進 message model,被標記的屬性成為 message 自己的欄位,會被抽出。extends Base則讓屬性留在 base model 上,payload 用allOf引用它。抽走它會影響所有繼承同一個 base 的 model,所以 emitter 保留該欄位並回報inherited-header-ignored。- payload 會拿到自己的一份 component。抽出只影響宣告 header 的那個 message。model 自己的
components.schemas項目保留全部欄位,所以 subtype、其他 message 的欄位型別,以及任何其他讀取者,看到的都是完整結構。message 指向第二份 component,key 是<Model>Payload,裡面只有留下來的欄位。若你自己已經宣告了名為<Model>Payload的 model,emitter 回報duplicate-schema-key,該 message 退回指向 model 自己的 component。 - 名為
content-type的 header 欄位會與@contentType衝突。AsyncAPI 只有一個欄位表示 content type,所以 emitter 回報content-type-header-conflict,不自行挑一個來源。
@headers
extern dec headers(target: Model, headers: Model);用一個獨立的 model 設定整個 message 的 headers schema。headers 自成一個 model、或 headers 需要巢狀結構時用它。emitter 會把該 model 輸出到 components.schemas 並以 $ref 引用,所以多個 message 可以共用同一份 headers 定義。
model MqmdFields {
CorrelId: string;
}
model ShippingHeaders {
MQMD: MqmdFields;
}
@message
@headers(ShippingHeaders)
model OrderShipped {
orderId: string;
}components:
messages:
OrderShipped:
name: OrderShipped
headers:
$ref: "#/components/schemas/ShippingHeaders"
payload:
$ref: "#/components/schemas/OrderShipped"這個 model 必須是 object 型態。AsyncAPI 規定 headers schema 描述一組 key/value map,所以 array 為底的 model 會回報 headers-not-object。
同一個 message 不要同時用欄位層級的 @header 或 @rawHeaders。兩個來源沒有明確的優先序,所以 emitter 回報 duplicate-message-headers,且兩邊都不輸出。
headers model 上名為 content-type 的屬性,與 message 上的 @contentType 衝突,情形和欄位層級的同名 @header 相同。emitter 回報 content-type-header-conflict。headers model 繼承來的屬性也會檢查。
@rawPayload
extern dec rawPayload(target: Model, schemaFormat: valueof string, schema: valueof unknown);用另一種格式的 schema 描述 message 的 payload,例如 Avro 或 Protobuf。AsyncAPI 稱這個結果為 Multi Format Schema Object。emitter 把 schemaFormat 與 schema 寫進 message,並且原樣輸出 schema。
emitter 不解讀 schema 的內容。所以它無法檢查 schema 是否符合該格式,也無法檢查 schema 是否符合這個 model。
@message
@contentType("application/avro")
@rawPayload(
"application/vnd.apache.avro;version=1.9.0",
#{
type: "record",
name: "OrderCreated",
fields: #[#{ name: "orderId", type: "string" }],
}
)
model OrderCreated {}components:
messages:
OrderCreated:
name: OrderCreated
contentType: application/avro
payload:
schemaFormat: application/vnd.apache.avro;version=1.9.0
schema:
type: record
name: OrderCreated
fields:
- name: orderId
type: string這個 model 不描述任何進入這個 message 的內容,所以不是 schema 走訪的起點,也不佔 components.schemas 的 key。它引用的 model 也一樣。但走訪不會跳過這個 model:其他 message 引用到它時,它仍然會拿到一般的 components.schemas 項目。這個 model 只承載 message decorator,內容留空。
raw schema 直接寫進 message,不寫進 components.schemas。所以目前兩個 message 無法共用同一份 raw schema。
schema 可以是任何形狀的值。常見的形式是 object value。字串與陣列也合法,因為 AsyncAPI 把這個欄位定義為 any。
Avro 中名為 namespace 的欄位要用反引號包住,因為 namespace 是 TypeSpec 的保留字:#{ `namespace`: "com.example" }。
AsyncAPI 規定或建議的 schemaFormat 值不會有任何回報。其他值仍然會輸出,同時回報 unknown-schema-format 警告。空白值會回報 empty-schema-format,該 message 退回使用從 model 建出來的 schema。
格式與 schema 之間有兩條規則,emitter 兩條都會回報。非 JSON 基礎的格式(例如 Protobuf)要把 schema 寫成字串。寫成 object 會回報 non-string-raw-schema。最外層以 #/ 開頭的 $ref 指向這份文件,而文件裡的每個 schema 都是 AsyncAPI Schema Object。其他格式會回報 raw-schema-local-ref。兩種情況下 schema 都照原樣輸出。
emitter 也會解析最外層的 $ref,所有格式都一樣。若 reference 在完成的文件中找不到對應位置,會回報 unresolved-raw-schema-ref。
同一個 message 不要同時用欄位層級的 @header。被抽出的欄位會離開 payload schema,而 emitter 無法從它不解讀的 schema 中移除欄位。emitter 回報 raw-payload-lifted-header,兩邊都照樣輸出。改用 @headers 或 @rawHeaders 描述 headers。這兩個都可以與本 decorator 併用,且不會有任何回報。
同一個 model 只能套用一次。第二次套用會回報 duplicate-raw-payload-decorator。
@rawHeaders
extern dec rawHeaders(target: Model, schemaFormat: valueof string, schema: valueof unknown);用另一種格式的 schema 描述 message 的 headers。它寫進 headers 欄位的 Multi Format Schema Object,與 @rawPayload 寫進 payload 的完全相同。schemaFormat 與 schema 的規則也相同。
@message
@rawHeaders(
"application/vnd.apache.avro;version=1.9.0",
#{
type: "record",
name: "OrderHeaders",
fields: #[#{ name: "traceId", type: "string" }],
}
)
model OrderCreated {
orderId: string;
}components:
messages:
OrderCreated:
name: OrderCreated
headers:
schemaFormat: application/vnd.apache.avro;version=1.9.0
schema:
type: record
name: OrderHeaders
fields:
- name: traceId
type: string
payload:
$ref: "#/components/schemas/OrderCreated"這是描述 message headers 的第三種方式。另外兩種是欄位層級的 @header,以及傳給 @headers 的 model。三者只能擇一。同時使用一種以上會回報 duplicate-message-headers,且完全不輸出 headers。
raw headers 不會從 payload 抽出任何欄位。所以 payload 仍然描述 model 的每一個欄位。
同一個 model 只能套用一次。第二次套用會回報 duplicate-raw-headers-decorator。
@correlationId
extern dec correlationId(target: Model, location: valueof string, description?: valueof string);設定 message 的 correlationId。location 是 runtime expression,指出關聯值在執行期的位置。
@message
@correlationId("$message.header#/correlationId", "把回覆與原請求關聯起來。")
model OrderCreated {
@header
correlationId: string;
orderId: string;
}components:
messages:
OrderCreated:
name: OrderCreated
headers:
type: object
properties:
correlationId:
type: string
required:
- correlationId
payload:
$ref: "#/components/schemas/OrderCreatedPayload"
correlationId:
location: "$message.header#/correlationId"
description: 把回覆與原請求關聯起來。合法的 location 是 $message.header# 或 $message.payload#,後面可再接一段 JSON Pointer。下列都合法:
| Location | 意義 |
|---|---|
$message.header# | headers 物件本身 |
$message.header#/correlationId | 單一 header |
$message.header#/MQMD/CorrelId | 巢狀兩層的 header |
$message.payload#/order/id | payload 內巢狀的欄位 |
# 是必要的。規格的 ABNF 條文看起來像是可以省略,但規格的正規 JSON Schema 要求它,官方 AsyncAPI parser 也會拒絕帶有 $message.header(不含 #)的文件。
其他寫法回報 invalid-correlation-id-location,且不輸出 correlationId。
emitter 只檢查格式。它不檢查該 pointer 是否指向 headers 或 payload schema 已宣告的欄位。
每個 model 只套用一次。第二次套用回報 duplicate-correlation-id-decorator。
@messageExample
extern dec messageExample(
target: Model,
example: valueof MessageExampleValue,
options?: valueof MessageExampleOptions
);為 message 加上一筆範例。參數形狀:
| 欄位 | 型別 | 必填 |
|---|---|---|
example.headers | Record<unknown> | 否 |
example.payload | unknown | 否 |
options.name | string | 否 |
options.summary | string | 否 |
headers 是一組 key/value map,因為 AsyncAPI Message Example Object 把它定義為 Map[string, any]。payload 則是自由格式,規格把它定義為 any,所以純量 payload 也合法。
可重複套用:每次套用在 examples 陣列加一筆,順序照原始碼順序。AsyncAPI 的 examples 是陣列,所以一個 message 可以列出多種情境,每筆各有自己的 name。
@message
@messageExample(
#{ headers: #{ correlationId: "abc-123" }, payload: #{ orderId: "o-1", total: 12.5 } },
#{ name: "smallOrder", summary: "單一品項,已付款。" }
)
@messageExample(#{ payload: #{ orderId: "o-2", total: 999.0 } }, #{ name: "largeOrder" })
model OrderCreated {
@header
correlationId: string;
orderId: string;
total: float64;
}components:
messages:
OrderCreated:
name: OrderCreated
examples:
- name: smallOrder
summary: 單一品項,已付款。
headers:
correlationId: abc-123
payload:
orderId: o-1
total: 12.5
- name: largeOrder
payload:
orderId: o-2
total: 999要注意:
- 每筆範例至少要有
headers或payload其中之一。 兩者皆無的範例說明不了任何事,會回報empty-message-example並丟棄該筆。 - 範例內容不會與 message schema 對照檢查。 值照寫的原樣輸出。若某個值無法序列化為 JSON(例如自訂 scalar 的建構式),該筆整筆丟棄,並回報
unserializable-message-example。