structview

package module
v0.0.0-...-9455615 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2022 License: MIT Imports: 16 Imported by: 0

README

Tools to manipulate with structs

Installation: go install github.com/reddec/struct-view/cmd/...@latest

Events generator

Usage:
  events-gen [OPTIONS] [Directories...]

Application Options:
  -p, --package=     Package name (can be override by output dir) (default: events) [$PACKAGE]
  -o, --output=      Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -P, --private      Make generated event structures be private by prefix 'event' [$PRIVATE]
      --event-bus=   Generate structure that aggregates all events [$EVENT_BUS]
  -m, --mirror       Mirror all events to the universal emitter [$MIRROR]
  -f, --from-mirror  Create producer events as from mirror (only for event bus) [$FROM_MIRROR]
  -i, --ignore-case  Ignore event case for universal source (--from-mirror) [$IGNORE_CASE]
  -s, --sink         Make a sink method for event bus to subscribe to all events [$SINK]
  -e, --emitter=     Create emitter factory [$EMITTER]
  -l, --listener=    Create method to subscribe for all events (default: SubscribeAll) [$LISTENER]
  -H, --hint=        Give a hint about events (eventName -> struct name) [$HINT]
  -c, --context      Add context to events [$CONTEXT]

Help Options:
  -h, --help         Show this help message
Basic usage

You should declare go file with types that will be used as event types. For example:

package basic
//go:generate events-gen -p basic -o events.go .

// event:"UserCreated"
// event:"UserRemoved"
type User struct {
	ID   int64
	Name string
}

// event:"UserSubscribed"
// event:"UserLeaved"
type Subscription struct {
	UserID int64
	Mail   string
}

Magic comment event:"" gives an instruction to the event generator hint that this type is used as parameter for event with name defined in commas (UserCreated, UserRemoved for type User).

You may use option ref (like event:"EventName,ref") to use payload by reference.

Instruction for go generator events-gen -p basic -o events.go . tells us to generate events to file events.go with package basic and look for source files in current (.) directory.

Feel free to look in examples/basic directory to see generated file.

Finally you can embed event emitter to you struct like this:

package basic

type UserApp struct {
    Subscribed UserSubscribed
    Leaved     UserLeaved
    //...
}
Advanced usage

Quite often we need to have some aggregated event source (aka event bus) that aggregates several event emitters in one place.

By using example above we may imagine situation that all our events actually relates to the one application. Let's call it App.

We also what to let other developers easily extend logic of our product by adding event listeners and (probably) let them also emits events using only one source of truth.

In terms of events-gen this pattern called EventBus.

Let's improve our previous application and change generator invocation to events-gen --event-bus Events -P -p advance -o events.go .. It means generate event bus (events aggregator) called Events and make all generated definitions private (-P) except bus itself. We also changed package to advance so you may look into examples/advance.

To mark generated structures as a private (-P) is completely optional, however, it's common case that if we already aggregated our events to the one structure then we probably don't want to expose events objects to outer world except only through event bus.

Generated event bus will looks something like

type Events struct {
    UserCreated    eventUserCreated
    UserRemoved    eventUserRemoved
    UserSubscribed eventUserSubscribed
    UserLeaved     eventUserLeaved
}

Finally you can embed event bus to you struct like this:

package advance

type App struct {
    Events Events
    //...
}
Multiple packages as a source

Sometimes you need to create events for types that you should not modify (add comments) or for types from different packages.

events-gen supports as positional arguments multiple source directories that needs to be scanned. Also it is possible to give generator a "hint": expected event name and type. In this case generator will create events object for types that matches hints as well as marked by comments.

As an addition to the example above let's imagine other package called transactions located in ../transactions directory with types UserTX and BankTX that we want to use as our events UserTxCreated and BankTxCreated. So we need to modify generator command (used example from basic to reduce number flags, however, you are not restricted in that) as:

