Skip to content
golang generators for structs
Go
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
cmd
examples
support/events
.gitignore
LICENSE
README.md
binary_gen.go
binary_gen_test.go
cache.go
cache_test.go
enum_gen.go
enum_gen_test.go
events.go
events_test.go
go.mod
go.sum
scanner.go
timed_cache.go
timed_cache_test.go

README.md

Tools to manipulate with structs

Installation: go get -v github.com/reddec/struct-view/cmd/...

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
}
You can’t perform that action at this time.