api示例

  1. /**
  2. * api语法示例及语法说明
  3. */
  4. // api语法版本
  5. syntax = "v1"
  6. // import literal
  7. import "foo.api"
  8. // import group
  9. import (
  10. "bar.api"
  11. "foo/bar.api"
  12. )
  13. info(
  14. author: "songmeizi"
  15. date: "2020-01-08"
  16. desc: "api语法示例及语法说明"
  17. )
  18. // type literal
  19. type Foo{
  20. Foo int `json:"foo"`
  21. }
  22. // type group
  23. type(
  24. Bar{
  25. Bar int `json:"bar"`
  26. }
  27. )
  28. // service block
  29. @server(
  30. jwt: Auth
  31. group: foo
  32. )
  33. service foo-api{
  34. @doc "foo"
  35. @handler foo
  36. post /foo (Foo) returns (Bar)
  37. }

api语法结构

  • syntax语法声明
  • import语法块
  • info语法块
  • type语法块
  • service语法块
  • 隐藏通道

    Tip 在以上语法结构中,各个语法块从语法上来说,按照语法块为单位,可以在**.api**文件中任意位置声明, 但是为了提高阅读效率,我们建议按照以上顺序进行声明,因为在将来可能会通过严格模式来控制语法块的顺序。

syntax语法声明

syntax是新加入的语法结构,该语法的引入可以解决:

  • 快速针对api版本定位存在问题的语法结构
  • 针对版本做语法解析
  • 防止api语法大版本升级导致前后不能向前兼容

**

Warningimportapi必须要和main apisyntax版本一致。

语法定义

  1. 'syntax'={checkVersion(p)}STRING

语法说明
syntax:固定token,标志一个syntax语法结构的开始
checkVersion:自定义go方法,检测STRING是否为一个合法的版本号,目前检测逻辑为,STRING必须是满足(?m)”v[1-9][0-9]*”正则。

STRING:一串英文双引号包裹的字符串,如”v1
一个api语法文件只能有0或者1syntax语法声明,如果没有syntax,则默认为v1版本

正确语法示例
eg1:不规范写法

  1. syntax="v1"

eg2:规范写法(推荐)

  1. syntax = "v2"

错误语法示例
eg1

  1. syntax = "v0"

eg2:

  1. syntax = v1

eg3:

  1. syntax = "V1"

import语法块

随着业务规模增大,api中定义的结构体和服务越来越多,所有的语法描述均为一个api文件,这是多么糟糕的一个问题, 其会大大增加了阅读难度和维护难度,import语法块可以帮助我们解决这个问题,通过拆分api文件, 不同的api文件按照一定规则声明,可以降低阅读难度和维护难度。
**

Warning 这里import不像golang那样包含package声明,仅仅是一个文件路径的引入,最终解析后会把所有的声明都汇聚到一个spec.Spec中。 不能import多个相同路径,否则会解析错误。

语法定义

  1. 'import' {checkImportValue(p)}STRING
  2. |'import' '(' ({checkImportValue(p)}STRING)+ ')'

语法说明
import:固定token,标志一个import语法的开始