events-gen -p basic -o events.go -H UserTxCreate:UserTX -H BankTxCreated:BankTX . ../transactions

Mirroring and integration

Event-based approach for complex systems most of the time means integration with other external, legacy or just other components. Common case for enterprise solution is to use integration bus (IBM MQ, RabbitMQ, ....) as a transport for events.

For such cases you may use mirroring (-m) as well as global sink (-m). Both ways let you consume events in unified way without caring about types.

Those approaches very similar to each other, however, mirroring (-m) is a bit faster but supports only one sink and global sink (-s) that just subscribes to all events and has no limits for the number of listeners.

So for the example above generator will create:

mirroring (-m)

func EventsWithMirror(mirror func(eventName string, payload interface{})) *Events 

global sink (-s)

func (bus *Events) Sink(sink func(eventName string, payload interface{})) *Events

Function parameters are self-explainable, but:

  • eventName - name of event (UserCreated, UserRemoved,...)
  • payload - original event object (not reference, a value)
From mirror

All described above were about consuming events made by our generated events bus. However, you may want also transparently integrate external system used as source of events and propagate them to the local instance. For example, you may want use notification from a message broker (RabbitMQ, IBM MQ, HTTP REST,...) as internal events.

+----------------+                +-----------+  <--- emit  --- +-----------+
| exteral system | --- event ---> | event bus |                 | component |
+----------------+    (as emit)   +-----------+  --- listen --> +-----------+

In terms of events-generator such approach called FromMirror and it's available ony together with EventBus.

Generated code could be a bit tricky, however, to generate FromMirror handlers just add -f flag to the generator. It will produce (for example above) methods for events:

universal emitter

func (ev *Events) Emit(eventName string, payload interface{})

Emits event by name. Payload should event type (reference or value). Silently drops invalid parameters: unknown event, incorrect payload.

universal payload fabric

func (ev *Events) Payload(eventName string) interface{}

Creates payload value (reference) by event name or returns nil.

Both of this methods require case-sensitive event name, however, by flag -i it can be switched to case-insensitive mode.

Emitter

It might be useful to use emitter in an external code or already existent code, however, use instance of a EventBus could be not an ideal decision due to increase of code coupling.

For that reason you may create additional emitter by flag -e <EmitterFunc> that will generate additional method EmitterFunc in a EventBus and additional structure that aggregates all Emit() methods in one place. By using this approach you may require an interface in your code instead of exact implementation.

So for the basic example, described above, -e Emitter the implementation of this interface will be generated (note: interface by itself will not be generated due to a best-practice "accept interface, return structure"):

type Sample interface {
    UserCreated(payload User)
    UserRemoved(payload User)
    UserSubscribed(payload Subscription)
    UserLeaved(payload Subscription)
}
Listener

To subscribe on all events exists method SubscribeAll, however, name of the method could be overloaded by -l <listener method> flag. If method name is empty, method will not be generated.

Context

To add context argument for all events, add flag -c

Cache generator

Generates multi-level cache for key-value data with a separate synchronization unit per value.

Usage:
  cache-gen [OPTIONS]

Application Options:
  -p, --package=      Package name (can be override by output dir) (default: cache) [$PACKAGE]
  -o, --output=       Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -k, --key-type=     Key type [$KEY_TYPE]
  -v, --value-type=   Value type [$VALUE_TYPE]
  -i, --key-import=   Import for key type [$KEY_IMPORT]
  -I, --value-import= Import for value type [$VALUE_IMPORT]
  -t, --type-name=    Typename for cache (default: Manager) [$TYPE_NAME]

Help Options:
  -h, --help          Show this help message

Common use-case: you have a service that contains user profiles (identified by ID) and you want to cache results to prevent multiple requests for the same user. However, you expect that your code could be used from multiple threads/gorountines so parallel requests to different users should not block each-other, but code should make only one request per unique user id.

Example:

cache-gen -p users -k int64 -v *UserProfile -I github.com/MyCompany/MyTypes -o user_cached.go
  • -p users sets package name to users
  • -k int64 sets key type (user id) to int64
  • -v *UserProfile sets value type (user profile) as a ref to user-defined type
  • -I github.com/MyCompany/MyType sets import for user-defined package for value type (UserProfile)
  • -o user_cached.go sets output to file named user_cached.go

Type name (-t) not set so default name will be used (Manager).

Result (functions body omitted):

package cache

import (
	"context"
	mytypes "github.com/MyCompany/MyTypes"
	"sync"
)

type UpdaterManager interface {
	Update(ctx context.Context, key int64) (*mytypes.UserProfile, error)
}
type UpdaterManagerFunc func(ctx context.Context, key int64) (*mytypes.UserProfile, error)

func (fn UpdaterManagerFunc) Update(ctx context.Context, key int64) (*mytypes.UserProfile, error) {} // body omitted

func NewManagerFunc(updateFunc UpdaterManagerFunc) *Manager {} // body omitted

func NewManager(updater UpdaterManager) *Manager {} // body omitted

type Manager struct {
    // fields omitted
}

func (mgr *Manager) Find(key int64) *cacheManager {}
func (mgr *Manager) FindOrCreate(key int64) *cacheManager {}
func (mgr *Manager) Get(ctx context.Context, key int64) (*mytypes.UserProfile, error) {}
func (mgr *Manager) Set(key int64, value *mytypes.UserProfile) {}
func (mgr *Manager) Purge(key int64) {}
func (mgr *Manager) PurgeAll() {}
func (mgr *Manager) Snapshot() map[int64]*mytypes.UserProfile {}

type cacheManager struct {
    // fields omitted    
}

func (cache *cacheManager) Valid() bool {}
func (cache *cacheManager) Invalidate() {}
func (cache *cacheManager) Key() int64 {}
func (cache *cacheManager) Get() *mytypes.UserProfile {}
func (cache *cacheManager) Ensure(ctx context.Context) (*mytypes.UserProfile, error) {}
func (cache *cacheManager) Set(value *mytypes.UserProfile) {}
func (cache *cacheManager) Update(ctx context.Context, force bool) error {}

See full example in examples/cache

Timed cache

Generate simple cache with expiration time

Usage:
  timed-cache [OPTIONS]

Application Options:
  -p, --package=      Package name (can be override by output dir) (default: cache) [$PACKAGE]
  -o, --output=       Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -v, --value-type=   Value type [$VALUE_TYPE]
  -I, --value-import= Import for value type [$VALUE_IMPORT]
  -t, --type-name=    Typename for cache (default: Manager) [$TYPE_NAME]
  -a, --array         Is value should be an array [$ARRAY]

Help Options:
  -h, --help          Show this help message

Binary gen

Generate very simple static binary marshal/unmarshal for struct. Unknown fields are ignored. Goal is support same encoding/decoding with C/C++ structures with same encoding layout (aka: result should be decodable on Big endian machines like (struct *my_type)(buffer)).

Currently supported types:

  • uint8, uint16, uint32, uint64
Usage:
  binary-gen [OPTIONS]

Application Options:
  -o, --output=    Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -t, --type-name= TypeName for generator (default: Manager) [$TYPE_NAME]

Help Options:
  -h, --help       Show this help message

see examples/binarygen directory

JSON-enum array gen

Generates type alias, validator for values (according to entered values) and custom JSON Unmarshal with embedded checks.

Usage:
  json-enum-gen [OPTIONS] [Values...]

Application Options:
  -p, --package=     Package name (can be override by output dir) (default: enum) [$PACKAGE]
  -o, --output=      Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -t, --type-name=   Enum name (default: Manager) [$TYPE_NAME]
  -s, --source-type= Source type name (default: string) [$SOURCE_TYPE]

Help Options:
  -h, --help         Show this help message

For example you need to check incoming request to play some channel (ex: podcast). Request message will look like

{
  "channel" : "channel name",
  "speed": 1 
}

You want to allow your customers choose only specific values for speed: 0.5, 1, 1.5, 2, 2.5

Common solution is to create enum, defined possible values, generate JSON wrapper. But it will bring mess to your code with meaningless values like Channel0.5 = 0.5; Channel1 = 1 and so on.

This command will let you keep clean code as much as possible:

json-enum-gen -s int -o speed.go -t Speed 0.5 1 1.5 2 2.5

will generate code:

// Code generated by json-enum-gen. DO NOT EDIT.
//go:generate json-enum-gen -s int -t Speed 0.5 1 1.5 2 2.5
package enum

import (
        "encoding/json"
        "errors"
)

type Speed int

func (v Speed) Get() int {
        return int(v)
}
func (v Speed) IsValid() bool {
        switch int(v) {
        case 0.5, 1, 1.5, 2, 2.5:
                return true
        default:
                return false
        }
}
func (v *Speed) UnmarshalJSON(data []byte) error {
        var parsed int
        err := json.Unmarshal(data, &parsed)
        if err != nil {
                return err
        }
        typed := Speed(parsed)
        if !typed.IsValid() {
                return errors.New("Invalid value for type Speed. Possible options are: 0.5, 1, 1.5, 2, 2.5")
        }
        *v = typed
        return nil
}

Sync map gen

Generates Java-like thread-safe map

Generated code will implement next interface:

package sample

type UpdaterFunc func(key KeyType) (ValueType, error)

type SyncItem interface {
    Valid() bool
    Invalidate() 
    Key() KeyType
    Get() ValueType
    Set(value ValueType)
    Ensure(updater UpdaterFunc) (ValueType, error)
}

type SyncMap interface{
    Find(key KeyType) SyncItem
    FindOrCreate(key KeyType) SyncItem
    Get(key KeyType, construct UpdaterFunc) (ValueType, error)
    Set(key KeyType, value ValueType)
    Purge(key KeyType)
    PurgeAll()
    Snapshot() map[KeyType]ValueType
} 

Usage

Usage:
  syncmap-gen [OPTIONS]

Application Options:
  -p, --package=      Package name (can be override by output dir) (default: cache) [$PACKAGE]
  -o, --output=       Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -k, --key-type=     Key type [$KEY_TYPE]
  -v, --value-type=   Value type [$VALUE_TYPE]
  -i, --key-import=   Import for key type [$KEY_IMPORT]
  -I, --value-import= Import for value type [$VALUE_IMPORT]
  -t, --type-name=    TypeName for cache (default: Manager) [$TYPE_NAME]

Help Options:
  -h, --help          Show this help message
Params gen

Scans struct methods and generate wrappers for all parameters for all exported methods

Example:


type App struct {}

func (app *App) Sum(a, b, c int) int {return a + b +c}

invoke

params-gen -t App -o params.go

will generate

type SumParams struct {
	A int `form:"a" json:"a" xml:"a" yaml:"a"`
	B int `form:"b" json:"b" xml:"b" yaml:"b"`
	C int `form:"c" json:"c" xml:"c" yaml:"c"`
}

func (sp *SumParams) Invoke(app *App) int {
	return app.Sum(sp.A, sp.B, sp.C)
}

Usage

Usage:
  params-gen [OPTIONS]

Application Options:
  -p, --package=   Package name (can be override by output dir) [$PACKAGE]
  -o, --output=    Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
      --dir=       Directory to scan (default: .) [$DIR]
  -t, --type-name= TypeName for cache (default: Manager) [$TYPE_NAME]

Help Options:
  -h, --help       Show this help message

Ring buffer generator

Classical fixed-buffer circle (ring) container, where new data overwrites old one. Wikipedia.

Complexity

Operation Complexity
Add O(1)
Get O(1)
Remove N/A
Copy O(N)

