diff --git a/README.md b/README.md
index 20b5af3..e4a0537 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,8 @@ kok (pronounced keɪ-oʊ-keɪ) is a toolkit of [Go kit][1].
- [x] Transport
- [x] HTTP
+ [x] HTTP Server
- + [x] HTTP Test
+ [x] HTTP Client
+ + [x] HTTP Test
+ [x] [OAS-v2][2] Documentation
- [x] gRPC
+ [x] gRPC Server
@@ -278,24 +278,31 @@ See more examples [here](examples).
- //kok:op
+ Directive //kok:op
-- Directive: `//kok:op`
-- Arguments: ` `
- + **method**: The request method.
- + **pattern**: The request URL.
- - NOTE: All variables in **pattern** will automatically be bound to their corresponding method arguments (match by names in *lower camel case*), as **path** parameters, if these variables have not yet been specified explicitly by `//kok:param`.
-- Examples:
+##### Syntax
- ```go
- type Service interface {
- //kok:op DELETE /users/{id}
- DeleteUser(ctx context.Context, id int) (err error)
- }
+```
+//kok:op
+```
- // HTTP request:
- // $ http DELETE /users/101
- ```
+##### Arguments
+
+- **method**: The request method.
+- **pattern**: The request URL.
+ + NOTE: All variables in **pattern** will automatically be bound to their corresponding method arguments (match by names in *lower camel case*), as **path** parameters, if these variables have not yet been specified explicitly by `//kok:param`.
+
+##### Examples
+
+```go
+type Service interface {
+ //kok:op DELETE /users/{id}
+ DeleteUser(ctx context.Context, id int) (err error)
+}
+
+// HTTP request:
+// $ http DELETE /users/101
+```
@@ -405,15 +412,24 @@ See more examples [here](examples).
- //kok:param
+ Directive //kok:param
-- Directive: `//kok:param`
-- Arguments: ` [ [; [; ...]]]`
- + **argName**: The name of the method argument.
- - *Argument aggregation*: By specifying multiple ``s, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).
- - *Blank identifier*: By specifying the **argName** with a double underscore prefix `__`, the corresponding request parameter(s) will not be mapped to any method argument. See [here](https://github.com/RussellLuo/kok/issues/15) for more details.
- + **parameter**: The definition of a single request parameter, to which the method argument will be mapped.
- - `in= name= required= type= descr=`
+##### Syntax
+
+```
+//kok:param [ [, [, ...]]]
+```
+
+If multiple method arguments are involved, you may need to apply multiple bindings. This can be done by adding a new `//kok:param` directive, or by appending the binding to the end of the last `//kok:param` directive in a semicolon-separated list.
+
+##### Arguments
+
+- **argName**: The name of the method argument.
+ + *Argument aggregation*: By specifying multiple ``s in a comma-separated list, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).
+ + *Blank identifier*: By specifying the **argName** with a double underscore prefix `__`, the corresponding request parameter(s) will not be mapped to any method argument. See [here](https://github.com/RussellLuo/kok/issues/15) for more details.
+- **parameter**: The definition of a single request parameter, to which the method argument will be mapped.
+ + Syntax: `in= name= required= type= descr=`
+ + Options:
- **in**:
+ **path**: The request parameter is a [path parameter](https://swagger.io/docs/specification/describing-parameters/#path-parameters).
- Optional: All variables in **pattern** will automatically be bound to their corresponding method arguments (match by names in *lower camel case*), as **path** parameters.
@@ -421,7 +437,6 @@ See more examples [here](examples).
- To receive values from a multi-valued query parameter, the method argument can be defined as a slice of basic type.
+ **header**: The request parameter is a [header parameter](https://swagger.io/docs/specification/describing-parameters/#header-parameters).
- To receive values from a multi-valued header parameter, the method argument can be defined as a slice of basic type.
-
+ **cookie**: The request parameter is a [cookie parameter](https://swagger.io/docs/specification/describing-parameters/#cookie-parameters).
- Not supported yet.
+ **request**: The request parameter is a property of Go's [http.Request](https://golang.org/pkg/net/http/#Request).
@@ -436,79 +451,108 @@ See more examples [here](examples).
+ Optional: Defaults to the type of the method argument, if not specified.
- **descr**: The OAS description of the request parameter.
+ Optional: Defaults to `""`, if not specified.
-- Examples:
- + Bind request parameters to simple arguments:
+
+##### Examples
- ```go
- type Service interface {
- //kok:op PUT /users/{id}
- //kok:param name in=header name=X-User-Name
- UpdateUser(ctx context.Context, id int, name string) (err error)
- }
+- Bind request parameters to simple arguments:
- // HTTP request:
- // $ http PUT /users/101 X-User-Name:tracey
- ```
- + Bind multiple request parameters to a struct according to tags:
+ ```go
+ type Service interface {
+ //kok:op PUT /users/{id}
+ //kok:param name in=header name=X-User-Name
+ UpdateUser(ctx context.Context, id int, name string) (err error)
+ }
- ```go
- type User struct {
- ID int `kok:"in=path"` // name defaults to snake case `id`
- Name string `kok:"in=query"` // name defaults to snake case `name`
- Age int `kok:"in=header name=X-User-Age"`
- }
+ // HTTP request:
+ // $ http PUT /users/101 X-User-Name:tracey
+ ```
+
+- Bind multiple request parameters to a struct according to tags:
- type Service interface {
- //kok:op PUT /users/{id}
- //kok:param user
- UpdateUser(ctx context.Context, user User) (err error)
- }
+ ```go
+ type User struct {
+ ID int `kok:"in=path"` // name defaults to snake case `id`
+ Name string `kok:"in=query"` // name defaults to snake case `name`
+ Age int `kok:"in=header name=X-User-Age"`
+ }
- // HTTP request:
- // $ http PUT /users/101?name=tracey X-User-Age:1
- ```
- + Bind multiple query parameters to a struct with no tags:
+ type Service interface {
+ //kok:op PUT /users/{id}
+ //kok:param user
+ UpdateUser(ctx context.Context, user User) (err error)
+ }
- ```go
- type User struct {
- Name string // equivalent to `kok:"in=query name=name"`
- Age int // equivalent to `kok:"in=query name=age"`
- Hobbies []string // equivalent to `kok:"in=query name=hobbies"`
- }
+ // HTTP request:
+ // $ http PUT /users/101?name=tracey X-User-Age:1
+ ```
+
+- Bind multiple query parameters to a struct with no tags:
- type Service interface {
- //kok:op POST /users
- //kok:param user
- CreateUser(ctx context.Context, user User) (err error)
- }
+ ```go
+ type User struct {
+ Name string // equivalent to `kok:"in=query name=name"`
+ Age int // equivalent to `kok:"in=query name=age"`
+ Hobbies []string // equivalent to `kok:"in=query name=hobbies"`
+ }
- // HTTP request:
- // $ http POST /users?name=tracey&age=1&hobbies=music&hobbies=sport
- ```
- + Argument aggregation:
+ type Service interface {
+ //kok:op POST /users
+ //kok:param user
+ CreateUser(ctx context.Context, user User) (err error)
+ }
- ```go
- type Service interface {
- //kok:op POST /logs
- //kok:param ip in=header name=X-Forwarded-For; in=request name=RemoteAddr
- Log(ctx context.Context, ip net.IP) (err error)
- }
+ // HTTP request:
+ // $ http POST /users?name=tracey&age=1&hobbies=music&hobbies=sport
+ ```
- // The equivalent annotations =>
- // (using backslash-continued annotations)
- type Service interface {
- //kok:op POST /logs
- //kok:param ip in=header name=X-Forwarded-For; \
- // in=request name=RemoteAddr
- Log(ctx context.Context, ip net.IP) (err error)
- }
+- Argument aggregation:
- // You must customize the decoding of `ip` later (conventionally in another file named `codec.go`).
- // See a runnable example in examples/usersvc.
+ ```go
+ type Service interface {
+ //kok:op POST /logs
+ //kok:param ip in=header name=X-Forwarded-For, in=request name=RemoteAddr
+ Log(ctx context.Context, ip net.IP) (err error)
+ }
- // HTTP request:
- // $ http POST /logs
- ```
+ // The equivalent annotations =>
+ // (using backslash-continued annotations)
+ type Service interface {
+ //kok:op POST /logs
+ //kok:param ip in=header name=X-Forwarded-For, \
+ // in=request name=RemoteAddr
+ Log(ctx context.Context, ip net.IP) (err error)
+ }
+
+ // You must customize the decoding of `ip` later (conventionally in another file named `codec.go`).
+ // See a runnable example in examples/usersvc.
+
+ // HTTP request:
+ // $ http POST /logs
+ ```
+
+- Multiple bindings in a single `//kok:param`:
+
+ ```go
+ type Service interface {
+ //kok:op POST /users
+ //kok:param name; age; ip in=header name=X-Forwarded-For, in=request name=RemoteAddr
+ CreateUser(ctx context.Context, name string, age int, ip net.IP) (err error)
+ }
+
+ // The equivalent annotations =>
+ // (using backslash-continued annotations)
+
+ type Service interface {
+ //kok:op POST /users
+ //kok:param name; \
+ // age; \
+ // ip in=header name=X-Forwarded-For, in=request name=RemoteAddr
+ CreateUser(ctx context.Context, name string, age int, ip net.IP) (err error)
+ }
+
+ // HTTP request:
+ // $ http POST /users?name=tracey&age=1
+ ```
@@ -580,14 +624,28 @@ See more examples [here](examples).
- //kok:body
+ Directive //kok:body
-- Directive: `//kok:body`
-- Arguments: `` or ` [; [; ...]]`
- + **field**: The name of the method argument whose value is mapped to the HTTP request body.
- - Optional: When omitted, a struct containing all the arguments, which are not located in **path**/**query**/**header**, will automatically be mapped to the HTTP request body.
- - The special name `-` can be used, to define that there is no HTTP request body. As a result, every argument, which is not located in **path**/**query**/**header**, will automatically be mapped to one or more query parameters.
- + **manipulation**: ` name= type= descr=`
+##### Syntax
+
+```
+//kok:body
+```
+
+or
+
+```
+//kok:body [; [; ...]]
+```
+
+##### Arguments
+
+- **field**: The name of the method argument whose value is mapped to the HTTP request body.
+ + Optional: When omitted, a struct containing all the arguments, which are not located in **path**/**query**/**header**, will automatically be mapped to the HTTP request body.
+ + The special name `-` can be used, to define that there is no HTTP request body. As a result, every argument, which is not located in **path**/**query**/**header**, will automatically be mapped to one or more query parameters.
+- **manipulation**:
+ + Syntax: ` name= type= descr=`
+ + Options:
- **argName**: The name of the method argument to be manipulated.
- **name**: The name of the request parameter.
+ Optional: Defaults to **argName** (snake-case, or lower-camel-case if `-snake=false`) if not specified.
@@ -595,68 +653,70 @@ See more examples [here](examples).
+ Optional: Defaults to the type of the method argument, if not specified.
- **descr**: The OAS description of the request parameter.
+ Optional: Defaults to `""`, if not specified.
-- Examples:
- + Omitted:
+
+##### Examples
- ```go
- type Service interface {
- //kok:op POST /users
- CreateUser(ctx context.Context, name string, age int) (err error)
- }
+- Omitted:
- // HTTP request:
- // $ http POST /users name=tracey age=1
- ```
+ ```go
+ type Service interface {
+ //kok:op POST /users
+ CreateUser(ctx context.Context, name string, age int) (err error)
+ }
- + Specified as a normal argument:
+ // HTTP request:
+ // $ http POST /users name=tracey age=1
+ ```
- ```go
- type User struct {
- Name string `json:"name"`
- Age int `json:"age"`
- }
+- Specified as a normal argument:
- type Service interface {
- //kok:op POST /users
- //kok:body user
- CreateUser(ctx context.Context, user User) (err error)
- }
+ ```go
+ type User struct {
+ Name string `json:"name"`
+ Age int `json:"age"`
+ }
- // HTTP request:
- // $ http POST /users name=tracey age=1
- ```
+ type Service interface {
+ //kok:op POST /users
+ //kok:body user
+ CreateUser(ctx context.Context, user User) (err error)
+ }
- + Specified as `-`:
+ // HTTP request:
+ // $ http POST /users name=tracey age=1
+ ```
- ```go
- type User struct {
- Name string
- Age int
- Hobbies []string `kok:"name=hobby"`
- }
+- Specified as `-`:
- type Service interface {
- //kok:op POST /users
- //kok:body -
- CreateUser(ctx context.Context, user User) (err error)
- }
+ ```go
+ type User struct {
+ Name string
+ Age int
+ Hobbies []string `kok:"name=hobby"`
+ }
- // HTTP request:
- // $ http POST /users?name=tracey&age=1&hobby=music&hobby=sport
- ```
+ type Service interface {
+ //kok:op POST /users
+ //kok:body -
+ CreateUser(ctx context.Context, user User) (err error)
+ }
- + Manipulation:
+ // HTTP request:
+ // $ http POST /users?name=tracey&age=1&hobby=music&hobby=sport
+ ```
- ```go
- type Service interface {
- //kok:op POST /users
- //kok:body age name=user_age type=string descr=The-user-age
- CreateUser(ctx context.Context, name string, age int) (err error)
- }
+- Manipulation:
- // HTTP request:
- // $ http POST /users name=tracey user_age=1
- ```
+ ```go
+ type Service interface {
+ //kok:op POST /users
+ //kok:body age name=user_age type=string descr=The-user-age
+ CreateUser(ctx context.Context, name string, age int) (err error)
+ }
+
+ // HTTP request:
+ // $ http POST /users name=tracey user_age=1
+ ```
@@ -689,30 +749,38 @@ See more examples [here](examples).
- //kok:success
+ Directive //kok:success
-- Directive: `//kok:success`
-- Arguments: ``statusCode= body= manip=` [; [; ...]]` ``
- + **statusCode**: The status code of the success HTTP response.
- - Optional: Defaults to `200`, if not specified.
- + **body**: The name of the response field whose value is mapped to the HTTP response body.
- - Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the HTTP response body.
- + **manipulation**: ` name= type= descr=`
- - Not supported yet.
-- Examples:
+##### Syntax
- ```go
- type User struct {
- Name string `json:"name"`
- Age int `json:"age"`
- }
+```
+//kok:success statusCode= body= manip=` [; [; ...]]`
+```
- type Service interface {
- //kok:op POST /users
- //kok:success statusCode=201 body=user
- CreateUser(ctx context.Context) (user User, err error)
- }
- ```
+##### Arguments
+
+- **statusCode**: The status code of the success HTTP response.
+ + Optional: Defaults to `200`, if not specified.
+- **body**: The name of the response field whose value is mapped to the HTTP response body.
+ + Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the HTTP response body.
+- **manipulation**:
+ + Syntax: ` name= type= descr=`
+ + Not supported yet.
+
+##### Examples
+
+```go
+type User struct {
+ Name string `json:"name"`
+ Age int `json:"age"`
+}
+
+type Service interface {
+ //kok:op POST /users
+ //kok:success statusCode=201 body=user
+ CreateUser(ctx context.Context) (user User, err error)
+}
+```
@@ -753,36 +821,43 @@ See more examples [here](examples).
- //kok:oas
+ Directive //kok:oas
-- Directive: `//kok:oas`
-- Arguments: `=`
- + ``: The property to set. Supported properties:
- - **docsPath**: The URL path to the OAS documentation itself.
- + Optional: Defaults to `"/api"` if not specified.
- - **title**: The `title` field of Info Object, see [Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/).
- + Optional: Defaults to `"No Title"` if not specified.
- - **version**: The `version` field of Info Object, see [Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/).
- + Optional: Defaults to `"0.0.0"` if not specified.
- - **description**: The `description` field of Info Object, see [Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/).
- + Unavailable: Automatically extracted from the Go documentation of the interface definition.
- - **basePath**: The `basePath` property, see [API Host and Base URL](https://swagger.io/docs/specification/2-0/api-host-and-base-path/).
- - **tags**: A list of tags (comma-separated), see [Grouping Operations With Tags](https://swagger.io/docs/specification/2-0/grouping-operations-with-tags/).
- + ``: The value of the property.
-- Examples:
+##### Syntax
- ```go
- // This is the API documentation of User.
- //kok:oas docsPath=/api-docs
- //kok:oas title=User-API
- //kok:oas version=1.0.0
- //kok:oas basePath=/v1
- //kok:oas tags=user
- type Service interface {
- //kok:op POST /users
- CreateUser(ctx context.Context, name string, age int) (err error)
- }
- ```
+```
+//kok:oas =
+```
+
+##### Arguments
+
+- **property**: The property to set. Supported properties:
+ + **docsPath**: The URL path to the OAS documentation itself.
+ - Optional: Defaults to `"/api"` if not specified.
+ + **title**: The `title` field of Info Object, see [Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/).
+ - Optional: Defaults to `"No Title"` if not specified.
+ + **version**: The `version` field of Info Object, see [Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/).
+ - Optional: Defaults to `"0.0.0"` if not specified.
+ + **description**: The `description` field of Info Object, see [Basic Structure](https://swagger.io/docs/specification/2-0/basic-structure/).
+ - Unavailable: Automatically extracted from the Go documentation of the interface definition.
+ + **basePath**: The `basePath` property, see [API Host and Base URL](https://swagger.io/docs/specification/2-0/api-host-and-base-path/).
+ + **tags**: A list of tags (comma-separated), see [Grouping Operations With Tags](https://swagger.io/docs/specification/2-0/grouping-operations-with-tags/).
+- **value**: The value of the property.
+
+##### Examples
+
+```go
+// This is the API documentation of User.
+//kok:oas docsPath=/api-docs
+//kok:oas title=User-API
+//kok:oas version=1.0.0
+//kok:oas basePath=/v1
+//kok:oas tags=user
+type Service interface {
+ //kok:op POST /users
+ CreateUser(ctx context.Context, name string, age int) (err error)
+}
+```
@@ -825,38 +900,45 @@ See more examples [here](examples).
- //kok:alias
+ Directive //kok:alias
-- Directive: `//kok:alias`
-- Arguments: ``=`` ``
- + ``: The name of the alias.
- + ``: The string value that the alias represents.
-- Examples:
+##### Syntax
- ```go
- type Service interface {
- //kok:op POST /users
- //kok:param operatorID in=header name=Authorization required=true
- CreateUser(ctx context.Context, operatorID int) (err error)
+```
+//kok:alias =``
+```
- //kok:op DELETE /users/{id}
- //kok:param operatorID in=header name=Authorization required=true
- DeleteUser(ctx context.Context, id, operatorID int) (err error)
- }
+##### Arguments
- // The equivalent annotations =>
+- **name**: The name of the alias.
+- **value**: The string value that the alias represents.
+
+##### Examples
- //kok:alias opID=`operatorID in=header name=Authorization required=true`
- type Service interface {
- //kok:op POST /users
- //kok:param $opID
- CreateUser(ctx context.Context, operatorID int) (err error)
+```go
+type Service interface {
+ //kok:op POST /users
+ //kok:param operatorID in=header name=Authorization required=true
+ CreateUser(ctx context.Context, operatorID int) (err error)
- //kok:op DELETE /users/{id}
- //kok:param $opID
- DeleteUser(ctx context.Context, id, operatorID int) (err error)
- }
- ```
+ //kok:op DELETE /users/{id}
+ //kok:param operatorID in=header name=Authorization required=true
+ DeleteUser(ctx context.Context, id, operatorID int) (err error)
+}
+
+// The equivalent annotations =>
+
+//kok:alias opID=`operatorID in=header name=Authorization required=true`
+type Service interface {
+ //kok:op POST /users
+ //kok:param $opID
+ CreateUser(ctx context.Context, operatorID int) (err error)
+
+ //kok:op DELETE /users/{id}
+ //kok:param $opID
+ DeleteUser(ctx context.Context, id, operatorID int) (err error)
+}
+```
@@ -918,43 +1000,51 @@ See the [OAS Schema](https://github.com/RussellLuo/kok/blob/master/pkg/oasv2/sch
- //kok:grpc
+ Directive //kok:grpc
-- Directive: `//kok:grpc`
-- Arguments: `request= response=`
- + **request**: The name of the method argument, whose value will be mapped to the gRPC request.
- - Optional: When omitted, a struct containing all the arguments (except context.Context) will automatically be mapped to the gRPC request.
- + **response**: The name of the method result, whose value will be mapped to the gRPC response.
- - Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the gRPC response.
-- Examples:
- + Omitted:
+##### Syntax
- ```go
- type Service interface {
- //kok:grpc
- CreateUser(ctx context.Context, name string, age int) (err error)
- }
+```
+//kok:grpc request= response=
+```
- // gRPC request:
- // $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
- ```
+##### Arguments
- + Specified:
+- **request**: The name of the method argument, whose value will be mapped to the gRPC request.
+ + Optional: When omitted, a struct containing all the arguments (except context.Context) will automatically be mapped to the gRPC request.
+- **response**: The name of the method result, whose value will be mapped to the gRPC response.
+ + Optional: When omitted, a struct containing all the results (except error) will automatically be mapped to the gRPC response.
+
+##### Examples
- ```go
- type User struct {
- Name string `json:"name"`
- Age int `json:"age"`
- }
+- Omitted:
- type Service interface {
- //kok:grpc request=user
- CreateUser(ctx context.Context, user User) (err error)
- }
+ ```go
+ type Service interface {
+ //kok:grpc
+ CreateUser(ctx context.Context, name string, age int) (err error)
+ }
- // gRPC request:
- // $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
- ```
+ // gRPC request:
+ // $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
+ ```
+
+- Specified:
+
+ ```go
+ type User struct {
+ Name string `json:"name"`
+ Age int `json:"age"`
+ }
+
+ type Service interface {
+ //kok:grpc request=user
+ CreateUser(ctx context.Context, user User) (err error)
+ }
+
+ // gRPC request:
+ // $ grpcurl -d '{"name": "tracey", "age": 1}' ... pb.Service/CreateUser
+ ```
diff --git a/examples/usersvc/endpoint.go b/examples/usersvc/endpoint.go
index 9fced46..1683a64 100644
--- a/examples/usersvc/endpoint.go
+++ b/examples/usersvc/endpoint.go
@@ -6,7 +6,7 @@ package usersvc
import (
"context"
- "github.com/RussellLuo/kok/pkg/httpoption"
+ httpoption "github.com/RussellLuo/kok/pkg/httpoption2"
"github.com/RussellLuo/validating/v2"
"github.com/go-kit/kit/endpoint"
)
diff --git a/examples/usersvc/service.go b/examples/usersvc/service.go
index bdb5415..018daec 100644
--- a/examples/usersvc/service.go
+++ b/examples/usersvc/service.go
@@ -10,7 +10,7 @@ import (
type User struct {
Name string
Age int
- IP net.IP `kok:"in=header name=X-Forwarded-For; in=request name=RemoteAddr"`
+ IP net.IP `kok:"in=header name=X-Forwarded-For, in=request name=RemoteAddr"`
}
type Service interface {
diff --git a/gen/http/parser/annotation/annotation.go b/gen/http/parser/annotation/annotation.go
index 5507289..5b896a4 100644
--- a/gen/http/parser/annotation/annotation.go
+++ b/gen/http/parser/annotation/annotation.go
@@ -71,11 +71,13 @@ func ParseMethodAnnotation(method *ifacetool.Method) (*MethodAnnotation, error)
anno.Op = op
case "param":
- param, err := ParseParam(value)
+ params, err := ParseParams(value)
if err != nil {
return nil, err
}
- anno.Params[param.ArgName] = param
+ for _, p := range params {
+ anno.Params[p.ArgName] = p
+ }
case "body":
if anno.Body != nil {
diff --git a/gen/http/parser/annotation/body.go b/gen/http/parser/annotation/body.go
index 7379f73..67c387d 100644
--- a/gen/http/parser/annotation/body.go
+++ b/gen/http/parser/annotation/body.go
@@ -49,7 +49,7 @@ func ParseBody(s string) (*Body, error) {
break
}
- param, err := ParseParam(text)
+ param, err := parseParam(text)
if err != nil {
return nil, err
}
diff --git a/gen/http/parser/annotation/param.go b/gen/http/parser/annotation/param.go
index 4cd442c..3dfeaab 100644
--- a/gen/http/parser/annotation/param.go
+++ b/gen/http/parser/annotation/param.go
@@ -17,48 +17,64 @@ type Param struct {
Params []*spec.Parameter
}
-// ParseParam parses s per the format as below:
+// ParseParams parses s per the format as below:
//
-// [ [; [; ...]]]
+// [ [, [, ...]]]
//
// The format of ``:
//
// in= name= required= type= descr=
//
-func ParseParam(s string) (*Param, error) {
+// Multiple bindings can be specified in a single semicolon-separated comment.
+//
+func ParseParams(s string) ([]*Param, error) {
s = strings.TrimSpace(s)
if s == "" {
return nil, fmt.Errorf("empty //kok:param")
}
+ var list []*Param
+
+ for _, text := range strings.Split(s, ";") {
+ p, err := parseParam(text)
+ if err != nil {
+ return nil, err
+ }
+ list = append(list, p)
+ }
+
+ return list, nil
+}
+
+func parseParam(s string) (*Param, error) {
+ s = strings.TrimSpace(s)
+
r := reKokParam.FindStringSubmatch(s)
if len(r) != 3 {
return nil, fmt.Errorf("invalid directive arguments: %s", s)
}
- argName, remaining := r[1], r[2]
+ argName, remaining := r[1], strings.TrimSpace(r[2])
- p := &Param{
- ArgName: argName,
- }
+ p := &Param{ArgName: argName}
- if len(remaining) == 0 {
+ if remaining == "" {
// No remaining parameter definitions after the argument name.
return p, nil
}
- params, err := ParseParamParameters(argName, remaining)
+ opts, err := ParseParamOptions(argName, remaining)
if err != nil {
return nil, err
}
- p.Params = append(p.Params, params...)
+ p.Params = append(p.Params, opts...)
return p, nil
}
-func ParseParamParameters(argName, s string) ([]*spec.Parameter, error) {
+func ParseParamOptions(argName, s string) ([]*spec.Parameter, error) {
var params []*spec.Parameter
- for _, text := range strings.Split(s, ";") {
- param, err := parseParamParameter(argName, strings.TrimSpace(text))
+ for _, text := range strings.Split(s, ",") {
+ param, err := parseOption(argName, strings.TrimSpace(text))
if err != nil {
return nil, err
}
@@ -67,7 +83,7 @@ func ParseParamParameters(argName, s string) ([]*spec.Parameter, error) {
return params, nil
}
-func parseParamParameter(argName, s string) (*spec.Parameter, error) {
+func parseOption(argName, s string) (*spec.Parameter, error) {
s = strings.TrimSpace(s)
p := new(spec.Parameter)
diff --git a/gen/http/parser/annotation/param_test.go b/gen/http/parser/annotation/param_test.go
index f9007fc..04fee3e 100644
--- a/gen/http/parser/annotation/param_test.go
+++ b/gen/http/parser/annotation/param_test.go
@@ -8,62 +8,100 @@ import (
"github.com/RussellLuo/kok/gen/http/spec"
)
-func TestParseParam(t *testing.T) {
+func TestParseParams(t *testing.T) {
tests := []struct {
name string
in string
- wantOut *annotation.Param
+ wantOut []*annotation.Param
wantErrStr string
}{
{
- name: "simple argument",
+ name: "one binding one sub-parameter",
in: "name in=header name=X-User-Name required=true type=string descr=user-name",
- wantOut: &annotation.Param{
- ArgName: "name",
- Params: []*spec.Parameter{
- {
- In: spec.InHeader,
- Name: "X-User-Name",
- Required: true,
- Type: "string",
- Description: "user-name",
+ wantOut: []*annotation.Param{
+ {
+ ArgName: "name",
+ Params: []*spec.Parameter{
+ {
+ In: spec.InHeader,
+ Name: "X-User-Name",
+ Required: true,
+ Type: "string",
+ Description: "user-name",
+ },
},
},
},
},
{
- name: "argument aggregation",
- in: "ip in=header name=X-Forwarded-For; in=request name=RemoteAddr",
- wantOut: &annotation.Param{
- ArgName: "ip",
- Params: []*spec.Parameter{
- {
- In: spec.InHeader,
- Name: "X-Forwarded-For",
- },
- {
- In: spec.InRequest,
- Name: "RemoteAddr",
+ name: "one binding no sub-parameter",
+ in: "name",
+ wantOut: []*annotation.Param{
+ {
+ ArgName: "name",
+ },
+ },
+ },
+ {
+ name: "one binding multiple sub-parameters",
+ in: "ip in=header name=X-Forwarded-For, in=request name=RemoteAddr",
+ wantOut: []*annotation.Param{
+ {
+ ArgName: "ip",
+ Params: []*spec.Parameter{
+ {
+ In: spec.InHeader,
+ Name: "X-Forwarded-For",
+ },
+ {
+ In: spec.InRequest,
+ Name: "RemoteAddr",
+ },
},
},
},
},
{
- name: "no parameters",
- in: "name",
- wantOut: &annotation.Param{
- ArgName: "name",
+ name: "multiple bindings",
+ in: "name; age in=query; ip in=header name=X-Forwarded-For, in=request name=RemoteAddr",
+ wantOut: []*annotation.Param{
+ {
+ ArgName: "name",
+ },
+ {
+ ArgName: "age",
+ Params: []*spec.Parameter{
+ {
+ In: spec.InQuery,
+ },
+ },
+ },
+ {
+ ArgName: "ip",
+ Params: []*spec.Parameter{
+ {
+ In: spec.InHeader,
+ Name: "X-Forwarded-For",
+ },
+ {
+ In: spec.InRequest,
+ Name: "RemoteAddr",
+ },
+ },
+ },
},
},
{
- name: "in query by default",
+ name: "defaults in query",
in: "name required=true",
- wantOut: &annotation.Param{
- ArgName: "name",
- Params: []*spec.Parameter{
- {
- In: spec.InQuery,
- Required: true,
+ wantOut: []*annotation.Param{
+ {
+ ArgName: "name",
+ Params: []*spec.Parameter{
+ {
+ In: spec.InQuery,
+ Required: true,
+ },
},
},
},
@@ -82,12 +120,12 @@ func TestParseParam(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- param, err := annotation.ParseParam(tt.in)
+ list, err := annotation.ParseParams(tt.in)
if err != nil && err.Error() != tt.wantErrStr {
t.Fatalf("ErrStr: got (%#v), want (%#v)", err.Error(), tt.wantErrStr)
}
- if !reflect.DeepEqual(param, tt.wantOut) {
- t.Fatalf("Out: got (%#v), want (%#v)", param.Params[0], tt.wantOut.Params[0])
+ if !reflect.DeepEqual(list, tt.wantOut) {
+ t.Fatalf("Out: got (%#v), want (%#v)", list, tt.wantOut)
}
})
}
diff --git a/gen/http/parser/parser.go b/gen/http/parser/parser.go
index 536bf66..b310612 100644
--- a/gen/http/parser/parser.go
+++ b/gen/http/parser/parser.go
@@ -44,6 +44,8 @@ func Parse(data *ifacetool.Data, snakeCase bool) (*spec.Specification, []docutil
for _, m := range data.Methods {
doc := docutil.Doc(m.Doc).JoinComments()
+ m.Doc = doc // Replace the original doc with joined doc.
+
transport := doc.Transport()
if transport == 0 {
// Empty transport indicates that there are no kok annotations.
@@ -395,7 +397,7 @@ type StructField struct {
}
func (f *StructField) Parse() error {
- params, err := annotation.ParseParamParameters(f.Name, f.Tag.Get(tagName))
+ params, err := annotation.ParseParamOptions(f.Name, f.Tag.Get(tagName))
if err != nil {
return err
}
diff --git a/gen/util/docutil/docutil.go b/gen/util/docutil/docutil.go
index 5e30c50..0aa7b1d 100644
--- a/gen/util/docutil/docutil.go
+++ b/gen/util/docutil/docutil.go
@@ -39,7 +39,9 @@ func (d Doc) JoinComments() (joined Doc) {
continue
}
- c := incompleteComment + strings.TrimSpace(comment)
+ noPrefix := strings.TrimPrefix(comment, "//")
+ c := incompleteComment + strings.TrimSpace(noPrefix)
+
if HasContinuationLine(c) {
incompleteComment = strings.TrimSuffix(c, `\`)
} else {
diff --git a/gen/util/docutil/docutil_test.go b/gen/util/docutil/docutil_test.go
index 88bf518..52a9a52 100644
--- a/gen/util/docutil/docutil_test.go
+++ b/gen/util/docutil/docutil_test.go
@@ -17,23 +17,23 @@ func TestDoc_JoinLines(t *testing.T) {
name: "no backslash",
in: []string{
"//kok:op POST /logs",
- "//kok:param ip in=header name=X-Forwarded-For; in=request name=RemoteAddr",
+ "//kok:param ip in=header name=X-Forwarded-For, in=request name=RemoteAddr",
},
want: []string{
"//kok:op POST /logs",
- "//kok:param ip in=header name=X-Forwarded-For; in=request name=RemoteAddr",
+ "//kok:param ip in=header name=X-Forwarded-For, in=request name=RemoteAddr",
},
},
{
name: "has backslash",
in: []string{
"//kok:op POST /logs",
- `//kok:param ip in=header name=X-Forwarded-For; \`,
- " in=request name=RemoteAddr",
+ `//kok:param ip in=header name=X-Forwarded-For, \`,
+ "// in=request name=RemoteAddr",
},
want: []string{
"//kok:op POST /logs",
- "//kok:param ip in=header name=X-Forwarded-For; in=request name=RemoteAddr",
+ "//kok:param ip in=header name=X-Forwarded-For, in=request name=RemoteAddr",
},
},
}