reflect

package
v0.0.7 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2024 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package reflect provides functionality for routing smart contract method calls using Go reflection. This package allows dynamic invocation of contract methods based on their names and types, enabling flexible and generic handling of blockchain transactions.

Method Naming Conventions:

To properly utilize the reflection-based routing, methods in the smart contract should follow specific naming conventions. These conventions help the router identify the type of method and route it accordingly:

  • "TxMethod": Prefix "Tx" indicates that the method is a transaction and may modify the blockchain state.
  • "NBTxMethod": Prefix "NBTx" stands for "Non-Batched Transaction," indicating a transaction that should not be batched with others.
  • "QueryMethod": Prefix "Query" indicates that the method is a query, meaning it only reads data without modifying the blockchain state.

Authorization:

If a method requires authorization, the first argument passed to the method is the github.com/anoideaopen/foundation/core/types.Sender, representing the address of the user who invoked the contract.

This is automatically handled by the router, which prepends the sender to the list of arguments before the method is invoked.

Example:

type MyContract struct {}

// TxCreateUser is a transaction method that creates a new user.
// It requires authorization, so the first argument is the sender's address.
func (c *MyContract) TxCreateUser(sender *types.Sender, args []string) error {
    // Implementation
}

// QueryGetUser retrieves a user by ID without modifying the state.
// Since it's a query, it does not require authorization.
func (c *MyContract) QueryGetUser(args []string) (User, error) {
    // Implementation
}

Method Parsing and Invocation:

The core of this package revolves around the ability to dynamically parse and invoke methods on the contract using reflection. This is achieved through functions that inspect the contract's methods, validate arguments, and handle the execution.

Method Inspection and Invocation:

The package provides utilities for reflectively inspecting methods, checking argument types, and invoking methods. The Call function is a central part of this process:

func Call(v any, method string, stub shim.ChaincodeStubInterface, args ...string) ([]any, error) {
    // Implementation
}

This function first checks whether the specified method exists on the given contract object. It then verifies the number and types of the provided arguments. If the method requires authorization, the Sender argument is automatically prepended. After all checks pass, it invokes the method and returns the result.

Example:

contract := &MyContract{}
result, err := reflect.Call(contract, "TxCreateUser", stub, "senderAddress", []string{"arg1", "arg2"})
if err != nil {
    log.Fatalf("Method invocation failed: %v", err)
}

Argument Parsing:

Argument parsing is handled by the ParseValue function, which converts string arguments into their corresponding Go types. The function supports various data formats, including plain strings, JSON, and custom interfaces such as BytesDecoder and StubBytesDecoder.

Example:

func ParseValue(s string, t reflect.Type, stub shim.ChaincodeStubInterface) (reflect.Value, error) {
    // Implementation
}

This function first checks if the target type is a simple string or a pointer to a string. If not, it attempts to decode the string using various interfaces and JSON. If all decoding attempts fail, it returns a ValueError.

Interface Types:

The package also supports custom interface types for argument parsing and validation. Interfaces such as Checker and CheckerWithStub allow for additional validation and processing logic to be applied to arguments before invoking a method.

Example:

type MyType struct {
    Value string
}

func (v *MyValidator) Check() error {
    if v.Value == "" {
        return errors.New("value cannot be empty")
    }
    return nil
}

func (c *MyContract) TxCreateValidatedUser(sender *proto.Address, v *MyValidator) error {
    // Implementation
}

In this example, the Check method of the MyValidator struct is called to validate the argument before invoking TxCreateValidatedUser.

Integration with Core:

In the core package, the reflect.Router is often used as the default router for managing contract methods. When initializing a chaincode using github.com/anoideaopen/foundation/core.NewCC, a router can be provided via core.WithRouters, and if no custom routers are provided, the default reflection-based router is used.

Example of chaincode initialization:

contract := &MyContract{}
router, err := reflect.NewRouter(contract)
if err != nil {
    log.Fatal(err)
}

cc, err := core.NewCC(contract, core.WithRouters(router))
if err != nil {
    log.Fatal(err)
}
cc.Start()

Error Handling:

The package defines several custom errors to handle cases where reflection encounters issues, such as methods not being found, incorrect argument types, or validation failures. These errors ensure that any issues in method invocation are caught and handled appropriately.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrMethodAlreadyDefined = errors.New("pure method has already defined")
	ErrUnsupportedMethod    = errors.New("unsupported method")
	ErrInvalidMethodName    = errors.New("invalid method name")
)

Reflect router errors.

View Source
var (
	ErrIncorrectArgumentCount = errors.New("incorrect number of arguments")
	ErrMethodNotFound         = errors.New("method not found")
)

Error types.

View Source
var ErrInvalidArgumentValue = errors.New("invalid argument value")

ErrInvalidArgumentValue is returned when an argument value cannot be converted to the specified type.

Functions

func Call

func Call(v any, method string, stub shim.ChaincodeStubInterface, args ...string) ([]any, error)

Call invokes a specified method on a given value using reflection. The method to be invoked is identified by its name. It checks whether the specified method exists on the value 'v' and if the number of provided arguments matches the method's expected input parameters.

The function returns a slice of any type representing the output from the called method, and an error if the method is not found, the number of arguments does not match, or if an error occurs during argument conversion or method invocation.

Example:

type MyType struct {
    Data string
}

func (m *MyType) Update(data string) string {
    m.Data = data
    return fmt.Sprintf("Updated data to: %s", m.Data)
}

func main() {
    myInstance := &MyType{}
    output, err := Call(myInstance, "Update", nil, `"New data"`)
    if err != nil {
        log.Fatalf("Error invoking method: %v", err)
    }
    fmt.Println(output[0]) // Output: Updated data to: New data
}

func InputParamCounts

func InputParamCounts(v any, method string) (in int)

InputParamCounts inspects the type of the given value 'v' and returns the number of input parameters of the specified method using reflection. If the method does not exist, it returns -1.

func IsArgOfType

func IsArgOfType(v any, method string, i int, argType any) bool

IsArgOfType checks if the i-th argument of the specified method on value 'v' is of the given type 'argType'.

func LowerFirstChar

func LowerFirstChar(s string) string

LowerFirstChar takes a string and returns a new string with the first character converted to lowercase.

func MethodReturnsError

func MethodReturnsError(v any, method string) bool

MethodReturnsError checks if the last return value of the specified method on value 'v' is of type error.

func Methods

func Methods(v any) []string

Methods inspects the type of the given value 'v' using reflection and returns a slice of strings containing the names of all methods that are defined on its type. This function only considers exported methods (those starting with an uppercase letter) due to Go's visibility rules in reflection.

func NewValueError

func NewValueError(arg string, t reflect.Type, errOrNil error) error

NewValueError constructs an error message for invalid argument value conversions.

func ParseValue

func ParseValue(s string, t reflect.Type, stub shim.ChaincodeStubInterface) (reflect.Value, error)

ParseValue converts a string representation of an argument to a reflect.Value of the specified type. It attempts to unmarshal the string into the appropriate type using various methods such as JSON, encoding.TextUnmarshaler, encoding.BinaryUnmarshaler and codec.BytesDecoder. The function follows these steps:

  1. Checks if the target type is a string or a pointer to a string and handles these cases directly.
  2. Attempts to unmarshal the string using the types.BytesDecoder or types.StubBytesDecoder code interface if implemented.
  3. Attempts to unmarshal the string as JSON if it is valid JSON. Note that simple values such as numbers, booleans, and null are also valid JSON if they are represented as strings.
  4. Attempts to unmarshal the string using the encoding.TextUnmarshaler interface if implemented.
  5. Attempts to unmarshal the string using the encoding.BinaryUnmarshaler interface if implemented.
  6. Returns an ValueError if none of the above methods succeed.

func ValidateArguments

func ValidateArguments(v any, method string, stub shim.ChaincodeStubInterface, args ...string) error

ValidateArguments validates the arguments for the specified method on the given value using reflection. It checks whether the specified method exists on the value 'v' and if the number of provided arguments matches the method's expected input parameters. Additionally, it attempts to convert the string arguments into the expected types. If an argument implements the types.Checker or types.CheckerWithStub interfaces, its Check method is called (with the provided stub if available).

The function returns an error if the method is not found, the number of arguments is incorrect, or if an error occurs during argument conversion or validation.

Types

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router routes method calls to contract methods based on reflection.

func MustNewRouter

func MustNewRouter(contract any) *Router

MustNewRouter creates a new Router instance with the given contract and panics if an error occurs.

func NewRouter

func NewRouter(contract any) (*Router, error)

NewRouter creates a new Router instance with the given contract.

func (*Router) ArgCount

func (r *Router) ArgCount(method string) int

ArgCount returns the number of arguments the method takes (excluding the receiver).

func (*Router) AuthRequired

func (r *Router) AuthRequired(method string) bool

AuthRequired indicates if the method requires authentication.

func (*Router) Check

func (r *Router) Check(stub shim.ChaincodeStubInterface, method string, args ...string) error

Check validates the provided arguments for the specified method.

func (*Router) Function

func (r *Router) Function(method string) (function string)

Function returns the name of the chaincode function by the specified method.

func (*Router) Handlers

func (r *Router) Handlers() map[string]string

Handlers returns a map of method names to chaincode functions.

func (*Router) Invoke

func (r *Router) Invoke(stub shim.ChaincodeStubInterface, method string, args ...string) ([]byte, error)

Invoke calls the specified method with the provided arguments.

func (*Router) IsInvoke

func (r *Router) IsInvoke(method string) bool

IsInvoke checks if the method is an invoke type.

func (*Router) IsQuery

func (r *Router) IsQuery(method string) bool

IsQuery checks if the method is a query type.

func (*Router) IsTransaction

func (r *Router) IsTransaction(method string) bool

IsTransaction checks if the method is a transaction type.

func (*Router) Method

func (r *Router) Method(function string) (method string)

Method retrieves the method associated with the specified chaincode function.

type ValueError

type ValueError struct {
	// contains filtered or unexported fields
}

ValueError is a custom error type that wraps both external and internal errors, providing additional context about the argument and the target type involved in the error.

func (ValueError) Error

func (e ValueError) Error() string

Error returns a formatted error message indicating the conversion failure.

func (ValueError) Is

func (e ValueError) Is(target error) bool

Is checks if the target error matches the internal error.

func (ValueError) Unwrap

func (e ValueError) Unwrap() error

Unwrap returns the external error, if any.

Jump to

Keyboard shortcuts

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