接下來將使用在 [Day 06] OpenAPI:標準的 API 描述技術規範(五) 時相同的情境做說明。會員允許多種登入方式,而透過不同的登入方式會有不同的註冊資訊組合,包含:以使用者名稱和密碼註冊(有使用者名稱和密碼兩個欄位)、以 Email 和密碼註冊(有 Email 和密碼兩個欄位)、和使用三方登入(只有一個外部 ID)。
若要宣告一個欄位有多種型別,可使用 |
串接它的各個型別。先定義三種不同的帳號模型,範例程式碼如下:
// member/model/default-account-model.tsp
namespace MemberService.Member.Model;
model DefaultAccountModel {
@doc("帳號")
userId: string;
@doc("密碼")
password: string;
}
// member/model/email-account-model.tsp
namespace MemberService.Member.Model;
model EmailAccountModel {
@doc("電子郵件")
email: string;
@doc("密碼")
password: string;
}
// member/model/thirdparty-account-model.tsp
namespace MemberService.Member.Model;
model ThirdpartyAccountModel {
@doc("第三方使用者 ID")
userId: string;
}
接著在 MemberModel
的 account
欄位用 |
串接型別,範例程式碼如下:
// member/model/member-model.tsp
import "./default-account-model.tsp";
import "./email-account-model.tsp";
import "./thirdparty-account-model.tsp";
namespace MemberService.Member.Model;
model MemberModel {
@doc("會員 ID")
id: int64;
@doc("帳號")
account: DefaultAccountModel | EmailAccountModel | ThirdpartyAccountModel;
// 中間省略
}
這時編譯後得到的 openapi.yaml
中可看到 account
欄位用 anyOf
對應到三個模型如下:
# 以上省略
components:
schemas:
Member.Model.MemberModel:
type: object
required:
- id
- account
- name
- phone
properties:
id:
type: integer
format: int64
description: 會員 ID
account:
anyOf:
- $ref: '#/components/schemas/Member.Model.DefaultAccountModel'
- $ref: '#/components/schemas/Member.Model.EmailAccountModel'
- $ref: '#/components/schemas/Member.Model.ThirdpartyAccountModel'
description: 帳號
# 以下省略
一種常見的 API 設計是所有 API 的 Response 都有相同的基本結構,僅在特定欄位視 API 不同而有不同的回傳,例如下面的 JSON:
{
"message": "Success",
"data": {} // data 欄位因 API 而不同
}
透過繼承的方式,可避免每支 API 的回傳模型都要自己定義一次基礎結構的問題。首先先定義出回傳的基礎結構模型,範例程式碼如下:
// base/model/base-response-model.tsp
import "@typespec/http";
using TypeSpec.Http;
namespace MemberService.Base.Model;
model BaseResponseModel {
@statusCode _: 200;
@header `x-request-id`: string;
@doc("回應訊息")
message: string;
}
接著使用 extend
關鍵字定義取得單一會員 API 回傳模型繼承 BaseResponseModel
,並針對這支 API 的場景回傳型別為 MemberModel
的 data
欄位,範例程式碼如下:
// member/model/get-member-response-model.tsp
import "../../base/model/base-response-model.tsp";
import "../model/member-model.tsp";
using MemberService.Base.Model;
namespace MemberService.Member.Model;
model GetMemberResponseModel extends BaseResponseModel {
@doc("回應資料")
data: MemberModel;
}
並將操作的回傳型別改為 GetMemberResponseModel
,範例程式碼如下:
// member/member.tsp
import "@typespec/http";
import "./model/member-model.tsp";
import "./model/get-member-response-model.tsp";
import "../base/model/base-response-model.tsp";
using TypeSpec.Http;
using MemberService.Member.Model;
using MemberService.Base.Model;
namespace MemberService.Member;
@summary("取得單一會員")
@doc("""
這個 API 用來取得單一會員的資料。
這個 API 需要提供會員 ID,並回應該會員的基本資料。
""")
@get()
@route("members/{memberId}")
op get(@path memberId: int64): GetMemberResponseModel;
// 以下省略
這時編譯後得到的 openapi.yaml
中可看到 GetMemberResponseModel
用 allOf
對應到 BaseResponseModel
和 MemberModel
兩個模型如下:
components:
schemas:
Base.Model.BaseResponseModel:
type: object
required:
- message
properties:
message:
type: string
description: 回應訊息
Member.Model.GetMemberResponseModel:
type: object
required:
- data
properties:
data:
allOf:
- $ref: '#/components/schemas/Member.Model.MemberModel'
description: 回應資料
allOf:
- $ref: '#/components/schemas/Base.Model.BaseResponseModel'
延續繼承的範例,比較常見的寫法是使用泛型,指定型別為 <T>
並在各個地方傳入的型別。調整 BaseResponseModel
支援泛型欄位 data
,並命名資料型別為 DataType
,範例程式碼如下:
// member/model/get-member-response-model.tsp
import "@typespec/http";
using TypeSpec.Http;
namespace MemberService.Base.Model;
model BaseResponseModel<DataType> {
@statusCode _: 200;
@header `x-request-id`: string;
@doc("回應訊息")
message: string;
@doc("回應資料")
data: DataType;
}
接著調整操作的回傳型別為 BaseResponseModel<MemberModel>
,範例程式碼如下:
// member/member.tsp
import "@typespec/http";
import "./model/member-model.tsp";
import "../base/model/base-response-model.tsp";
using TypeSpec.Http;
using MemberService.Member.Model;
using MemberService.Base.Model;
namespace MemberService.Member;
@summary("取得單一會員")
@doc("""
這個 API 用來取得單一會員的資料。
這個 API 需要提供會員 ID,並回應該會員的基本資料。
""")
@get()
@route("members/{memberId}")
op get(@path memberId: int64): BaseResponseModel<MemberModel>;
// 以下省略
這時編譯後得到的 openapi.yaml
中可看到 responses.200
即為 BaseResponseModel
結構,並在 data
欄位使用 MemberModel
如下:
paths:
/members/{memberId}:
get:
operationId: Member_get
summary: 取得單一會員
description: |2-
這個 API 用來取得單一會員的資料。
這個 API 需要提供會員 ID,並回應該會員的基本資料。
parameters:
- name: memberId
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: The request has succeeded.
headers:
x-request-id:
required: true
schema:
type: string
content:
application/json:
schema:
type: object
required:
- message
- data
properties:
message:
type: string
description: 回應訊息
data:
allOf:
- $ref: '#/components/schemas/Member.Model.MemberModel'
description: 回應資料
明天將介紹附加屬性及 TypeSpec.Http
提供的模型。