174 lines
5.3 KiB
Go
174 lines
5.3 KiB
Go
package appconfig
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
gogoproto "github.com/cosmos/gogoproto/proto"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
protov2 "google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/types/dynamicpb"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"cosmossdk.io/depinject"
|
|
"cosmossdk.io/depinject/appconfig/v1alpha1"
|
|
internal "cosmossdk.io/depinject/internal/appconfig"
|
|
)
|
|
|
|
// LoadJSON loads an app config in JSON format.
|
|
func LoadJSON(bz []byte) depinject.Config {
|
|
// in order to avoid a direct dependency on api types, but in order to also be able to support
|
|
// either gogo or google.golang.org/protobuf types, we use protojson and dynamicpb to unmarshal
|
|
// from JSON
|
|
resolver := gogoproto.HybridResolver
|
|
desc, err := resolver.FindDescriptorByName(protoreflect.FullName(gogoproto.MessageName(&v1alpha1.Config{})))
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
config := dynamicpb.NewMessage(desc.(protoreflect.MessageDescriptor))
|
|
err = protojson.UnmarshalOptions{
|
|
Resolver: dynamicTypeResolver{resolver: gogoproto.HybridResolver},
|
|
}.Unmarshal(bz, config)
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
return Compose(config)
|
|
}
|
|
|
|
// LoadYAML loads an app config in YAML format.
|
|
func LoadYAML(bz []byte) depinject.Config {
|
|
j, err := yaml.YAMLToJSON(bz)
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
return LoadJSON(j)
|
|
}
|
|
|
|
// WrapAny marshals a proto message into a proto Any instance
|
|
func WrapAny(config gogoproto.Message) *anypb.Any {
|
|
pbz, err := gogoproto.Marshal(config)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &anypb.Any{
|
|
TypeUrl: "/" + gogoproto.MessageName(config),
|
|
Value: pbz,
|
|
}
|
|
}
|
|
|
|
// Compose composes an app config into a container option by resolving
|
|
// the required modules and composing their options. appConfig should be an instance
|
|
// of cosmos.app.v1alpha1.Config (it doesn't matter whether you use gogo proto or
|
|
// google.golang.org/protobuf types).
|
|
func Compose(appConfig gogoproto.Message) depinject.Config {
|
|
appConfigConcrete, ok := appConfig.(*v1alpha1.Config)
|
|
if !ok {
|
|
// we convert any other proto type that was passed (such as an api module type) to the concrete
|
|
// type we're using here
|
|
appConfigConcrete = &v1alpha1.Config{}
|
|
bz, err := gogoproto.Marshal(appConfig)
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
err = gogoproto.Unmarshal(bz, appConfigConcrete)
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
}
|
|
|
|
opts := []depinject.Config{
|
|
depinject.Supply(appConfig),
|
|
}
|
|
|
|
modules, err := internal.ModulesByModuleTypeName()
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
for _, module := range appConfigConcrete.Modules {
|
|
if module.Name == "" {
|
|
return depinject.Error(errors.New("module is missing name"))
|
|
}
|
|
|
|
if module.Config == nil {
|
|
return depinject.Error(fmt.Errorf("module %q is missing a config object", module.Name))
|
|
}
|
|
|
|
msgName := module.Config.TypeUrl
|
|
// strip type URL prefix
|
|
if slashIdx := strings.LastIndex(msgName, "/"); slashIdx >= 0 {
|
|
msgName = msgName[slashIdx+1:]
|
|
}
|
|
if msgName == "" {
|
|
return depinject.Error(fmt.Errorf("module %q is missing a type URL", module.Name))
|
|
}
|
|
|
|
init, ok := modules[msgName]
|
|
if !ok {
|
|
if msgDesc, err := gogoproto.HybridResolver.FindDescriptorByName(protoreflect.FullName(msgName)); err == nil {
|
|
modDesc, err := internal.GetModuleDescriptor(msgDesc)
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
if modDesc == nil {
|
|
return depinject.Error(fmt.Errorf("no module registered for type URL %s and that protobuf type does not have the option %s\n\n%s",
|
|
module.Config.TypeUrl, v1alpha1.E_Module.Name, dumpRegisteredModules(modules)))
|
|
}
|
|
|
|
return depinject.Error(fmt.Errorf("no module registered for type URL %s, did you forget to import %s: find more information on how to make a module ready for app wiring: https://docs.cosmos.network/main/building-modules/depinject\n\n%s",
|
|
module.Config.TypeUrl, modDesc.GoImport, dumpRegisteredModules(modules)))
|
|
}
|
|
}
|
|
|
|
var config gogoproto.Message
|
|
if configInit, ok := init.ConfigProtoMessage.(protov2.Message); ok {
|
|
config = configInit.ProtoReflect().Type().New().Interface().(gogoproto.Message)
|
|
} else {
|
|
config = reflect.New(init.ConfigGoType.Elem()).Interface().(gogoproto.Message)
|
|
}
|
|
// as of gogo v1.5.0 this should work with either gogoproto or golang v2 proto
|
|
err = gogoproto.Unmarshal(module.Config.Value, config)
|
|
if err != nil {
|
|
return depinject.Error(err)
|
|
}
|
|
|
|
opts = append(opts, depinject.Supply(config))
|
|
|
|
for _, provider := range init.Providers {
|
|
opts = append(opts, depinject.ProvideInModule(module.Name, provider))
|
|
}
|
|
|
|
for _, invoker := range init.Invokers {
|
|
opts = append(opts, depinject.InvokeInModule(module.Name, invoker))
|
|
}
|
|
|
|
for _, binding := range module.GolangBindings {
|
|
opts = append(opts, depinject.BindInterfaceInModule(module.Name, binding.InterfaceType, binding.Implementation))
|
|
}
|
|
}
|
|
|
|
for _, binding := range appConfigConcrete.GolangBindings {
|
|
opts = append(opts, depinject.BindInterface(binding.InterfaceType, binding.Implementation))
|
|
}
|
|
|
|
return depinject.Configs(opts...)
|
|
}
|
|
|
|
func dumpRegisteredModules(modules map[string]*internal.ModuleInitializer) string {
|
|
var mods []string
|
|
for name := range modules {
|
|
mods = append(mods, " "+name)
|
|
}
|
|
return fmt.Sprintf("registered modules are:\n%s", strings.Join(mods, "\n"))
|
|
}
|