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