使用 API Blueprint 寫文件¶
原文在 2023-04-24 完成
API Blueprint 是一種 markdown 語法的擴充,用來寫 RESTful API 文件
工具¶
API Blueprint 官網有列出許多工具,推薦網站Apiary。免費註冊後就能在線上編輯文件,左右對照 Markdown 和預覽結果,對於屬性有完整支援。
Aglio 則用來把 Markdown 轉成 HTML,安裝與使用方法如下:
# 在 Debian 11 上安裝
# 先安裝 Node.js
sudo apt install curl build-essential
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
sudo apt install nodejs
node -v
npm -v
# 使用 npm 全域安裝
sudo npm install -g aglio
# 也可以用 yarn 安裝
# 安裝 yarn
sudo apt install curl gnupg
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
yarn --version
# 使用 yarn 全域安裝
sudo yarn global add aglio
接著編輯好 markdown 檔案,例如 example.md,然後執行 aglio -i example.md -o example.html
,轉換成 example.html。可以執行 aglio
指令看看參數有哪些。可以使用 aglio -i example.md -s -h 0.0.0.0 -p 8000
產生即時預覽。
語法¶
以下筆記參考自官方文件的範例
開始¶
FORMAT: 1A
HOST: https://api.example.com/v1
# The Simplest API
This is one of the simplest APIs written in the **API Blueprint**. One plain
resource combined with a method and that's it! We will explain what is going on
in the next installment -
[Resource and Actions](02.%20Resource%20and%20Actions.md).
FORMAT: 1A
表示文件格式是 markdown,不寫也可以。
HOST
表示 API 所在的網站網址,如果有一個路由是 GET /users,在 aglio 的示範會顯示爲 GET https://api.example.com/v1/users
。HOST
可以不寫,則示範會顯示爲 GET /users
接著一定要有一個 h1 標題(# The Simplest API
)作爲文件主標題,一般是說明這是哪一個 API 文件,標題下方可以加上摘要描述,寫法和一般的markdown 一樣。
# GET /message
+ Response 200 (text/plain)
Hello World!
這是一個簡單的例子,說明 HTTP 請求與回應。#
之後的是 HTTP method GET
和 RESTful 資源 /message
,回應則寫成 + Response
,使用了 markdown 的無序清單語法 +
(清單可用 +
、-
或 *
開頭),Response 後面接著是 HTTP 狀態碼 200 和 Content-Type text/plain(要寫在小括號內),最後回應的 body 則用 markdown 的 code 區塊語法表示,所以 Hello World 前面要有 8 個空格或是 2 個 Tab,或是用成對的三個 ` 包起來,不過一般都是用空格或 Tab,之後會看到,那是因爲 body 會和 Headers 等其它設定構成巢狀結構。
資源與動作¶
上一個例子中,定義了一個路由 GET /message
放在 h1 標題裡,如果有另一個路由 PUT /message
,可以放在另一個 h1 標題裡,但是 /message
重複了,所以更好的方式是定義一個資源 /message
,裏面包含 2 個動作 GET
和 PUT
。如果日後需要修改資源,只要修改一個地方就好,減少產生 bug 的機會。
# /message
在這裡定義了一個資源 `/message`
## GET
裡面定義了動作 GET,所以等同於路由 GET /message
+ Response 200 (text/plain)
Hello World!
## PUT
這裡是另一個動作 PUT,等同於路由 PUT /message,用來修改資料。
+ Request (text/plain)
All your base are belong to us.
+ Response 204
動作 PUT
中定義了 HTTP 請求,和回應一樣使用 markdown 的無序清單語法 +
,後面接著 Request
,括號內是請求的標題欄位 Accept text/plain,表示可接受的回應 body 類型是純文字。請求的 body 和回應一樣使用 markdown 的 code 區塊語法,所以 All your base are belong to us. 的前面要有 8 個空格或 2 個 Tab,最後是回應 + Response 204
,狀態碼 204 表示 No Content,表示修改完成,回應的 body 沒有內容。
命名資源與動作¶
# 訊息 [/message]
資源 /message 取名爲訊息
## 接收訊息 [GET]
GET /message 的名稱是接收訊息。
## 更新訊息 [PUT]
PUT /message 的名稱是更新訊息。
#
之後,資源和動作改成放在中括號裡。名稱可以用中文。
群組¶
可以把多個資源和動作包含在一個群組裡。
# Group 訊息
定義一個訊息群組
## 訊息 [/message]
### 接收訊息 [GET]
### 更新訊息 [PUT]
# Group Users
Group
或 group
後面接著群組名稱(訊息),則之後的資源和動作都包含在這個群組之內,直到出現另一個群組。資源和動作的 #
可以維持原來的 # 訊息 [/message]
和 ## 接收訊息 [GET]
,改成如上這樣是爲了閱讀 markdown 時了解階層關係,是比較好的做法。
回應¶
定義標頭和回應的方式如下:
# GET /message
+ Response 200 (text/plain)
這個是純文字回應
+ Headers
X-My-Message-Header: 42
+ Body
Hello World!
+ Response 200 (application/json)
這個是 JSON 回應
+ Headers
X-My-Message-Header: 42
+ Body
{ "message": "Hello World!" }
一個路由有兩個回應,指定 body 是純文字或 JSON。回應的格式是 + Response 狀態碼 (Content-Type)
,標頭 Headers 和內容 Body 是回應的內含資訊,所以用巢狀無序清單,必須向內縮至少一個空格,慣例是 4 個空格或 1 個 Tab;X-My-Message-Header: 42
和 Hello World!
都使用 code 區塊,必須向內縮止至少 8 個空格或 2 個 Tab,再加上 Header 或 Body 的縮排,所以是 12 個空格或 3 個 Tab。
請求¶
定義請求的方式如下:
## 訊息 [/message]
### 接收訊息 [GET]
+ Request 純文字
名稱是純文字的請求
+ Headers
Accept: text/plain
+ Response 200 (text/plain)
+ Headers
X-My-Message-Header: 42
+ Body
Hello World!
+ Request JSON 訊息
名稱是 JSON 訊息的請求
+ Headers
Accept: application/json
+ Response 200 (application/json)
+ Headers
X-My-Message-Header: 42
+ Body
{ "message": "Hello World!" }
### 更新訊息 [PUT]
2 種不同的更新請求:純文字和 JSON
+ Request 更新文字訊息 (text/plain)
All your base are belong to us.
+ Request 更新 JSON 訊息 (application/json)
{ "message": "All your base are belong to us." }
+ Response 204
+
後面接著關鍵字 Request,然後標頭欄位 Accept 可以放在小括號內 (text/plain) 或是放在 Headers 內。
可以爲請求取名,名稱放在 + Request
和小括號之間,可以用中文,但是回應不能取名。
所以請求和回應很類似,都用 +
開頭,都有 + Headers
和 + Body
定義標頭和內容,都用小括號定義 Accept 或 Content-Type。
不同的是請求可以取名,回應不行;回應要有 HTTP 狀態碼。
參數 Parameters¶
資源通常有 id 或關鍵字包含其中,例如 /users/123
或 /search?q=關鍵字
,這時使用參數來定義。
## 訊息 [/message/{id}]
+ Parameters
+ id: 1 (number) - 訊息的識別 ID
## 所有訊息 [/messages{?limit}]
### Retrieve all Messages [GET]
+ Parameters
+ limit (number, optional) - 回傳訊息的最大數量
+ Default: `20`
/message/{id}
定義了一個名稱爲 id 的URI Template variable,使用大括號包起來,然後在底下的 + Parameters
定義 id 。
同樣使用 markdown 的無序清單表示每一個項目,所以用 +
開頭,向內縮4 個空格或 1 個 Tab,然後格式是
+ Parameters
+ 名稱: 範例值 (型別, optional 或 required) - 描述(可用 markdown 語法)
額外的描述(可用 markdown 語法)
+ Default: 預設值
參數 Parameters 和之後會提到的屬性 Attributes、資料結構 Data Structures 都使用Markdown Syntax for Object Notation (MSON)的語法。
- 名稱:參數的名稱,這是唯一必須要有的,其它都是可選的。
- 範例值用來示範這個參數的值,有些 render 例如 aglio 會顯示在 URI,例如 GET /users/123
- 型別:number, string, 或 boolean。預設是 string
- optional 或 required:表明這個參數是可選的或是必須要有的,預設是 required
- 描述和額外的描述:描述參數的用途等資訊,可用 markdown 語法。額外的描述要向內縮 4 個空格或 1 個 Tab
+ Default
:預設值,沒有指定參數值時所用的值,只在參數設定爲 optional 時可用,可用 ` 包起值,確保可以正確轉換成 HTML,尤其是字串。要向內縮 4 個空格或 1 個 Tab
資源模型 Resource Model¶
可以把請求或回應的內容獨立出來,重複引用,名爲資源模型。
## My Resource [/resource]
+ Model (text/plain)
Hello World
## My Message [/message]
+ Model (application/vnd.siren+json)
一個 application/vnd.siren+json 訊息資源範例
+ Headers
Location: http://api.acme.com/message
+ Body
{
"class": [ "message" ],
"properties": {
"message": "Hello World!"
},
"links": [
{ "rel": "self" , "href": "/message" }
]
}
### Retrieve a Message [GET]
在回應和請求中引用
+ Request 接收訊息的請求
[My Resource][]
+ Response 200
[My Message][]
上述有兩個資源模型,格式是 + Model (Content-Type)
,底下必須要向內縮 4 個空格或 1 個 Tab,表示這些都隸屬於這個資源模型。第一個的內容是簡寫,表示 body 的內容是 Hello World,第二個定義在資源 My Message 中,內含說明、Headers
和 Body
,然後在 + Request
或 + Response
向內縮 4 個空格或 1 個 Tab,使用 [資源名稱][]
引用它。
資源模型中的小括號在請求或回應中都代表 Content-Type,所以 Accept 需要在 + Headers
中明確指出才行。不過即使如此,在請求和回應中,Accept 也不會覆蓋 Content-Type,而是兩者併呈。
一個資源模型只能在資源中定義,而且一個資源只能有一個資源模型,所以上面的範例只有一個資源模型。模型本身不能像請求一樣命名,它使用資源的名稱作爲引用的名稱,所以沒有 + Model A (application/json)
這樣的寫法。
引用使用的是 Markdown 的參考語法,不過第二個中括號要空著,否則無法引用。
JSON Schema¶
JSON Schema用來描述 JSON 的資料格式,例如欄位 name 的型別是字串,欄位 quantity 的型別是整數,方便自動測試。使用屬性 Attributes 時,會自動產生 Schema,你也可以手動輸入 Schema。
+ Response 200 (application/json)
+ Body
{
"id": "abc123",
"title": "This is a note",
"content": "This is the note content."
"tags": [
"todo",
"home"
]
}
+ Schema
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"content": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
使用 + Schema
手動輸入 JSON Schema,如上所示。apiary 自動產生的 JSON Schema 如下所示,所以 + Schema
手動輸入的內容會覆蓋掉屬性自動產生的 JSON Schema。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {
"type": "string"
},
"content": {
"type": "string"
},
"tags": {
"type": "array"
}
}
}
之後的內容是在請求和回應的 body 中使用Markdown Syntax for Object Notation (MSON)描述 JSON 格式,aglio 沒有完整支援,而且使用 apiary 時只能把 MSON 轉成 JSON,稍微複雜一點的 JSON 就會需要更複雜的語法,所以我覺得直接寫 JSON 就好了,沒有必要多加一層語法。如果單純爲了避免重複程式碼,可以使用模型就好了,沒有必要使用接下來介紹的屬性和資料結構。
屬性 Attributes¶
請求或回應的 body 常常是重複的,可以用屬性 Attributes 來定義 body,然後在請求或回應中引用它。
## 折價卷 [/coupons/{id}]
折價卷資料。屬性定義在資源中如下:
+ Parameters
+ id (string)
折價卷 ID
+ Attributes (object)
+ id: 250FF (string, required)
+ created: 1415203908 (number) - 新增時間戳
+ percent_off: 25 (number)
打折的百分比,從 1 到 100
+ redeem_by (number) - 兌換有效期限,此爲時間戳
### 收到一張折價卷 [GET]
+ Response 200 (application/json)
+ Attributes (折價卷)
### 新增一個折價卷 [POST]
屬性也可以定義在請求和回應中:
+ Request
+ Attributes (object)
+ percent_off: 25 (number)
+ redeem_by (number)
### 列出所有折價卷 [GET /coupons]
+ Response 200 (application/json)
+ Attributes (array[折價卷, 折價卷])
屬性 Attributes 可以定義在資源或動作中,上面的例子裡,在資源折價卷中定義了一個屬性,有 4 個欄位,欄位的定義方式使用Markdown Syntax for Object Notation (MSON),然後在動作 GET 的回應中使用 + Attributes (折價卷)
引用,會自動轉成 JSON,所以 body 會是如下所示:
{
"id": "250FF",
"created": 1415203908,
"percent_off": 25,
"redeem_by": 0
}
動作「新增一個折價卷」的請求定義了一個屬性作爲 body,所以屬性可以定義在請求和回應中作爲 body,但是寫法是如上所示,不是在 + Body
中內含 + Attributes
,如下所示:
+ Response 200 (application/json)
+ Body
+ Attributes (object)
+ percent_off: 25 (number)
+ redeem_by (number)
不能引用自己內部定義的屬性。如下所示,在動作 get users 內定義的屬性,不能用在自己的回應中引用。
# users [/users]
## get users [GET]
+ Attributes (object)
+ name (string)
+ email (string)
+ Response 200 (application/json)
+ Attributes (get users)
資料結構 Data Structures¶
屬性 Attributes 不一定要屬於某個資源或動作,可以單獨放到外面,名爲資料結構 Data Structures 的區塊中。
### 收到折價卷 [GET /coupons]
+ Response 200 (application/json)
+ Attributes (折價卷)
# Data Structures
## 折價卷 (object)
+ id: 250FF (string, required)
+ created: 1415203908 (number) - 新增時間戳
+ percent_off: 25 (number)
打折的百分比,從 1 到 100
+ redeem_by (number) - 兌換有效期限,此爲時間戳
上述放在折價卷資源中的屬性,改成放在資料結構中,則 + Attributes (object)
改成 ## 折價卷 (object)
,內含的欄位 id, created, percent_off 和 redeem_by 都不變,只是沒有向內縮排
請注意,使用 aglio 轉換時,如果在資源或動作中定義屬性,會沒有 body,但是在 Data Structures 定義就沒有這個問題。
總結:屬性可以定義在資源、動作、請求與回應和資料結構,但是只能引用定義在其他資源和資料結構的屬性。
繼承屬性¶
屬性之間可以有類似類別的繼承關係,例如定義折價卷屬性基礎的 2 個欄位 percent_off 和 redeem_by,然後在需要時繼承它,再新增 2 個欄位
### 收到折價卷 [GET /coupons]
+ Response 200 (application/json)
+ Attributes (折價卷)
# Data Structures
## 折價卷基礎 (object)
+ percent_off: 25 (number)
打折的百分比,從 1 到 100
+ redeem_by (number) - 兌換有效期限,此爲時間戳
## 折價卷 (折價卷基礎)
+ id: 250FF (string, required)
+ created: 1415203908 (number) - 新增時間戳
折價卷基礎先定義 2 個欄位 percent_off 和 redeem_by,然後折價卷屬性在小括號中指定繼承它,再新增 2 個欄位 id 和 created,資源中的屬性也能這樣繼承。
{
"percent_off": 25,
"redeem_by": 0,
"id": "250FF",
"created": 1415203908
}
只有定義在資源和資料結構的屬性可以被引用,所以也只有這 2 個地方的屬性可以被繼承。