checkImportValue:自定义go方法,检测STRING是否为一个合法的文件路径,目前检测逻辑为,STRING必须是满足(?m)”(/?[a-zA-Z0-9_#-])+.api”正则。

STRING:一串英文双引号包裹的字符串,如”foo.api

正确语法示例
eg:

  1. import "foo.api"
  2. import "foo/bar.api"
  3. import(
  4. "bar.api"
  5. "foo/bar/foo.api"
  6. )

错误语法示例

  1. import foo.api
  2. import "foo.txt"
  3. import (
  4. bar.api
  5. bar.api
  6. )

info语法块

info语法块是一个包含了多个键值对的语法体,其作用相当于一个api服务的描述,解析器会将其映射到spec.Spec中, 以备用于翻译成其他语言(golangjava等) 时需要携带的meta元素。如果仅仅是对当前api的一个说明,而不考虑其翻译 时传递到其他语言,则使用简单的多行注释或者java风格的文档注释即可,关于注释说明请参考下文的 隐藏通道

**

Warning 不能使用重复的key,每个api文件只能有0或者1个info语法块

语法定义

  1. 'info' '(' (ID {checkKeyValue(p)}VALUE)+ ')'

语法说明
info:固定token,标志一个info语法块的开始
checkKeyValue:自定义go方法,检测VALUE是否为一个合法值。
VALUEkey对应的值,可以为单行的除**'\r','\n','/'**后的任意字符,多行请以""包裹,不过强烈建议所有都以””包裹

正确语法示例
eg1:不规范写法

  1. info(
  2. foo: foo value
  3. bar:"bar value"
  4. desc:"long long long long
  5. long long text"
  6. )

eg2:规范写法(推荐)

  1. info(
  2. foo: "foo value"
  3. bar: "bar value"
  4. desc: "long long long long long long text"
  5. )

错误语法示例
eg1:没有key-value内容

  1. info()

eg2:不包含冒号

  1. info( foo value )

eg3:key-value没有换行

  1. info(foo:"value")

eg4:没有key

  1. info( : "value" )

eg5:非法的key

  1. info( 12: "value" )

eg6:移除旧版本多行语法

  1. info( foo: > some text < )

type语法块

api服务中,我们需要用到一个结构体(类)来作为请求体,响应体的载体,因此我们需要声明一些结构体来完成这件事情, type语法块由golangtype演变而来,当然也保留着一些golang type的特性,沿用golang特性有:

  • 保留了golang内置数据类型bool,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr ,float32,float64,complex64,complex128,string,byte,rune,
  • 兼容golang struct风格声明
  • 保留golang关键字

**

Warning

  • 不支持alias
  • 不支持time.Time数据类型
  • 结构体名称、字段名称、不能为golang关键字

语法定义
由于其和golang相似,因此不做详细说明,具体语法定义请在 ApiParser.g4 中查看typeSpec定义。

语法说明
参考golang写法

正确语法示例
eg1:不规范写法

  1. type Foo struct{
  2. Id int `path:"id"` // ①
  3. Foo int `json:"foo"`
  4. }
  5. type Bar struct{
  6. // 非导出型字段
  7. bar int `form:"bar"`
  8. }
  9. type(
  10. // 非导出型结构体
  11. fooBar struct{
  12. FooBar int
  13. }
  14. )

eg2:规范写法(推荐)

  1. type Foo{
  2. Id int `path:"id"`
  3. Foo int `json:"foo"`
  4. }
  5. type Bar{
  6. Bar int `form:"bar"`
  7. }
  8. type(
  9. FooBar{
  10. FooBar int
  11. }
  12. )

错误语法示例
eg

  1. type Gender int // 不支持
  2. // 非struct token
  3. type Foo structure{
  4. CreateTime time.Time // 不支持time.Time
  5. }
  6. // golang关键字 var
  7. type var{}
  8. type Foo{
  9. // golang关键字 interface
  10. Foo interface
  11. }
  12. type Foo{
  13. foo int
  14. // map key必须要golang内置数据类型
  15. m map[Bar]string
  16. }

Note ① tag定义和golang中json tag语法一样,除了json tag外,go-zero还提供了另外一些tag来实现对字段的描述, 详情见下表。

  • 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” |

  • tag修饰符

常见参数校验描述

tag key 描述 提供方 有效范围 示例
optional 定义当前字段为可选参数 go-zero request json:”name,optional”
options 定义当前字段的枚举值,多个以竖线|隔开 go-zero request json:”gender,options=male”
default 定义当前字段默认值 go-zero request json:”gender,default=male”
range 定义当前字段数值范围 go-zero request json:”age,range=[0:120]”

Tip tag修饰符需要在tag value后以引文逗号,隔开

service语法块

service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。
**

Warning

  • main api和被import的api服务名称必须一致,不能出现服务名称歧义。
  • handler名称不能重复
  • 路由(请求方法+请求path)名称不能重复
  • 请求体必须声明为普通(非指针)struct,响应体做了一些向前兼容处理,详请见下文说明

语法定义

  1. serviceSpec: atServer? serviceApi;
  2. atServer: '@server' lp='(' kvLit+ rp=')';
  3. serviceApi: {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
  4. serviceRoute: atDoc? (atServer|atHandler) route;
  5. atDoc: '@doc' lp='('? ((kvLit+)|STRING) rp=')'?;
  6. atHandler: '@handler' ID;
  7. route: {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
  8. body: lp='(' (ID)? rp=')';
  9. replybody: lp='(' dataType? rp=')';
  10. // kv
  11. kvLit: key=ID {checkKeyValue(p)}value=LINE_VALUE;
  12. serviceName: (ID '-'?)+;
  13. path: (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;

语法说明
serviceSpec:包含了一个可选语法块atServerserviceApi语法块,其遵循序列模式(编写service必须要按照顺序,否则会解析出错)

atServer: 可选语法块,定义key-value结构的server metadata,’@server‘ 表示这一个server语法块的开始,其可以用于描述serviceApi或者route语法块,其用于描述不同语法块时有一些特殊关键key 需要值得注意,见 atServer关键key描述说明

serviceApi:包含了1到多个serviceRoute语法块

serviceRoute:按照序列模式包含了atDoc,handlerroute

atDoc:可选语法块,一个路由的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:路由,有httpMethodpath、可选request、可选response组成,httpMethod是必须是小写。

body:api请求体语法定义,必须要由**()**包裹的可选的ID

replyBody:api响应体语法定义,必须由()包裹的struct、array(向前兼容处理,后续可能会废弃,强烈推荐以struct包裹,不要直接用array作为响应体)

kvLit:info key-value

serviceName: 可以有多个‘-‘joinID

path:api请求路径,必须以'/'或者'/:'开头,切不能以'/'结尾,中间可包含ID或者多个以'-'joinID字符串

atServer关键key描述说明
修饰service

key 描述 示例
jwt 声明当前service下所有路由需要jwt鉴权,且会自动生成包含jwt逻辑的代码 jwt: Auth
group 声明当前service或者路由文件分组 group: login
middleware 声明当前service需要开启中间件 middleware: AuthMiddleware

修饰route

key 描述 示例
handler 声明一个handler -

正确语法示例
eg1:不规范写法

  1. @server(
  2. jwt: Auth
  3. group: foo
  4. middleware: AuthMiddleware
  5. )
  6. service foo-api{
  7. @doc(
  8. summary: foo
  9. )
  10. @server(
  11. handler: foo
  12. )
  13. // 非导出型body
  14. post /foo/:id (foo) returns (bar)
  15. @doc "bar"
  16. @handler bar
  17. post /bar returns ([]int)// 不推荐数组作为响应体
  18. @handler fooBar
  19. post /foo/bar (Foo) returns // 可以省略'returns'
  20. }

eg2:规范写法(推荐)

  1. @server(
  2. jwt: Auth
  3. group: foo
  4. middleware: AuthMiddleware
  5. )
  6. service foo-api{
  7. @doc "foo"
  8. @handler foo
  9. post /foo/:id (Foo) returns (Bar)
  10. }
  11. service foo-api{
  12. @handler ping
  13. get /ping
  14. @doc "foo"
  15. @handler bar
  16. post /bar/:id (Foo)
  17. }

错误语法示例

  1. // 不支持空的server语法块
  2. @server(
  3. )
  4. // 不支持空的service语法块
  5. service foo-api{
  6. }
  7. service foo-api{
  8. @doc kkkk // 简版doc必须用英文双引号引起来
  9. @handler foo
  10. post /foo
  11. @handler foo // 重复的handler
  12. post /bar
  13. @handler fooBar
  14. post /bar // 重复的路由
  15. // @handler和@doc顺序错误
  16. @handler someHandler
  17. @doc "some doc"
  18. post /some/path
  19. // handler缺失
  20. post /some/path/:id
  21. @handler reqTest
  22. post /foo/req (*Foo) // 不支持除普通结构体外的其他数据类型作为请求体
  23. @handler replyTest
  24. post /foo/reply returns (*Foo) // 不支持除普通结构体、数组(向前兼容,后续考虑废弃)外的其他数据类型作为响应体
  25. }

隐藏通道

隐藏通道目前主要为空白符号、换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。

单行注释

语法定义

  1. '//' ~[\r\n]*

语法说明 由语法定义可知道,单行注释必须要以//开头,内容为不能包含换行符

正确语法示例

  1. // doc
  2. // comment

错误语法示例

  1. // break
  2. line comments

java风格文档注释

语法定义

  1. // break
  2. line comments

语法说明
由语法定义可知道,单行注释必须要以/开头,/结尾的任意字符。

正确语法示例

  1. /**
  2. * java-style doc
  3. */

错误语法示例

  1. /*
  2. * java-style doc */
  3. */

Doc&Comment

如果想获取某一个元素的doc或者comment开发人员需要怎么定义?

Doc
我们规定上一个语法块(非隐藏通道内容)的行数line+1到当前语法块第一个元素前的所有注释(当行,或者多行)均为doc, 且保留了//、/*、*/原始标记。

Comment
我们规定当前语法块最后一个元素所在行开始的一个注释块(当行,或者多行)为comment 且保留了//、/*、*/原始标记。 语法块DocComment的支持情况

语法块 parent语法块 Doc Comment
syntaxLit api
kvLit infoSpec
importLit importSpec
typeLit api
typeLit typeBlock
field typeLit
key-value atServer
atHandler serviceRoute
route serviceRoute

以下为对应语法块解析后细带doccomment的写法

  1. // syntaxLit doc
  2. syntax = "v1" // syntaxLit commnet
  3. info(
  4. // kvLit doc
  5. author: songmeizi // kvLit comment
  6. )
  7. // typeLit doc
  8. type Foo {}
  9. type(
  10. // typeLit doc
  11. Bar{}
  12. FooBar{
  13. // filed doc
  14. Name int // filed comment
  15. }
  16. )
  17. @server(
  18. /**
  19. * kvLit doc
  20. * 开启jwt鉴权
  21. */
  22. jwt: Auth /**kvLit comment*/
  23. )
  24. service foo-api{
  25. // atHandler doc
  26. @handler foo //atHandler comment
  27. /*
  28. * route doc
  29. * post请求
  30. * path为 /foo
  31. * 请求体:Foo
  32. * 响应体:Foo
  33. */
  34. post /foo (Foo) returns (Foo) // route comment
  35. }

原文链接

https://go-zero.dev/cn/api-grammar.html