/**
* api語法示例及語法說明
*/
// api語法版本
syntax = "v1"
// import literal
import "foo.api"
// import group
import (
"bar.api"
"foo/bar.api"
)
info(
author: "songmeizi"
date: "2020-01-08"
desc: "api語法示例及語法說明"
)
// type literal
type Foo{
Foo int `json:"foo"`
}
// type group
type(
Bar{
Bar int `json:"bar"`
}
)
// service block
@server(
jwt: Auth
group: foo
)
service foo-api{
@doc "foo"
@handler foo
post /foo (Foo) returns (Bar)
}
在以上語法結構中,各個語法塊從語法上來說,按照語法塊為單位,可以在.api文件中任意位置聲明, 但是為了提高閱讀效率,我們建議按照以上順序進行聲明,因為在將來可能會通過嚴格模式來控制語法塊的順序。
?syntax
?是新加入的語法結構,該語法的引入可以解決:
注意
被import的api必須要和main api的syntax版本一致。
'syntax'={checkVersion(p)}STRING
syntax
?:固定token,標志一個syntax語法結構的開始checkVersion
?:自定義go方法,檢測STRING是否為一個合法的版本號,目前檢測邏輯為,STRING必須是滿足(?m)"v[1-9][0-9]*"正則。STRING
?:一串英文雙引號包裹的字符串,如"v1"一個api語法文件只能有0或者1個syntax語法聲明,如果沒有syntax,則默認為v1版本
syntax="v1"
syntax = "v2"
syntax = "v0"
syntax = v1
syntax = "V1"
隨著業(yè)務規(guī)模增大,api中定義的結構體和服務越來越多,所有的語法描述均為一個api文件,這是多么糟糕的一個問題, 其會大大增加了閱讀難度和維護難度,import語法塊可以幫助我們解決這個問題,通過拆分api文件, 不同的api文件按照一定規(guī)則聲明,可以降低閱讀難度和維護難度。
注意
這里import不像golang那樣包含package聲明,僅僅是一個文件路徑的引入,最終解析后會把所有的聲明都匯聚到一個spec.Spec中。 不能import多個相同路徑,否則會解析錯誤。
'import' {checkImportValue(p)}STRING
|'import' '(' ({checkImportValue(p)}STRING)+ ')'
import
?:固定token,標志一個import語法的開始checkImportValue
?:自定義go方法,檢測STRING是否為一個合法的文件路徑,目前檢測邏輯為,STRING必須是滿足(?m)"(/?[a-zA-Z0-9_#-])+\.api"正則。STRING
?:一串英文雙引號包裹的字符串,如"foo.api"import "foo.api"
import "foo/bar.api"
import(
"bar.api"
"foo/bar/foo.api"
)
import foo.api
import "foo.txt"
import (
bar.api
bar.api
)
info語法塊是一個包含了多個鍵值對的語法體,其作用相當于一個api服務的描述,解析器會將其映射到spec.Spec中, 以備用于翻譯成其他語言(golang、java等) 時需要攜帶的meta元素。如果僅僅是對當前api的一個說明,而不考慮其翻譯 時傳遞到其他語言,則使用簡單的多行注釋或者java風格的文檔注釋即可,關于注釋說明請參考下文的 隱藏通道。
注意
不能使用重復的key,每個api文件只能有0或者1個info語法塊
'info' '(' (ID {checkKeyValue(p)}VALUE)+ ')'
info
?:固定token,標志一個info語法塊的開始checkKeyValue
?:自定義go方法,檢測VALUE是否為一個合法值。VALUE
?:key對應的值,可以為單行的除'\r','\n','/'后的任意字符,多行請以""包裹,不過強烈建議所有都以""包裹info(
foo: foo value
bar:"bar value"
desc:"long long long long
long long text"
)
info(
foo: "foo value"
bar: "bar value"
desc: "long long long long long long text"
)
info()
info(
foo value
)
info(foo:"value")
info(
: "value"
)
info(
12: "value"
)
info(
foo: >
some text
<
)
在api服務中,我們需要用到一個結構體(類)來作為請求體,響應體的載體,因此我們需要聲明一些結構體來完成這件事情, type語法塊由golang的type演變而來,當然也保留著一些golang type的特性,沿用golang特性有:
bool
?,?int
?,?int8
?,?int16
?,?int32
?,?int64
?,?uint
?,?uint8
?,?uint16
?,?uint32
?,?uint64
?,?uintptr
?,?float32
?,?float64
?,?complex64
?,?complex128
?,?string
?,?byte
?,?rune
?,注意
- 不支持alias
- 不支持time.Time數(shù)據(jù)類型
- 結構體名稱、字段名稱、不能為golang關鍵字
由于其和golang相似,因此不做詳細說明,具體語法定義請在 ApiParser.g4 中查看typeSpec定義。
參考golang寫法
type Foo struct{
Id int `path:"id"` // ①
Foo int `json:"foo"`
}
type Bar struct{
// 非導出型字段
bar int `form:"bar"`
}
type(
// 非導出型結構體
fooBar struct{
FooBar int `json:"fooBar"`
}
)
type Foo{
Id int `path:"id"`
Foo int `json:"foo"`
}
type Bar{
Bar int `form:"bar"`
}
type(
FooBar{
FooBar int `json:"fooBar"`
}
)
type Gender int // 不支持
// 非struct token
type Foo structure{
CreateTime time.Time // 不支持time.Time,且沒有聲明 tag
}
// golang關鍵字 var
type var{}
type Foo{
// golang關鍵字 interface
Foo interface // 沒有聲明 tag
}
type Foo{
foo int
// map key必須要golang內(nèi)置數(shù)據(jù)類型,且沒有聲明 tag
m map[Bar]string
}
tag定義和golang中json tag語法一樣,除了json tag外,go-zero還提供了另外一些tag來實現(xiàn)對字段的描述, 詳情見下表。
綁定參數(shù)時,以下四個tag只能選擇其中一個
tag key | 描述 | 提供方 | 有效范圍 | 示例 |
json | json序列化tag | golang | request、response | json:"fooo"
|
path | 路由path,如/foo/:id
|
go-zero | request | path:"id"
|
form | 標志請求體是一個form(POST方法時)或者一個query(GET方法時/search?name=keyword ) |
go-zero | request | form:"name"
|
header | HTTP header,如 Name: value
|
go-zero | request | header:"name"
|
常見參數(shù)校驗描述
tag key | 描述 | 提供方 | 有效范圍 | 示例 |
optional | 定義當前字段為可選參數(shù) | go-zero | request | json:"name,optional"
|
options | 定義當前字段的枚舉值,多個以豎線|隔開 | go-zero | request | json:"gender,options=male"
|
default | 定義當前字段默認值 | go-zero | request | json:"gender,default=male"
|
range | 定義當前字段數(shù)值范圍 | go-zero | request | json:"age,range=[0:120]"
|
Tip
tag修飾符需要在tag value后以英文逗號,隔開
service語法塊用于定義api服務,包含服務名稱,服務metadata,中間件聲明,路由,handler等。
注意
- main api和被import的api服務名稱必須一致,不能出現(xiàn)服務名稱歧義。
- handler名稱不能重復
- 路由(請求方法+請求path)名稱不能重復
- 請求體必須聲明為普通(非指針)struct,響應體做了一些向前兼容處理,詳請見下文說明
語法定義
serviceSpec: atServer? serviceApi;
atServer: '@server' lp='(' kvLit+ rp=')';
serviceApi: {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
serviceRoute: atDoc? (atServer|atHandler) route;
atDoc: '@doc' lp='('? ((kvLit+)|STRING) rp=')'?;
atHandler: '@handler' ID;
route: {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
body: lp='(' (ID)? rp=')';
replybody: lp='(' dataType? rp=')';
// kv
kvLit: key=ID {checkKeyValue(p)}value=LINE_VALUE;
serviceName: (ID '-'?)+;
path: (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;
serviceSpec
?:包含了一個可選語法塊atServer和serviceApi語法塊,其遵循序列模式(編寫service必須要按照順序,否則會解析出錯)atServer
?: 可選語法塊,定義key-value結構的server metadata,'@server' 表示這一個server語法塊的開始,其可以用于描述serviceApi或者route語法塊,其用于描述不同語法塊時有一些特殊關鍵key 需要值得注意,見 atServer關鍵key描述說明。serviceApi
?:包含了1到多個serviceRoute語法塊serviceRoute
?:按照序列模式包含了atDoc,handler和routeatDoc
?:可選語法塊,一個路由的key-value描述,其在解析后會傳遞到spec.Spec結構體,如果不關心傳遞到spec.Spec, 推薦用單行注釋替代。handler
?:是對路由的handler層描述,可以通過atServer指定handler key來指定handler名稱, 也可以直接用atHandler語法塊來定義handler名稱atHandler
?:'@handler' 固定token,后接一個遵循正則[_a-zA-Z][a-zA-Z_-]*)的值,用于聲明一個handler名稱route
?:路由,有httpMethod、path、可選request、可選response組成,httpMethod是必須是小寫。body
?:api請求體語法定義,必須要由()包裹的可選的ID值replyBody
?:api響應體語法定義,必須由()包裹的struct、kvLit
?: 同info key-valueserviceName
?: 可以有多個'-'join的ID值path
?:api請求路徑,必須以'/'或者'/:'開頭,切不能以'/'結尾,中間可包含ID或者多個以'-'join的ID字符串key | 描述 | 示例 |
jwt | 聲明當前service下所有路由需要jwt鑒權,且會自動生成包含jwt邏輯的代碼 | jwt: Auth
|
group | 聲明當前service或者路由文件分組 | group: login
|
middleware | 聲明當前service需要開啟中間件 | middleware: AuthMiddleware
|
prefix | 添加路由分組 | prefix: api
|
key | 描述 | 示例 |
handler | 聲明一個handler | - |
@server(
jwt: Auth
group: foo
middleware: AuthMiddleware
prefix api
)
service foo-api{
@doc(
summary: foo
)
@server(
handler: foo
)
// 非導出型body
post /foo/:id (foo) returns (bar)
@doc "bar"
@handler bar
post /bar returns ([]int)// 不推薦數(shù)組作為響應體
@handler fooBar
post /foo/bar (Foo) returns // 可以省略'returns'
}
@server(
jwt: Auth
group: foo
middleware: AuthMiddleware
prefix: api
)
service foo-api{
@doc "foo"
@handler foo
post /foo/:id (Foo) returns (Bar)
}
service foo-api{
@handler ping
get /ping
@doc "foo"
@handler bar
post /bar/:id (Foo)
}
// 不支持空的server語法塊
@server(
)
// 不支持空的service語法塊
service foo-api{
}
service foo-api{
@doc kkkk // 簡版doc必須用英文雙引號引起來
@handler foo
post /foo
@handler foo // 重復的handler
post /bar
@handler fooBar
post /bar // 重復的路由
// @handler和@doc順序錯誤
@handler someHandler
@doc "some doc"
post /some/path
// handler缺失
post /some/path/:id
@handler reqTest
post /foo/req (*Foo) // 不支持除普通結構體外的其他數(shù)據(jù)類型作為請求體
@handler replyTest
post /foo/reply returns (*Foo) // 不支持除普通結構體、數(shù)組(向前兼容,后續(xù)考慮廢棄)外的其他數(shù)據(jù)類型作為響應體
}
隱藏通道目前主要為空白符號、換行符號以及注釋,這里我們只說注釋,因為空白符號和換行符號我們目前拿來也無用。
'//' ~[\r\n]*
由語法定義可知道,單行注釋必須要以//開頭,內(nèi)容為不能包含換行符
// doc
// comment
// break
line comments
'/*' .*? '*/'
由語法定義可知道,單行注釋必須要以/*開頭,*/結尾的任意字符。
/**
* java-style doc
*/
/*
* java-style doc */
*/
如果想獲取某一個元素的doc或者comment開發(fā)人員需要怎么定義?
我們規(guī)定上一個語法塊(非隱藏通道內(nèi)容)的行數(shù)line+1到當前語法塊第一個元素前的所有注釋(單行,或者多行)均為doc, 且保留了//、/*、*/原始標記。
我們規(guī)定當前語法塊最后一個元素所在行開始的一個注釋塊(當行,或者多行)為comment 且保留了//、/*、*/原始標記。 語法塊Doc和Comment的支持情況
語法塊 | parent語法塊 | Doc | Comment |
syntaxLit | api | ? | ? |
kvLit | infoSpec | ? | ? |
importLit | importSpec | ? | ? |
typeLit | api | ? | ? |
typeLit | typeBlock | ? | ? |
field | typeLit | ? | ? |
key-value | atServer | ? | ? |
atHandler | serviceRoute | ? | ? |
route | serviceRoute | ? | ? |
以下為對應語法塊解析后細帶doc和comment的寫法
// syntaxLit doc
syntax = "v1" // syntaxLit commnet
info(
// kvLit doc
author: songmeizi // kvLit comment
)
// typeLit doc
type Foo {}
type(
// typeLit doc
Bar{}
FooBar{
// filed doc
Name int // filed comment
}
)
@server(
/**
* kvLit doc
* 開啟jwt鑒權
*/
jwt: Auth /**kvLit comment*/
)
service foo-api{
// atHandler doc
@handler foo //atHandler comment
/*
* route doc
* post請求
* path為 /foo
* 請求體:Foo
* 響應體:Foo
*/
post /foo (Foo) returns (Foo) // route comment
}
更多建議: