api语法介绍

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语法大版本升级导致前后不能向前兼容

**[!WARNING] 被import的api必须要和main api的syntax版本一致。

语法定义

  1. '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版本

正确语法示例

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. )

错误语法示例

eg:

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

info语法块

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

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

语法定义

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

语法说明

info:固定token,标志一个info语法块的开始

checkKeyValue:自定义go方法,检测VALUE是否为一个合法值。

VALUE:key对应的值,可以为单行的除’\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(
  2. foo value
  3. )

eg3:key-value没有换行

  1. info(foo:"value")

eg4:没有key

  1. info(
  2. : "value"
  3. )

eg5:非法的key

  1. info(
  2. 12: "value"
  3. )

eg6:移除旧版本多行语法

  1. info(
  2. foo: >
  3. some text
  4. <
  5. )

type语法块

在api服务中,我们需要用到一个结构体(类)来作为请求体,响应体的载体,因此我们需要声明一些结构体来完成这件事情, type语法块由golang的type演变而来,当然也保留着一些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 `json:"fooBar"`
  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 `json:"fooBar"`
  11. }
  12. )

错误语法示例

eg

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

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

  • 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”
    header HTTP header,如 Name: value go-zero request header:”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,handler和route

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: 可以有多个’-‘join的ID值

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

atServer关键key描述说明

修饰service时

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

修饰route时

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

正确语法示例

eg1:不规范写法

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

eg2:规范写法(推荐)

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

错误语法示例

  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. '/*' .*? '*/'

语法说明

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

正确语法示例

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

错误语法示例

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

Doc&Comment

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

Doc

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

Comment

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

语法块parent语法块DocComment
syntaxLitapi
kvLitinfoSpec
importLitimportSpec
typeLitapi
typeLittypeBlock
fieldtypeLit
key-valueatServer
atHandlerserviceRoute
routeserviceRoute

以下为对应语法块解析后细带doc和comment的写法

  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. }