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 ¶
- Variables
- func Call(v any, method string, stub shim.ChaincodeStubInterface, args ...string) ([]any, error)
- func InputParamCounts(v any, method string) (in int)
- func IsArgOfType(v any, method string, i int, argType any) bool
- func LowerFirstChar(s string) string
- func MethodReturnsError(v any, method string) bool
- func Methods(v any) []string
- func NewValueError(arg string, t reflect.Type, errOrNil error) error
- func ParseValue(s string, t reflect.Type, stub shim.ChaincodeStubInterface) (reflect.Value, error)
- func ValidateArguments(v any, method string, stub shim.ChaincodeStubInterface, args ...string) error
- type Router
- func (r *Router) ArgCount(method string) int
- func (r *Router) AuthRequired(method string) bool
- func (r *Router) Check(stub shim.ChaincodeStubInterface, method string, args ...string) error
- func (r *Router) Function(method string) (function string)
- func (r *Router) Handlers() map[string]string
- func (r *Router) Invoke(stub shim.ChaincodeStubInterface, method string, args ...string) ([]byte, error)
- func (r *Router) IsInvoke(method string) bool
- func (r *Router) IsQuery(method string) bool
- func (r *Router) IsTransaction(method string) bool
- func (r *Router) Method(function string) (method string)
- type ValueError
Constants ¶
This section is empty.
Variables ¶
var ( ErrMethodAlreadyDefined = errors.New("pure method has already defined") ErrUnsupportedMethod = errors.New("unsupported method") ErrInvalidMethodName = errors.New("invalid method name") )
Reflect router errors.
var ( ErrIncorrectArgumentCount = errors.New("incorrect number of arguments") ErrMethodNotFound = errors.New("method not found") )
Error types.
var ErrInvalidArgumentValue = errors.New("invalid argument value")
ErrInvalidArgumentValue is returned when an argument value cannot be converted to the specified type.
Functions ¶
func Call ¶
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 ¶
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 ¶
IsArgOfType checks if the i-th argument of the specified method on value 'v' is of the given type 'argType'.
func LowerFirstChar ¶
LowerFirstChar takes a string and returns a new string with the first character converted to lowercase.
func MethodReturnsError ¶
MethodReturnsError checks if the last return value of the specified method on value 'v' is of type error.
func Methods ¶
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 ¶
NewValueError constructs an error message for invalid argument value conversions.
func ParseValue ¶
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:
- Checks if the target type is a string or a pointer to a string and handles these cases directly.
- Attempts to unmarshal the string using the types.BytesDecoder or types.StubBytesDecoder code interface if implemented.
- 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.
- Attempts to unmarshal the string using the encoding.TextUnmarshaler interface if implemented.
- Attempts to unmarshal the string using the encoding.BinaryUnmarshaler interface if implemented.
- 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 ¶
MustNewRouter creates a new Router instance with the given contract and panics if an error occurs.
func (*Router) ArgCount ¶
ArgCount returns the number of arguments the method takes (excluding the receiver).
func (*Router) AuthRequired ¶
AuthRequired indicates if the method requires authentication.
func (*Router) Function ¶
Function returns the name of the chaincode function by the specified method.
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) IsTransaction ¶
IsTransaction checks if the method is a transaction type.
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.