kok
The toolkit of Go kit.
Zen
- Just write code for your business logic, generate everything else.
- Implement the service once, consume it in various ways (in-process function call or RPC).
Features
-
Code Generation Tool
- Endpoint
- Transport
- HTTP
- HTTP Server
- HTTP Test
- HTTP Client
- OAS-v2 Documentation
- gRPC
- HTTP
-
Useful Packages
- appx: Application framework for HTTP and CRON applications (a wrapper of appx).
- prometheus: Prometheus metrics utilities.
- trace: A thin wrapper of x/net/trace for Go kit.
- werror: Classified business errors.
Installation
$ go get -u github.com/RussellLuo/kok/cmd/kokgen
Usage
$ kokgen -h
kokgen [flags] source-file interface-name
-fmt
whether to make code formatted (default true)
-out string
output directory (default ".")
-pkg string
package name (default will infer)
-test string
the YAML file that provides test-cases for HTTP (default "./http.test.yaml")
-trace
whether to enable tracing
Quick Start
NOTE: The following code is located in helloworld.
-
Define the interface
type Service interface { SayHello(ctx context.Context, name string) (message string, err error) }
-
Implement the service
type Greeter struct{} func (g *Greeter) SayHello(ctx context.Context, name string) (string, error) { return "Hello " + name, nil }
-
Add HTTP annotations
type Service interface { // @kok(op): POST /messages SayHello(ctx context.Context, name string) (message string, err error) }
-
Generate the HTTP code
$ cd examples/helloworld $ kokgen ./service.go Service
-
Consume the service
Run the HTTP server:
$ go run cmd/main.go 2020/09/15 18:06:22 transport=HTTP addr=:8080
Consume by HTTPie:
$ http POST :8080/messages name=Tracey HTTP/1.1 200 OK Content-Length: 27 Content-Type: application/json; charset=utf-8 Date: Tue, 15 Sep 2020 10:06:34 GMT { "message": "Hello Tracey" }
-
See the OAS documentation
(Click to show details)
$ http GET :8080/api HTTP/1.1 200 OK Content-Length: 848 Content-Type: text/plain; charset=utf-8 Date: Tue, 15 Sep 2020 10:08:24 GMT swagger: "2.0" info: version: "1.0.0" title: "Swagger Example" description: "" license: name: "MIT" host: "example.com" basePath: "/api" schemes: - "https" consumes: - "application/json" produces: - "application/json" paths: /messages: post: description: "" operationId: "SayHello" parameters: - name: body in: body schema: $ref: "#/definitions/SayHelloRequestBody" produces: - application/json; charset=utf-8 responses: 200: description: "" schema: $ref: "#/definitions/SayHelloResponse" definitions: SayHelloRequestBody: type: object properties: name: type: string SayHelloResponse: type: object properties: message: type: string
See more examples here.
HTTP
Annotations
Define the HTTP request operation
-
Key:
@kok(op)
-
Value:
<method> <pattern>
- method: The request method
- pattern: The request URL
- NOTE: All variables (snake-case or camel-case) in pattern will automatically be bound to their corresponding method arguments (matches by name), as path parameters, if the variables are not specified as path parameters explicitly by
@kok(param)
.
- NOTE: All variables (snake-case or camel-case) in pattern will automatically be bound to their corresponding method arguments (matches by name), as path parameters, if the variables are not specified as path parameters explicitly by
-
Example:
type Service interface { // @kok(op): DELETE /users/{id} DeleteUser(ctx context.Context, id int) (err error) } // HTTP request: // $ http DELETE /users/101
Define the HTTP request parameters
- Key:
@kok(param)
- Value:
<argName> < in:<in>,name:<name>,required:<required>
- argName: The name of the method argument.
- Argument aggregation: By specifying the same argName, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).
- You do not need to repeat the argName, only the first one is required.
- Argument aggregation: By specifying the same argName, multiple request parameters (each one is of basic type or repeated basic type) can be aggregated into one method argument (of any type).
- in:
- path: The method argument is sourced from a path parameter.
- Optional: All variables (snake-case or camel-case) in pattern will automatically be bound to their corresponding method arguments (matches by name), as path parameters.
- query: The method argument is sourced from a query parameter.
- To receive values from a multi-valued query parameter, the method argument can be defined as a slice of basic type.
- header: The method argument is sourced from a header parameter.
- cookie: The method argument is sourced from a cookie parameter.
- Not supported yet.
- request: The method argument is sourced from a property of Go's http.Request.
- This is a special case, and only one property
RemoteAddr
is available now. - Note that parameters located in request have no relationship with OAS.
- This is a special case, and only one property
- path: The method argument is sourced from a path parameter.
- name: The name of the corresponding request parameter.
- Optional: Defaults to argName if not specified.
- required: Determines whether this parameter is mandatory.
- Optional: Defaults to
false
, if not specified. - If the parameter location is path, this property will be set to
true
internally, whether it's specified or not.
- Optional: Defaults to
- argName: The name of the method argument.
- Example:
-
Bind request parameters to simple arguments:
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) } // HTTP request: // $ http PUT /users/101 X-User-Name:tracey
-
Bind multiple request parameters to a struct according to tags:
type User struct { ID int `kok:"path.id"` Name string `kok:"query.name"` Age int `kok:"header.X-User-Age"` } type Service interface { // @kok(op): PUT /users/{id} // @kok(param): user UpdateUser(ctx context.Context, user User) (err error) } // HTTP request: // $ http PUT /users/101?name=tracey X-User-Age:1
-
Bind multiple query parameters to a struct with no tags:
type User struct { Name string Age int Hobbies []string } type Service interface { // @kok(op): POST /users // @kok(param): user CreateUser(ctx context.Context, user User) (err error) } // HTTP request: // $ http POST /users?Name=tracey&Age=1&Hobbies=music&Hobbies=sport
-
Argument aggregation:
type Service interface { // @kok(op): POST /logs // @kok(param): ip < in:header,name:X-Forwarded-For // @kok(param): ip < in:request,name:RemoteAddr Log(ctx context.Context, ip net.IP) (err error) } // The equivalent annotations. type Service interface { // @kok(op): POST /logs // @kok(param): ip < in:header,name:X-Forwarded-For // @kok(param): < 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 examples in the `Encoding and decoding` section. // HTTP request: // $ http POST /logs
-
Define the HTTP request body
- Key:
@kok(body)
- Value:
<field>
- 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.
- field: The name of the method argument whose value is mapped to the HTTP request body.
- Example:
-
Omitted:
type Service interface { // @kok(op): POST /users CreateUser(ctx context.Context, name string, age int) (err error) } // HTTP request: // $ http POST /users name=tracey age=1
-
Specified as a normal argument:
type User struct { Name string `json:"name"` Age int `json:"age"` } type Service interface { // @kok(op): POST /users // @kok(body): user CreateUser(ctx context.Context, user User) (err error) } // HTTP request: // $ http POST /users name=tracey age=1
-
Specified as
-
:type User struct { Name string `kok:"name"` Age int `kok:"age"` Hobbies []string `kok:"hobby"` } type Service interface { // @kok(op): POST /users // @kok(body): - CreateUser(ctx context.Context, user User) (err error) } // HTTP request: // $ http POST /users?name=tracey&age=1&hobby=music&hobby=sport
-
Define the success HTTP response
-
Key:
@kok(success)
-
Value:
statusCode:<statusCode>,body:<body>
- 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.
- statusCode: The status code of the success HTTP response.
-
Example:
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) }
Encoding and decoding
See the HTTP Codec interface.
Take the IP decoding
mentioned above as an example, we can get the real IP by customizing the built-in httpcodec.JSON:
// codec.go
import (
"net"
"github.com/RussellLuo/kok/pkg/codec/httpcodec"
)
type Codec struct {
httpcodec.JSON
}
func (c Codec) DecodeRequestParams(name string, values map[string][]string, out interface{}) error {
switch name {
case "ip":
// We are decoding the "ip" argument.
remote := values["request.RemoteAddr"][0]
if fwdFor := values["header.X-Forwarded-For"][0]; fwdFor != "" {
remote = strings.TrimSpace(strings.Split(fwdFor, ",")[0])
}
ipStr, _, err := net.SplitHostPort(remote)
if err != nil {
ipStr = remote // OK; probably didn't have a port
}
ip := net.ParseIP(ipStr)
if ip == nil {
return fmt.Errorf("invalid client IP address: %s", ipStr)
}
outIP := out.(*net.IP)
*outIP = ip
return nil
default:
// Use the JSON codec for other arguments.
return c.JSON.DecodeRequestParams(name, values, out)
}
}
For how to use the customized Codec implementation in your HTTP server, see the profilesvc example.
OAS Schema
See the OAS Schema interface.
Documentation
Checkout the Godoc.