The toolkit of Go kit.

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

  1. Code Generation Tool

    • Endpoint
    • Transport
      • HTTP
        • HTTP Server
        • HTTP Test
        • HTTP Client
        • OAS-v2 Documentation
      • gRPC
  2. 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.

  1. Define the interface

    type Service interface {
        SayHello(ctx context.Context, name string) (message string, err error)
    }
  2. Implement the service

    type Greeter struct{}
    
    func (g *Greeter) SayHello(ctx context.Context, name string) (string, error) {
        return "Hello " + name, nil
    }
  3. Add HTTP annotations

    type Service interface {
        // @kok(op): POST /messages
        SayHello(ctx context.Context, name string) (message string, err error)
    }
  4. Generate the HTTP code

    $ cd examples/helloworld
    $ kokgen ./service.go Service
  5. 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"
    }
  6. 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).
  • 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.
    • 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.
    • 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.
  • 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.
  • 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.
  • 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.

License

MIT

Comments
  • Rename this project

    Rename this project

    Motivation

    In some cultures, the pronunciation of kok may lead to misunderstanding. (See comments here and here.)

    New name

    The new name under consideration is kin, which means "Kin Is Not just a kit of Go kit".

    Other related names:

    • Module path
      • github.com/RussellLuo/kin
    • Code generation tool
      • kingen
    • Annotation directives
      • //kin:op
      • //kin:param
      • //kin:body
      • //kin:success
      • //kin:oas
      • //kin:alias
      • //kin:grpc
    • Struct tags
      • kin:"..."

    Tasks

    • Rename the repo
    • Update the code
    • Update the docs
  • Invalid literal error when generating code for an Interface

    Invalid literal error when generating code for an Interface

    Hi @RussellLuo thanks creating kun, great toolkit! i'm running into a (minor) issue with the generated returnErr when generating an http client.

    As an example, suppose I have this service with a method that contains an interface (i.e. Animal)

    package service 
    
    //go:generate kungen -force ./service.go Service
    type Service interface {
    	//kun:op GET /animal/{id}
    	Find(id string) (Animal, error)
    }
    
    type Animal interface {
    	IsAnimal()
    }
    

    After generating the code http_client.go will contain the following:

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
    	return Animal{}, err
    }
    

    returning Animal{} results in an InvalidLiteral error.

    If you're open to contributions I created a PR: https://github.com/RussellLuo/kun/pull/29 Feel free to give feedback, or let me know if you'd like to see tests for contributions:)

  • annotation: Add support for multiple HTTP operations

    annotation: Add support for multiple HTTP operations

    Sometimes, a Go method needs to correspond to more than one URI (or HTTP method). For example:

    type Service interface {
    	//kun:op GET /messages/{messageID}
    	//kun:op GET /users/{userID}/messages/{messageID}
    	GetMessage(ctx context.Context, userID string, messageID string) (text string, err error)
    }
    

    This will enable the following two alternative HTTP to Go Method mappings:

    HTTP | Go Method -- | -- GET /messages/123456 | GetMessage(ctx, "", "123456") GET /users/me/messages/123456 | GetMessage(ctx, "me", "123456")

    This feature is much like (and is partially inspired by) gRPC's additional_bindings option (also see Multiple URI bindings).

    This closes #25.

  • annotation: Add support for multiple HTTP operations

    annotation: Add support for multiple HTTP operations

    Sometimes, a Go method needs to correspond to more than one URI (or HTTP method). For example:

    type Service interface {
    	//kun:op GET /messages/{messageID}
    	//kun:op GET /users/{userID}/messages/{messageID}
    	GetMessage(ctx context.Context, userID string, messageID string) (text string, err error)
    }
    

    This will enable the following two alternative HTTP to Go-method mappings:

    HTTP | Go Method -- | -- GET /messages/123456 | GetMessage(ctx, "", "123456") GET /users/me/messages/123456 | GetMessage(ctx, "me", "123456")

    This feature is much like (and is partially inspired by) gRPC's additional_bindings option (also see Multiple URI bindings).

  • Minor syntax improvements for `//kok:param`

    Minor syntax improvements for `//kok:param`

    Syntax improvements

    Original syntax (after #18):

    • //kok:param <argName> [<parameter> [; <parameter2> [; ...]]]
      • parameter: in=<in> name=<name> required=<required> type=<type> descr=<descr>

    New syntax:

    • //kok:param <argName> [<parameter> [, <parameter2> [, ...]]]
      • parameter: in=<in> name=<name> required=<required> type=<type> descr=<descr>
    • NOTE: 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.

    Example

    After the above syntax improvements, here are the equivalent annotations:

    One binding per //kok:param

    type Service interface {
        //kok:op POST /users
        //kok:param name
        //kok:param age in=query
        //kok:param 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
    

    Multiple bindings in a single //kok:param

    type Service interface {
        //kok:op POST /users
        //kok:param name; age in=query; 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
    

    Multiple bindings in a single //kok:param (but using backslash for better readability)

    type Service interface {
        //kok:op POST /users
        //kok:param name; \
        //          age in=query; \
        //          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
    
  • Redesign the annotation syntax

    Redesign the annotation syntax

    Motivation

    1. Follow the Go conventions for comment directives (and this).
    2. Provide a unified annotation syntax for both comments and struct tags.
    3. Make it possible to apply Argument aggregation in a single-line annotation.
    4. Improve the comment syntax for manipulating HTTP request/response body fields.

    Proposed annotation syntax

    HTTP

    1. Define the HTTP operation

      • Directive: //kok:op

      • Arguments: <method> <pattern>

        • ...
      • Examples:

        type Service interface {
            //kok:op DELETE /users/{id}
            DeleteUser(ctx context.Context, id int) (err error)
        }
        
        // HTTP request:
        // $ http DELETE /users/101
        
    2. Define the HTTP request parameters

      • Directive: //kok:param
      • Arguments: <argName> [<parameter> [; <parameter2> [; ...]]]
        • parameter: in=<in> name=<name> required=<required> type=<type> descr=<descr>
          • ...
      • Examples:
        • 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:"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"`
          }
          
          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   // 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"`
          }
          
          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; in=request name=RemoteAddr
              Log(ctx context.Context, ip net.IP) (err error)
          }
          
          // The equivalent annotations =>
          // (using backslash `\` for line continuation)
          
          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
          
    3. Define the HTTP request body

      • Directive: //kok:body
      • Arguments: <field> or <manipulation> [; <manipulation2> [; ...]]
        • manipulation: <argName> name=<name> type=<type> descr=<descr>
          • ...
      • Examples:
        • 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
              Age     int
              Hobbies []string `kok:"name=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
          
    4. Define the success HTTP response

      • Directive: //kok:success

      • Arguments: statusCode=<statusCode> body=<body> manip=`<manipulation> [; <manipulation2> [; ...]]`

        • manipulation: <argName> name=<name> type=<type> descr=<descr>
          • ...
      • Examples:

        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)
        }
        
    5. Define the OAS metadata

      • Directive: //kok:oas

      • Arguments: <property>=<value>

      • Examples:

        // 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)
        }
        
    6. Define the annotation alias

      • Directive: //kok:alias

      • Arguments: <name>=`<value>`

      • Examples:

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

    gRPC

    • Directive: //kok:grpc
    • Arguments: request=<request> response=<response>
    • Examples:
      • Omitted:

        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
        
      • Specified:

        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
        
  • Improve the interface parser

    Improve the interface parser

    Since the underlying interface parser is borrowed from the old version of moq, kokgen has the same legacy issues as moq:

    • https://github.com/matryer/moq/issues/68
    • https://github.com/matryer/moq/issues/94

    In order to address these issues, we must improve the interface parser by leveraging the new version of moq (thanks to https://github.com/matryer/moq/pull/141).

    Since the core packages for interface parsing in moq are internal, we have to copy the code from moq instead.

  • Add support for using blank identifiers in `@kok(param)`

    Add support for using blank identifiers in `@kok(param)`

    Problem

    Sometimes, an HTTP request parameter is only used in the Transport/Endpoint layer, and no further argument binding (in the Service layer) is needed.

    Taking the following GetUser operation as an example:

    type Service interface {
    	// @kok(op): GET /users/{id}
    	// @kok(success): body:user
    	GetUser(ctx context.Context, id int) (user User, err error)
    }
    

    In order to protect the operation, we may implement service-to-service authentication via the Authorization header. Meanwhile, there is no corresponding argument in GetUser to be mapped to.

    So how to represent this Authorization header in kok's HTTP annotation?

    Proposed Solution

    Add support for the blank identifier __ (double underscore, similar to Go's blank identifier) in @kok(param):

    type Service interface {
    	// @kok(op): GET /users/{id}
    	// @kok(param): __ < in:header,name:Authorization,required:true
    	// @kok(success): body:user
    	GetUser(ctx context.Context, id int) (user User, err error)
    }
    

    And here is the possible codec.go for the Authorization header:

    import (
    	"fmt"
    
    	"github.com/RussellLuo/kok/pkg/codec/httpcodec"
    )
    
    const (
    	authPrivateKey = "AUTH_PRIVATE_KEY"
    )
    
    // AuthCodec is used to encode and decode the `Authorization` header. It can be reused wherever needed.
    type AuthCodec struct{}
    
    func (c AuthCodec) Decode(in []string, out interface{}) error {
    	// NOTE: never use out, which is nil here.
    
    	if len(in) == 0 || in[0] != authPrivateKey {
    		return fmt.Errorf("authentication failed")
    	}
    	return nil
    }
    
    func (c AuthCodec) Encode(in interface{}) (out []string) {
    	// NOTE: never use in, which is nil here.
    
    	return []string{authPrivateKey}
    }
    
    func NewCodecs() *httpcodec.DefaultCodecs {
    	// Use AuthCodec to encode and decode the argument named "__", if exists,
    	// for the operation named "GetUser".
    	return httpcodec.NewDefaultCodecs(nil,
    		httpcodec.Op("GetUser", httpcodec.NewPatcher(httpcodec.JSON{}).Param("__", AuthCodec{})))
    }
    

    Multiple blank identifiers

    In complicated cases, where multiple blank identifiers are involved and need to be differentiated from one another (typically for applying different encoders and decoders), we can use meaningful names, each with a double underscore prefix __ (e.g. __auth).

    In summary, any name with a double underscore prefix __ is a valid blank identifier:

    • __
    • __auth
    • __other
  • Add support for the gRPC transport

    Add support for the gRPC transport

    • [x] Generate the .proto file from the service definition in Go
    • [x] Compile the .proto file to the gRPC definition
    • [x] Adapt the gRPC definition to Go kit
  • Add support for `@kok(alias)`

    Add support for `@kok(alias)`

    Lengthy

    type User interface {
        // @kok(op): POST /users
        // @kok(param): operatorID < in:header,name:Authorization,required:true
        Create(ctx context.Context, operatorID int) (err error)
    
        // @kok(op): GET /users/{id}
        // @kok(param): operatorID < in:header,name:Authorization,required:true
        Read(ctx context.Context, id, operatorID int) (err error)
    
        // @kok(op): PUT /users/{id}
        // @kok(param): operatorID < in:header,name:Authorization,required:true
        Update(ctx context.Context, id, operatorID int) (err error)
    
        // @kok(op): DELETE /users/{id}
        // @kok(param): operatorID < in:header,name:Authorization,required:true
        Delete(ctx context.Context, id, operatorID int) (err error)
    }
    

    Simplified

    // @kok(alias): auth=`in:header,name:Authorization,required:true`
    type User interface {
        // @kok(op): POST /users
        // @kok(param): operatorID < $auth
        Create(ctx context.Context, operatorID int) (err error)
    
        // @kok(op): GET /users/{id}
        // @kok(param): operatorID < $auth
        Read(ctx context.Context, id, operatorID int) (err error)
    
        // @kok(op): PUT /users/{id}
        // @kok(param): operatorID < $auth
        Update(ctx context.Context, id, operatorID int) (err error)
    
        // @kok(op): DELETE /users/{id}
        // @kok(param): operatorID < $auth
        Delete(ctx context.Context, id, operatorID int) (err error)
    }
    

    Or

    // @kok(alias): opID=`operatorID < in:header,name:Authorization,required:true`
    type User interface {
        // @kok(op): POST /users
        // @kok(param): $opID
        Create(ctx context.Context, operatorID int) (err error)
    
        // @kok(op): GET /users/{id}
        // @kok(param): $opID
        Read(ctx context.Context, id, operatorID int) (err error)
    
        // @kok(op): PUT /users/{id}
        // @kok(param): $opID
        Update(ctx context.Context, id, operatorID int) (err error)
    
        // @kok(op): DELETE /users/{id}
        // @kok(param): $opID
        Delete(ctx context.Context, id, operatorID int) (err error)
    }
    
  • Add support for request validation

    Add support for request validation

    Take helloworld as an example, this is the original construction code:

    https://github.com/RussellLuo/kok/blob/1a22aefc486c8250d788a4d36012af5b391425ed/examples/helloworld/cmd/main.go#L22

    If we want to add request validation for the SayHello operation, we can change the above code as follows:

    1. Validation rules written in Go

    validators := []httpoption.NamedValidator{
        httpoption.Op("SayHello", httpoption.FuncValidator(func(value interface{}) error {
            req := value.(*helloworld.SayHelloRequest)
            if len(req.Name) > 10 {
                return werror.Wrapf(gcode.ErrInvalidArgument, "name: length exceeds 10")
            }
    
            reVarName := regexp.MustCompile(`^\w+$`)
            if !reVarName.MatchString(req.Name) {
                return werror.Wrapf(gcode.ErrInvalidArgument, "name: invalid name format")
            }
    
            return nil
        })),
    }
    r := helloworld.NewHTTPRouter(svc, httpcodec.NewDefaultCodecs(nil), httpoption.RequestValidators(validators...))
    

    2. Validation rules written in DSL of validating

    validators := []httpoption.NamedValidator{
        httpoption.Op("SayHello", helloworld.ValidateSayHelloRequest(func(req *helloworld.SayHelloRequest) v.Schema {
            return v.Schema{
                v.F("name", &req.Name): v.All(
                    v.Len(0, 10).Msg("length exceeds 10"),
                    v.Match(regexp.MustCompile(`^\w+$`)).Msg("invalid name format"),
                ),
            }
        })),
    }
    r := helloworld.NewHTTPRouter(svc, httpcodec.NewDefaultCodecs(nil), httpoption.RequestValidators(validators...))
    

    NOTE:

    • The ValidateSayHelloRequest() function is designed to be generated by kokgen
    • The validation of each validator will be triggered in its corresponding DecodeRequestFunc (https://github.com/go-kit/kit/issues/908#issuecomment-526874228)
  • Async communication

    Async communication

    i really like the approach this framework takes of using annotations.

    i would like to try to add async communication generators .

    i use NATS quite a bit . It’s a message bus and broker .

    https://github.com/nats-io/nats.go

    It has HTTP / web socket and tcp transports built in.

    So for iot people use the tcp layer . But for web they use the http and web sockets .

    Nats is globally clustered , self load balancing and has build in security. It also has a clustered kv that replaced etc and consul. So it remove the need for a lot of other layers typically needed for global internet scale systems engineering .

    It’s 100% golang and runs on anything .

    It can do RPC and PUB SUB also.

    You can use protobufs, json , anything you want serialisation . It does not care .

    I don’t know how familiar you are with NATS . But just let me know if this is something if interest.

    i currently use it in iot, web, desktop and servers .

    —-

    there is a standard approach to async communications that is emerging also called “async api”.

    https://www.asyncapi.com/

    It’s like openapi but for async.

    it also has generators but like openapi takes the approach of you first writing your api description in json or yaml and then code generating from that .

    it’s quite possible however to generate an async api document using kok !!!! This I think would be pretty cool because then others can generate the client in whatever language they want .

    https://github.com/orgs/asyncapi/repositories?q=&type=&language=go&sort=

Tpf2-tpnetmap-toolkit - A toolkit to create svg map images from TransportFever2 world data
Tpf2-tpnetmap-toolkit - A toolkit to create svg map images from TransportFever2 world data

tpf2-tpnetmap-toolkit TransportFever2 のワールドデータから svg のマップ画像を作成するツールキットです。 1. 導入方

Feb 17, 2022
An idiomatic Go REST API starter kit (boilerplate) following the SOLID principles and Clean Architecture

Go RESTful API Starter Kit (Boilerplate) This starter kit is designed to get you up and running with a project structure optimized for developing REST

Jan 3, 2023
This is an example to demonstrate implementation golang microservices using domain driven design principles and sugestions from go-kit

go-kit DDD Domain Driven Design is prevelent and rising standard for organizing your microservice code. This design architecture emphasis on Code orga

Feb 9, 2022
This tool generates Go language bindings of services in protobuf definition files for go-kit

protoc-gen-go-kit This tool generates Go language bindings of services in protobuf definition files for go-kit. Installation $ go install github.com/x

Nov 9, 2021
Astro Starter Kit: Docs Site

Astro Starter Kit: Docs Site npm init astro -- --template docs Features ✅ Full Markdown support ✅ Responsive mobile-friendly design ✅ Sidebar navigat

Apr 10, 2022
Protoc plugin used to generate go-kit grpc code

protoc-gen-gokit-endpoint protoc plugin used to generate go-kit grpc code 安装 go install github.com/wwbweibo/protoc-gen-gokit-endpoint/cmd/protoc-gen-g

Sep 29, 2022
Protoc plugin used to generate go-kit grpc code

protoc-gen-gokit-endpoint protoc plugin used to generate go-kit grpc code 安装 go

Sep 29, 2022
⚔ Personal Golang starter kit with an engineer research perspective, expressjs developer friendly, and aims for rapid app development.
⚔ Personal Golang starter kit with an engineer research perspective, expressjs developer friendly, and aims for rapid app development.

Goku (WIP; Author Only) ⚔ Personal Golang starter kit with an engineer research perspective, expressjs developer friendly, and aims for rapid app deve

Jan 6, 2022
Huawei-push-authorizator - Huawei Push Kit authorizator in time

huawei-push-authorizator Huawei Push Kit authorizator in time Why? To send push

Jan 3, 2022
Netkit - A type parameter(generics) net kit, support tcp kcp, customize packet

Netkit Netkit is a type parameter(generics) golang package Get Started Need Go i

Jan 12, 2022
Awpark - Development kit via Alfred Workflow
Awpark - Development kit via Alfred Workflow

AWPark Alfred Workflow for engineer. Alfred Workflow Store Search and install Wo

Oct 26, 2022
This project implements p11-kit RPC server protocol, allowing Go programs to act as a PKCS #11 module without the need for cgo

PKCS #11 modules in Go without cgo This project implements p11-kit RPC server protocol, allowing Go programs to act as a PKCS #11 module without the n

Nov 30, 2022
Realworld implementation written with Go using go-kit!
Realworld implementation written with Go using go-kit!

This project is an implementation of the RealWorld project using Go utilizing go-kit as an application framework. The project heavily utilizes Docker

Dec 28, 2022
Concurrent task runner, developer's routine tasks automation toolkit. Simple modern alternative to GNU Make 🧰
Concurrent task runner, developer's routine tasks automation toolkit. Simple modern alternative to GNU Make 🧰

taskctl - concurrent task runner, developer's routine tasks automation toolkit Simple modern alternative to GNU Make. taskctl is concurrent task runne

Dec 14, 2022
A memcached binary protocol toolkit for go.

gomemcached This is a memcached binary protocol toolkit in go. It provides client and server functionality as well as a little sample server showing h

Nov 9, 2022
Building powerful interactive prompts in Go, inspired by python-prompt-toolkit.
Building powerful interactive prompts in Go, inspired by python-prompt-toolkit.

go-prompt A library for building powerful interactive prompts inspired by python-prompt-toolkit, making it easier to build cross-platform command line

Jan 3, 2023
PostgreSQL driver and toolkit for Go

pgx - PostgreSQL Driver and Toolkit pgx is a pure Go driver and toolkit for PostgreSQL. pgx aims to be low-level, fast, and performant, while also ena

Jan 4, 2023
Now is a time toolkit for golang

Now Now is a time toolkit for golang Install go get -u github.com/jinzhu/now Usage Calculating time based on current time import "github.com/jinzhu/n

Dec 23, 2022
Image processing library and rendering toolkit for Go.

blend Image processing library and rendering toolkit for Go. (WIP) Installation: This library is compatible with Go1. go get github.com/phrozen/blend

Nov 11, 2022
gui toolkit for go

Simple GUI toolkit with go Install You will need to install gtk-dev files. go get github.com/zozor/hgui The idea This is a gui toolkit that relies on

Feb 14, 2021