Giter Club home page Giter Club logo

gowrap's Introduction

GoWrap

License Build Coverage Status Go Report Card GoDoc Awesome Release

GoWrap is a command line tool that generates decorators for Go interface types using simple templates. With GoWrap you can easily add metrics, tracing, fallbacks, pools, and many other features into your existing code in a few seconds.

Demo

demo

Installation

go get -u github.com/hexdigest/gowrap/cmd/gowrap

Usage of gowrap

Usage: gowrap gen -p package -i interfaceName -t template -o output_file.go
  -g	don't put //go:generate instruction into the generated code
  -i string
    	the source interface name, i.e. "Reader"
  -o string
    	the output file name
  -p string
    	the source package import path, i.e. "io", "github.com/hexdigest/gowrap" or
    	a relative import path like "./generator"
  -t template
    	the template to use, it can be an HTTPS URL a local file or a
    	reference to one of the templates in the gowrap repository
  -v value
    	a key-value pair to parametrize the template,
    	arguments without an equal sign are treated as a bool values,
    	i.e. -v DecoratorName=MyDecorator -v disableChecks

This will generate an implementation of the io.Reader interface wrapped with prometheus metrics

  $ gowrap gen -p io -i Reader -t prometheus -o reader_with_metrics.go

This will generate a fallback decorator for the Connector interface that can be found in the ./connector subpackage:

  $ gowrap gen -p ./connector -i Connector -t fallback -o ./connector/with_metrics.go

Run gowrap help for more options

Hosted templates

When you specify a template with the "-t" flag, gowrap will first search for and use the local file with this name. If the file is not found, gowrap will look for the template here and use it if found.

List of available templates:

  • circuitbreaker stops executing methods of the wrapped interface after the specified number of consecutive errors and resumes execution after the specified delay
  • fallback takes several implementations of the source interface and concurrently runs each implementation if the previous attempt didn't return the result in a specified period of time, it returns the first non-error result
  • log instruments the source interface with logging using standard logger from the "log" package
  • logrus instruments the source interface with logging using popular sirupsen/logrus logger
  • opencensus instruments the source interface with opencensus spans
  • opentelemetry instruments the source interface with opentelemetry spans
  • opentracing instruments the source interface with opentracing spans
  • prometheus instruments the source interface with prometheus metrics
  • ratelimit instruments the source interface with RPS limit and concurrent calls limit
  • retry instruments the source interface with retries
  • robinpool puts several implementations of the source interface to the slice and for every method call it picks one implementation from the slice using the Round-robin algorithm
  • syncpool puts several implementations of the source interface to the sync.Pool and for every method call it gets one implementation from the pool and puts it back once finished
  • timeout instruments each method that accepts context with configurable timeout
  • validate runs func Validate() error method on each argument if it's present
  • twirp_error inject request data into twirp.Error as metadata
  • twirp_validate runs func Validate() error method on each argument if it's present and wraps returned error with twirp.Malformed error
  • grpc_validate runs func Validate() error method on each argument if it's present and returns InvalidArgument error in case when validation failed
  • elastic apm instruments the source interface with elastic apm spans

By default GoWrap places the //go:generate instruction into the generated code. This allows you to regenerate decorators' code just by typing go generate ./... when you change the source interface type declaration. However if you used a remote template, the //go:generate instruction will contain the HTTPS URL of the template and therefore you will need to have internet connection in order to regenerate decorators. In order to avoid this, you can copy templates from the GoWrap repository to local files and add them to your version control system:

$ gowrap template copy fallback templates/fallback

The above command will fetch the fallback template and copy it to the templates/fallback local file. After template is copied, you can generate decorators using this local template:

$ gowrap gen -p io -i Reader -t templates/fallback reader_with_fallback.go

Custom templates

You can always write your own template that will provide the desired functionality to your interfaces. If you think that your template might be useful to others, please consider adding it to our template repository.

The structure of information passed to templates is documented with the TemplateInputs struct.

Template Functions

In the templates, all functions provided by the sprig template library are available.

Additionally gowrap includes the following template functions:

  • up: returns the input with all Unicode letters mapped to their upper case.
  • down: returns the input with all Unicode letters mapped to their lower case.
  • upFirst: returns the input with the first Unicode letter mapped to their upper case.
  • downFirst: returns the input with the first Unicode letter mapped to their lower case.
  • replace: returns the input with all occurences of the first argument replaced with the second argument.
  • snake: returns the input in snake case representation.

Become a patron

Here's my Patreon page. Thank you!

gowrap's People

Contributors

bars43ru avatar beono avatar breml avatar codebien avatar damianopetrungaro avatar david7482 avatar dependabot[bot] avatar devbrom avatar dovbysh avatar dpasiukevich avatar eenlucian avatar fryuni avatar hexdigest avatar kristofpento avatar masim05 avatar max107 avatar optiman avatar recht avatar regeda avatar rgngl avatar saku2 avatar shousper avatar trusch avatar uvyuhin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gowrap's Issues

Method.ReturnsError is not a proper interface/implements check

I have code that implements a more advanced error model which is passed around as its own interface rather than error (which it does, implement). The code:

https://github.com/hexdigest/gowrap/blob/master/generator/types.go#L95

Dees not do an error interface check and therefore does not work for anything except a literal error type. A proper type check would look something like:

var paramType ast.Type
types.Implements(paramType, ErrorInterface)

where:

// ErrorInterface defines the error interface as a type for comparison.
var ErrorInterface = types.NewInterfaceType([]*types.Func{
	types.NewFunc(
		0,
		nil,
		"Error",
		types.NewSignatureType(nil, nil, nil, nil,
			types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String])),
			false)),
}, nil)

Unfortunately this would require a much larger re-writing of the existing code so I'm only filing an issue instead of a fix :(.

Unable to generate if destination directory does not contain go files

When trying to generate files with -o option pointing to an empty directory the command fails with
failed to load destination package <relative_destination_package_path> -: unknown import path <full_destination_package_path>: cannot find module providing package <full_destination_package_path>

The error goes away if any .go file is created into destination directory.

template path in generated code uses OS specific path separators

When using file paths to specify the location of a template, the generated code will use either a "/" or a "\" to separate directories in the path. This is an issue if you checkin the generated code and you have some users that generate using a Linux system and other users that generate using a Windows system.

The expectation is that the generated code should not change based on what type of system the generation is performed on.

Propose to do a simple search/replace on the file path and replace any "\" with a "/"

template/retry: do not use time.After

I think, it should not be difficult to change usage of time.After to time.NewTimer.
The problem is described in docs of time.After.
I some edge cases it should reduce memory consumption.
Because it is a template, it should not break back capability.

{{range $method := .Interface.Methods}}
	{{if $method.ReturnsError}}
	// {{$method.Name}} implements {{$.Interface.Type}}
	func (_d {{$decorator}}) {{$method.Declaration}} {
		for _i := 0; _i < _d._retryCount; _i++ {
			{{$method.ResultsNames}} = _d.{{$.Interface.Name}}.{{$method.Call}}
			if err == nil {
				break
			}
			if _d._retryCount > 1 {
				{{- if $method.AcceptsContext}}
				_t := time.NewTimer(_d._retryInterval)
				defer _t.Stop()
				select {
				case <-ctx.Done():
					return {{$method.ResultsNames}}
				case <-_t.C:
				}
				{{else}}
				time.Sleep(_d._retryInterval)
				{{end -}}
			}
		}
		return {{$method.ResultsNames}}
	}
	{{end}}
{{end}}

Prometheus metric is not registered

Prometheus metrics must be registered. Not registered metrics are not avaliable in metrics path.

There are two ways to resolve it:
*Add metric registration in constructor
*Make metric variable public and delegate registration to developer

log template not emitting anything if method returns value but no error

Very minor point about the provided log template.
If I have a method that returns a single result, but not an error, the template generates a deferred function to set the results but these are not emitted by a log call.
Consequently, the generated code fails to compile because of an unused variable.

I addressed the issue by adding the following before line 42 of the template:

{{else}}   
  _d._stdlog.Println(_results...)

method.Declaration does not add package names in argument types and return types

