Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for patching existing codecs #8

Closed
RussellLuo opened this issue May 10, 2021 · 0 comments
Closed

Add support for patching existing codecs #8

RussellLuo opened this issue May 10, 2021 · 0 comments

Comments

@RussellLuo
Copy link
Owner

RussellLuo commented May 10, 2021

Motivation

Take the IP decoding mentioned in README.md as an example:

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

The existing solution is to implement a new codec:

// codec.go

import (
	"fmt"
	"net"
	"strings"

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

func NewCodecs() *httpcodec.DefaultCodecs {
	return httpcodec.NewDefaultCodecs(Codec{})
}

While the above solution is feasible, the custom encoding and decoding behavior here is so common that we should provide:

  • an easier way to customize codecs for request parameters
  • and better code reusability for custom codecs

Proposed Solution

Add support for patching existing codecs, which is shown as below:

// codec.go

import (
	"fmt"
	"net"
	"strings"

	"github.com/RussellLuo/kok/pkg/codec/httpcodec"
)

// IPCodec is used to encode and decode an IP. It can be reused wherever needed.
type IPCodec struct{}

func (c IPCodec) Decode(in map[string][]string, out interface{}) error {
	remote := in["request.RemoteAddr"][0]
	if fwdFor := in["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
}

func (c IPCodec) Encode(in interface{}) (out map[string][]string) {
	return nil
}

func NewCodecs() *httpcodec.DefaultCodecs {
	// Use IPCodec to encode and decode the argument named "ip", if exists,
	// for the operation named "Log".
	return httpcodec.NewDefaultCodecs(nil,
		httpcodec.Op("Log", httpcodec.NewPatcher(httpcodec.JSON{}).Params("ip", IPCodec{})))
}

// Another way to create the codecs.
func NewCodecs2() *httpcodec.DefaultCodecs {
	// Use IPCodec to encode and decode the argument named "ip", if exists,
	// for all the operations.
	return httpcodec.NewDefaultCodecs(nil).
		PatchAll(func(c httpcodec.Codec) *httpcodec.Patcher {
			return httpcodec.NewPatcher(c).Params("ip", IPCodec{})
		})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant