runtimescan

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2022 License: Apache-2.0 Imports: 6 Imported by: 2

Documentation

Overview

Example (Compare)
package main

import (
	"fmt"
	"reflect"
	"sort"
	"strings"

	"github.com/future-architect/tagscanner/runtimescan"
)

type cmpStrategy struct {
	Path       string
	IgnoreCase bool
}

type compare struct {
	values map[string]any
}

func (c compare) ParseTag(name, tagKey, tagStr, pathStr string, elemType reflect.Type) (tag any, err error) {
	if tagStr == "skip" {
		return nil, runtimescan.Skip
	}
	if tagStr == "ignorecase" && elemType.Kind() != reflect.String {
		return nil, fmt.Errorf("the field '%v' is specified as 'ignorecase' but it is not string.", pathStr)
	}
	return &cmpStrategy{
		Path:       pathStr,
		IgnoreCase: tagStr == "ignorecase",
	}, nil
}

func (c *compare) VisitField(tag, value any) (err error) {
	t := tag.(*cmpStrategy)
	if t.IgnoreCase {
		c.values[t.Path] = strings.ToLower(value.(string))
	} else {
		c.values[t.Path] = value
	}
	return nil
}

func (c compare) EnterChild(tag any) (err error) {
	return nil
}

func (c compare) LeaveChild(tag any) (err error) {
	return nil
}

func Compare(s1, s2 any) (bool, []string, error) {
	c1 := compare{
		values: make(map[string]any),
	}
	err := runtimescan.Encode(s1, []string{"cmp"}, &c1)
	if err != nil {
		return false, nil, err
	}
	c2 := compare{
		values: make(map[string]any),
	}
	err = runtimescan.Encode(s2, []string{"cmp"}, &c2)
	if err != nil {
		return false, nil, err
	}
	var unmatch []string
	for k, v1 := range c1.values {
		if c2.values[k] != v1 {
			unmatch = append(unmatch, k)
		}
	}
	for k, v2 := range c2.values {
		v1, ok := c1.values[k]
		if !ok && v1 != v2 {
			unmatch = append(unmatch, k)
		}
	}
	sort.Strings(unmatch)
	return len(unmatch) == 0, unmatch, nil
}

func main() {
	s1 := struct {
		BothHaveSameValue int
		DifferentValue    int
		DifferentType     int
		Skip1             string `cmp:"skip"`
		IgnoreCase        string `cmp:"ignorecase"`
		OnlyOnS1          bool
	}{
		BothHaveSameValue: 17,
		DifferentValue:    19,
		DifferentType:     11,
		Skip1:             "skip by tag",
		IgnoreCase:        "",
		OnlyOnS1:          true,
	}
	s2 := struct {
		BothHaveSameValue int
		DifferentValue    int
		DifferentType     float64
		Skip2             string `cmp:"skip"`
		IgnoreCase        string `cmp:"ignorecase"`
		OnlyOnS2          bool
	}{
		BothHaveSameValue: 17,
		DifferentValue:    23,
		DifferentType:     1.23,
		Skip2:             "skip by tag",
		IgnoreCase:        "",
		OnlyOnS2:          true,
	}
	equal, diffs, err := Compare(&s1, &s2)
	fmt.Printf("equal: %v\n", equal)
	fmt.Printf("different keys: %v\n", diffs)
	fmt.Printf("err: %v\n", err)
}
Output:

equal: false
different keys: [DifferentType DifferentValue OnlyOnS1 OnlyOnS2]
err: <nil>
Example (Copy)
package main

import (
	"fmt"
	"log"
	"reflect"

	"github.com/future-architect/tagscanner/runtimescan"
)

type cpyStrategy struct {
	Path string
}

type cpy struct {
	values map[string]any
}

func (c *cpy) VisitField(tag, value any) (err error) {
	t := tag.(*cpyStrategy)
	c.values[t.Path] = value
	return nil
}

func (c cpy) EnterChild(tag any) (err error) {
	return nil
}

func (c cpy) LeaveChild(tag any) (err error) {
	return nil
}

func (c cpy) ExtractValue(tag any) (value any, err error) {
	t := tag.(*cpyStrategy)
	if v, ok := c.values[t.Path]; ok {
		return v, nil
	}
	return nil, runtimescan.Skip
}

func (c cpy) ParseTag(name, tagKey, tagStr, pathStr string, elemType reflect.Type) (tag any, err error) {
	if tagStr == "skip" {
		return nil, runtimescan.Skip
	}
	return &cpyStrategy{
		Path: pathStr,
	}, nil

}

func Copy(dest, src any) error {
	c := &cpy{
		values: make(map[string]any),
	}
	err := runtimescan.Encode(src, []string{"copy"}, c)
	if err != nil {
		return err
	}
	return runtimescan.Decode(dest, []string{"copy"}, c)
}

type Struct struct {
	Value  string
	Ignore string `copy:"skip"`
}

func main() {

	src := Struct{
		Value:  "copy from source",
		Ignore: "this value should be ignored",
	}
	dest := Struct{}
	err := Copy(&dest, &src)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Value: %s\n", dest.Value)
	fmt.Printf("Ignore: %s\n", dest.Ignore)
}
Output:

Value: copy from source
Ignore:
Example (Map2struct)
package main

import (
	"fmt"
	"log"
	"reflect"

	"github.com/future-architect/tagscanner/runtimescan"
)

// User should implement runtimescan.Decoder interface
// This instance is created in user code before runtimescan.Decode() function call
type decoder struct {
	src map[string]any
}

func (m decoder) ParseTag(name, tagKey, tagStr, pathStr string, elemType reflect.Type) (tag any, err error) {
	return runtimescan.BasicParseTag(name, tagKey, tagStr, pathStr, elemType)
}

func (m *decoder) ExtractValue(tag any) (value any, err error) {
	t := tag.(*runtimescan.BasicTag)
	v, ok := m.src[t.Tag]
	if !ok {
		return nil, runtimescan.Skip
	}
	return v, nil
}

func Decode(dest any, src map[string]any) error {
	dec := &decoder{
		src: src,
	}
	return runtimescan.Decode(dest, []string{"map"}, dec)
}

func main() {
	sampleMap := map[string]any{
		"int":     1,
		"float":   1.1,
		"string":  "hello world",
		"private": "this should be ignored",
	}
	sampleStruct := struct {
		Int         int      `map:"int"`
		Pointer     *float64 `map:"float"`
		String      string
		NonExisting *bool  `map:"bool"`
		private     string `map:"private"`
	}{}
	err := Decode(&sampleStruct, sampleMap)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("Field with tag: %v\n", sampleStruct.Int)
	fmt.Printf("Pointer field with tag: %v\n", *(sampleStruct.Pointer))
	fmt.Printf("Field without tag: %v\n", sampleStruct.String)
	fmt.Printf("Field that doesn't exist in source: %v\n", sampleStruct.NonExisting)
	fmt.Printf("Private field is ignored: %v\n", sampleStruct.private)
}
Output:

Field with tag: 1
Pointer field with tag: 1.1
Field without tag: hello world
Field that doesn't exist in source: <nil>
Private field is ignored:
Example (Struct2map)
package main

import (
	"fmt"
	"log"
	"reflect"

	"github.com/future-architect/tagscanner/runtimescan"
)

type encoder struct {
	dest map[string]any
}

func (m encoder) ParseTag(name, tagKey, tagStr, pathStr string, elemType reflect.Type) (tag any, err error) {
	return runtimescan.BasicParseTag(name, tagKey, tagStr, pathStr, elemType)
}

func (m *encoder) VisitField(tag, value any) (err error) {
	t := tag.(*runtimescan.BasicTag)
	m.dest[t.Tag] = value
	return nil
}

func (m encoder) EnterChild(tag any) (err error) {
	return nil
}

func (m encoder) LeaveChild(tag any) (err error) {
	return nil
}

func Encode(dest map[string]any, src any) error {
	enc := &encoder{
		dest: dest,
	}
	return runtimescan.Encode(src, []string{"map"}, enc)
}

func main() {
	destMap := map[string]any{}
	sampleStruct := struct {
		Int         int      `map:"int"`
		Pointer     *float64 `map:"float"`
		String      string
		NonExisting *bool  `map:"bool"`
		private     string `map:"private"`
	}{
		Int:     13,
		Pointer: &[]float64{3.1415}[0],
		String:  "hello world",
		private: "this should be ignored",
	}
	err := Encode(destMap, &sampleStruct)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("string: %v\n", destMap["string"])
	fmt.Printf("int: %v\n", destMap["int"])
	fmt.Printf("float: %v\n", destMap["float"])
}
Output:

string: hello world
int: 13
float: 3.1415

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// SkipTraverse is returned by Parser interface's ParseTag() method to notify to skip traversing child struct.
	SkipTraverse = errors.New("skip traverse")
	ErrParseTag  = errors.New("tag parse error")
	// Skip is a flag to skip. This is returned by Parser interface's ParseTag() method to notify to add skip tag
	// and ExtractValue() method of Decoder interface.
	Skip = errors.New("skip")
)
View Source
var Epsilon = 0.001

Epsilon is a threshold value that specify input floating point value is true or false when converting from float64 to bool by using FuzzyAssign().

View Source
var ErrAssignError = errors.New("assign error")

ErrAssignError is a base error that is happens in FuzzyAssign().

Functions

func BasicParseTag

func BasicParseTag(name, tagKey, tagStr, pathStr string, elemType reflect.Type) (tag any, err error)

BasicParseTag is a helper function to make tagscanner easy.

Both Encoder and Decoder should implement ParseTag() method. This is the simplest implementation of these methods.

func Decode

func Decode(dest any, tags []string, decoder Decoder) error

Decode convert from some source into struct by using tag information.

func Encode

func Encode(src any, tags []string, encoder Encoder) error

Encode convert from some source into struct by using tag information.

func FuzzyAssign

func FuzzyAssign(dest, value any) error

FuzzyAssign assigns value to variable. It converts data format to meet variable type as much as possible.

func IsPointerOfSliceOfPointerOfStruct

func IsPointerOfSliceOfPointerOfStruct(i any) bool

IsPointerOfSliceOfPointerOfStruct is a helper function that checks passed any is the form of *[]*struct.

func IsPointerOfSliceOfStruct

func IsPointerOfSliceOfStruct(i any) bool

IsPointerOfSliceOfStruct is a helper function that checks passed any is the form of *[]struct.

func IsPointerOfStruct

func IsPointerOfStruct(i any) bool

IsPointerOfStruct is a helper function that checks passed any is the form of *struct.

func NewStructInstance

func NewStructInstance(i any) (any, error)

NewStructInstance is a helper function that returns new instance based on passed input.

Even if the input has a form of *struct, *[]struct, *[]*struct, it creates new instance and returns in the form of *struct.

func Str2PrimitiveValue

func Str2PrimitiveValue(value string, elemType reflect.Type) (any, error)

Str2PrimitiveValue is a helper function that generates primitive from string like "1", "true". It is for creating primitive from string in tag.

Types

type BasicTag

type BasicTag struct {
	Name     string
	TagKey   string
	Tag      string
	Path     string
	ElemType reflect.Type
}

BasicTag is struct for convenience.

BasicParseTag() creates this instance.

type Decoder

type Decoder interface {
	Parser
	// ExtractValue is called for each field of each struct instance inside Decode().
	ExtractValue(tag any) (value any, err error)
}

Decoder is an interface that extracts value from some type and assign to struct instance.

ParseTag() is used when parsing struct tag.

type Encoder

type Encoder interface {
	Parser
	VisitField(tag, value any) (err error)
	EnterChild(tag any) (err error)
	LeaveChild(tag any) (err error)
}

Encoder is an interface that receive values from some type and create other files.

ParseTag() is used when parsing struct tag.

VisitField() is called when getting value from source struct. EnterChild() and LeaveChild)() are called when traversing struct structure.

type Errors

type Errors struct {
	Errors []error
}

func (Errors) Error

func (e Errors) Error() string

type Parser

type Parser interface {
	// ParseTag is called when parsing struct. This is called once per each struct definition
	// inside Decode() or Encode()
	// Returned value of this method is passed to ExtractValue() method call.
	//
	// name: Field name
	// tagKey: Tag key
	// tagStr: Tag string
	// pathStr: Field name for error message (it contains nested struct names)
	// elemType: Field type
	ParseTag(name, tagKey, tagStr, pathStr string, elemType reflect.Type) (tag any, err error)
}

Parser is a part of Decoder and Encoder. The user of this library will implement this method.

Jump to

Keyboard shortcuts

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