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