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")) }