The
func (_d {{$decorator}}) {{$method.Declaration}} {
line generates

func (_d wrapped) Method(ctx context.Context, paramName ParamType) (returnName ReturnType)
instead of the expected
func (_d wrapped) Method(ctx context.Context, paramName wrappedtype.ParamType) (returnName wrappedtype.ReturnType)

This change in behaviour has happened between release 1.2.7 and 1.3.0

Access to Documentation Comments

We would like to be able to make interface and method ‘documentation’ comments available to templates.

For example, we would like to use a method comment to provide hints to a logging decorator about what and how to log specific params and results (avoiding monolithic log output, hiding sensitive data etc)

The documentary comments are readily available in the ast and so could be copied to the template input.

I’m happy to prototype this and raise a pull request, but would welcome your opinion on whether this would be a useful feature.

Wrong position of package comment if template has no own `import`

If template has own import section (e.g. with github.com/prometheus/client_golang/prometheus) then the generated file header looks correct :

package name

// DO NOT EDIT!
// This code is generated with http://github.com/hexdigest/gowrap tool
// using templates/metrics template

import (
	"context"
	
	"github.com/prometheus/client_golang/prometheus"
)

But if template doesn't have own import section then package comment goes after import in generated file. It looks bad:

package name

import (
	"context"
)

// DO NOT EDIT!
// This code is generated with http://github.com/hexdigest/gowrap tool
// using templates/metrics template

import() in the beginning of template fixes it but looks weird.

Imports of modules with custom names are not included into generated code

The generator does not work with the case when the name of the package imported into source file does not match last import path segment.

For example, source file like

package authz

import (
	"mymodulename/pkg/api/iam/v1" // Go package name is "iamv1"
)

type Enforcer interface {
	Enforce(ctx context.Context, request *iamv1.Request) ([]string, error)
}

Will result generated code that is using *iamv1.Request type in method signatures, however mymodulename/pkg/api/iam/v1 will not be added to the imports section.

I tried all sorts of aliases for this module, however, nothing helps. Generator only works if I rename imported package to v1.

.Interface.Methods no longer contains nested methods

proofOfBug.tpl:

{{range $method := .Interface.Methods}}
func {{$method.Declaration}}{}
{{end}}

proofOfBug.go:

//go:generate gowrap gen -g  -v DecoratorName=POC -i Base -t proofOfBug.tpl -o proofOfBug.gen.go
type Base interface {
	A()
	Nested
}

type Nested interface {
	B()
}

version 1.2.7 did not ignore nesteds

Reason for errUnexportableType error

I am curious to know why the use of an unexported type should abort decorator generation (line 138 of printer.go).

I want to generate decorators for an interface that is only used in the context of the package in which it is defined, but this error blocks me from doing that.

If I comment out the check, everything is generated as I need.

Can't loading template during the gen

good afternoon

I've tried to generate a tracing wrapper by my custom teplate which is uploading from github https://github.com/XeniaBgd/templates/blob/main/tracing

The command is :
gowrap gen -p ./internal/domain/repository -i Repository -t https://raw.githubusercontent.com/XeniaBgd/templates/main/tracing -o ./internal/providers/analytics/tracing.go

But I've got this error:
"failed to load template: open https://raw.githubusercontent.com/XeniaBgd/templates/main/tracing: The filename, directory name, or volume label syntax is incorrect"

I've checked code
https://github.com/hexdigest/gowrap/blob/master/cmd_generate.go#L191

func underlyingErrorIs(err, target error) bool {
// Note that this function is not errors.Is:
// underlyingError only unwraps the specific error-wrapping types
// that it historically did, not all errors implementing Unwrap().
err = underlyingError(err)
if err == target {
return true
}
// To preserve prior behavior, only examine syscall errors.
e, ok := err.(syscallErrorType)
return ok && e.Is(target)
}

My error was "internal/syscall/windows.ERROR_INVALID_NAME (123)". That's why the function above returned false.

//if !os.IsNotExist(err) {
// return
//}

Without this code everything is ok.

Could somebody explain me why was that?

Add support for build tags

When attempting to generate from a file with a build tag, gowrap exits with an error.

//go:build unit

package main

//go:generate gowrap gen -g -p . -i TestInterface -t prometheus -o prom.go

type TestInterface interface{
    TestMethod()
}
go generate -tags unit ./...
failed to load source package: -: build constraints exclude all Go files in /path/to/package
path/to/package/prom_test.go:5: running "gowrap": exit status 1

Add support for internal packages

When I try to generate a wrapper from an interface in an internal package, gowrap fails.

/path/to/package/internal/generated/prom_test.go:

package generated

//go:generate gowrap gen -g -p . -i TestInterface -t prometheus -o prom.go

type TestInterface interface{
    TestMethod()
}
go generate ./...
failed to parse source package: open repo.git/path/to/package/internal/generated: no such file or directory
internal/generated/prom_test.go:3: running "gowrap": exit status 1

Moving the file out of the internal subpackage makes it work.

Unable to install this package

I'm trying to install gowrap using command go get -u github.com/hexdigest/gowrap/cmd/gowrap
And receiving an error:

go get: github.com/cavaliercoder/[email protected] updating to
github.com/cavaliercoder/[email protected]: parsing go.mod:
module declares its path as: github.com/cavaliergopher/cpio
but was required as: github.com/cavaliercoder/go-cpio

Cannot iterate over m.Params names

I am trying to implement a custom template which needs to provide each of the method's parameters in a certain way

Based on the log template here: https://github.com/hexdigest/gowrap/blob/master/templates/log

I need to make this code

 _params := []interface{}{"{{$decorator}}: calling {{$method.Name}} with params:", {{$method.ParamsNames}} }

To be this code instead

      var _params []zapcore.Field
      var _field zapcore.Field
      {{range $param := $method.ParamsSlice}}
      _field = zapcore.Field{ Key:"{{$decorator}}: calling {{$method.Name}} with params:", Type: zapcore.ObjectMarshalerType, Interface: {{$param.Name}}}
      _params = append(_params, _field)
      {{end}}

To make it happen had to add the ParamsSlice function to gowrap code, so the slice is public and available to be used. I may be missing something or not?

This is the code I've added to types.go to make it work

// ParamsSlice returns a list of method params
func (m Method) ParamsSlice() []Param {
	return m.Params
}

Hope this makes sense or please point me to the best practice to consuming that data

Thanks

Merge generated file with existing instead of overwriting

I want to edit generated methods and I want them not to be overwritten by next generation. If new methods are added to interface I want them to be generated and added to the existing file.

So what I want is some kind of strategy. Invocation may look like:

gowrap gen -i Some -t mine.tmpl -o generated.go -strategy merge|overwrite

How woild this work:

Template:

type {{$decorator}} struct {
  logger *zap.Logger
  next {{.Interface.Type}}
}

{{range $method := .Interface.Methods}}
func (m *{{$decorator}}) {{$method.Declaration}} {
    m.logger.Info("{{ $method.Name }}")
    {{ $method.Pass "m.next." }}
}
{{end}}

Initial interface:

type Some interface {
    A()
}

Initial generated file:

type decorator struct {
    logger *zap.Logger
    next Some
}

func (d *decorator) A() {
    m.logger.Info("A") 
    m.next.A()
}

Updated file:

type decorator struct {
    logger *zap.Logger
    next Some
}

func (d *decorator) A() {
    m.logger.Info("A with updates") // updated generated file
    m.next.A()
}

Updated interface:

type Some interface {
    A()
    B() // New added method
}

Generated with stratedy 'merge' file:

type decorator struct {
    logger *zap.Logger
    next Some
}

func (d *decorator) A() {
    m.logger.Info("A with updates") // updates are not overwritten
    m.next.A()
}

func (d *decorator) B() {
    m.logger.Info("B") 
    m.next.A()
}

If you believe there is a place for such a feature in this library I might be able to implement it myself.

comments on the top of generated file

Greetings!
We use gowrap and golines in out project. As you can see here golines checks a few top lines of each file to check if it generated, but gowrap places comments after imports block

Screenshot ![image](https://user-images.githubusercontent.com/6426720/136575496-b641600a-e7e2-4922-aa9d-db17b5bc81f9.png)

Could you fix it and place comments on the top?

Access to Package Name in Body Template

We need access to the interface’s package name in a template.
We can see this is available when the header template is executed, but don’t think it is available to body templates.
Could this be made available in the map?

Also, is there any way to customise the header template?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.