Example:

Generate ring buffer for int type

ring-buffer-gen -p abc -t int --name IntBuffer

will produce (implementation details omitted):

package abc

// New instance of ring buffer
func NewIntBuffer(size uint) *IntBuffer {}

// Wrap pre-allocated buffer to ring buffer
func WrapIntBuffer(buffer []int) *IntBuffer {}

// Ring buffer for type int
type IntBuffer struct {
	seq  uint64
	data []int
}

// Add new element to the ring buffer. If buffer is full, the oldest element will be overwritten
func (rb *IntBuffer) Add(value int) {}

// Get element by index. Negative index is counting from end
func (rb *IntBuffer) Get(index int) (ans int) {}

// Length of used elements. Always in range between zero and maximum capacity
func (rb *IntBuffer) Len() int {}

// Clone of ring buffer with shallow copy of underlying buffer
func (rb *IntBuffer) Clone() *IntBuffer {}

// Flatten copy of underlying buffer. Data is always ordered in an insertion order
func (rb *IntBuffer) Flatten() []int {}

Usage

Usage:
  ring-buffer-gen [OPTIONS]

Application Options:
  -p, --package=      Package name (can be override by output dir) (default: enum) [$PACKAGE]
  -o, --output=       Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -t, --type-name=    Type name to wrap [$TYPE_NAME]
      --name=         Result structure name (default: RingBuffer) [$NAME]
      --synchronized  Make collection be synchronized [$SYNCHRONIZED]
  -i, --import=       Import for type [$IMPORT]
  -n, --notify        Notify events in case of updates [$NOTIFY]

Help Options:
  -h, --help          Show this help message

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FindPackage

func FindPackage(dir string) (string, error)

func TypeDefinition

func TypeDefinition(file *ast.File, typeDef ast.Expr, localPackage string) *jen.Statement

Types

type BinaryGenerator

type BinaryGenerator struct {
	TypeName string
}

func (BinaryGenerator) Generate

func (bg BinaryGenerator) Generate(directory string) (jen.Code, string, error)

type CacheGen

type CacheGen struct {
	TypeName       string
	KeyType        string
	KeyImport      string
	ValueType      string
	ValueImport    string
	PrivateInit    bool
	WithEvents     bool
	WithExpiration bool
}

func (*CacheGen) CacheType

func (cc *CacheGen) CacheType() string

func (*CacheGen) EventKindType

func (cc *CacheGen) EventKindType() string

func (*CacheGen) EventType

func (cc *CacheGen) EventType() string

func (*CacheGen) EventTypeName

func (cc *CacheGen) EventTypeName(event string) string

func (*CacheGen) Generate

func (cc *CacheGen) Generate() jen.Code

func (*CacheGen) Key

func (cc *CacheGen) Key() jen.Code

func (*CacheGen) UpdaterType

func (cc *CacheGen) UpdaterType() string

func (*CacheGen) Value

func (cc *CacheGen) Value() jen.Code

type ChainGen

type ChainGen struct {
	ContextType string
	Import      string
	TypeName    string
}

func (*ChainGen) Builder

func (cg *ChainGen) Builder() string

func (*ChainGen) BuilderFunc

func (cg *ChainGen) BuilderFunc() *jen.Statement

func (*ChainGen) Chain

func (cg *ChainGen) Chain() string

func (*ChainGen) Generate

func (cg *ChainGen) Generate() jen.Code

func (*ChainGen) Handler

func (cg *ChainGen) Handler() string

func (*ChainGen) HandlerType

func (cg *ChainGen) HandlerType() string

func (*ChainGen) Value

func (cg *ChainGen) Value() jen.Code

type EnumGen

type EnumGen struct {
	TargetType string
	Name       string
	Values     []string
}

func (EnumGen) Generate

func (eg EnumGen) Generate() jen.Code

type Event

type Event struct {
	Name     string
	TypeName string
	Dir      string
}

type EventGenerator

type EventGenerator struct {
	WithBus        bool
	WithMirror     bool
	WithSink       bool
	WithContext    bool
	FromMirror     bool
	FromIgnoreCase bool
	BusName        string
	MirrorType     string
	Private        bool
	Emitter        string
	Listener       string
	PrivateEmit    bool
	Hints          map[string]string // Event->Struct Name
}

func (EventGenerator) Generate

func (eg EventGenerator) Generate(directories ...string) (*EventGeneratorResult, error)

type EventGeneratorResult

type EventGeneratorResult struct {
	Events []Event
	Code   jen.Code
}

type Mapping

type Mapping struct {
	Code       jen.Code
	NotMatched int
}

type ParamsGen

type ParamsGen struct {
	Dir        string
	StructName string
	Gin        bool
}

func (*ParamsGen) Generate

func (pg *ParamsGen) Generate() (jen.Code, error)

type RingBuffer

type RingBuffer struct {
	Name         string
	TypeName     string
	Import       string
	Synchronized bool
	Notify       bool
}

func (*RingBuffer) Generate

func (rb *RingBuffer) Generate() jen.Code

func (*RingBuffer) Qual

func (rb *RingBuffer) Qual() jen.Code

type Struct

type Struct struct {
	Struct     string
	Dir        string
	Definition *ast.StructType
	ImportPath string
	Ref        bool
}

func LoadAllStructs

func LoadAllStructs(dir string) ([]*Struct, error)

func LoadStruct

func LoadStruct(dir, structName string) (*Struct, error)

func WrapStruct

func WrapStruct(dir string, name string, definition *ast.StructType) (*Struct, error)

func (Struct) AsRef

func (s Struct) AsRef() Struct

func (Struct) Empty

func (s Struct) Empty() bool

func (*Struct) FindClosetField

func (s *Struct) FindClosetField(name string, containsSearch bool) *ast.Field

func (Struct) Qual

func (s Struct) Qual() jen.Code

type SyncMapGen

type SyncMapGen struct {
	TypeName    string
	KeyType     string
	KeyImport   string
	ValueType   string
	ValueImport string
}

func (*SyncMapGen) CacheType

func (cc *SyncMapGen) CacheType() string

func (*SyncMapGen) Generate

func (cc *SyncMapGen) Generate() jen.Code

func (*SyncMapGen) Key

func (cc *SyncMapGen) Key() jen.Code

func (*SyncMapGen) UpdaterType

func (cc *SyncMapGen) UpdaterType() string

func (*SyncMapGen) Value

func (cc *SyncMapGen) Value() jen.Code

type TimedCache

type TimedCache struct {
	TypeName    string
	ValueType   string
	ValueImport string
	Array       bool
	PrivateInit bool
	WithEvents  bool
}

func (*TimedCache) EventKindType

func (cc *TimedCache) EventKindType() string

func (*TimedCache) EventType

func (cc *TimedCache) EventType() string

func (*TimedCache) EventTypeName

func (cc *TimedCache) EventTypeName(event string) string

func (*TimedCache) Generate

func (cc *TimedCache) Generate() jen.Code

func (*TimedCache) UpdaterType

func (cc *TimedCache) UpdaterType() string

func (*TimedCache) Value

func (cc *TimedCache) Value() jen.Code

type ToConvert

type ToConvert struct {
	Source         Struct
	Target         Struct
	FnName         string
	SearchContains bool
	Remap          map[string]string
}

func (ToConvert) Convert

func (config ToConvert) Convert() Mapping

Directories

Path Synopsis
cmd
events-gen/internal
Package internal generated by go-bindata.// sources: ts.gotemplate
Package internal generated by go-bindata.// sources: ts.gotemplate
examples
binarygen
Code generated by binary-gen.
Code generated by binary-gen.
cache
Code generated by cache-gen.
Code generated by cache-gen.
support

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL