go-kratos / kratos

Your ultimate Go microservices framework for the cloud-native era.
https://go-kratos.dev
MIT License
23.43k stars 4.01k forks source link

[BUG]Wrong parsing of config, when using config.Scan #3439

Closed godcong closed 1 month ago

godcong commented 1 month ago

What happened:

The default output of toml is configured in UpperCase, which is converted to map=>json during transmission. But proto's json supports the format of snake_case. Eventually the json=>Config parsing fails.

flagconf, _ = filepath.Abs(flagconf)
    fmt.Println("load config at:", flagconf)

    client, err := api.NewClient(&api.Config{
        Address: "host:8500",
    })
    if err != nil {
        panic(err)
    }
    fs := file.NewSource(flagconf)
    kvs, err := fs.Load()
    if err != nil {
        panic(err)
    }

    for _, kv := range kvs {
        fmt.Println("key:", kv.Key)
        _, err := client.KV().Put(&api.KVPair{Key: "configs/" + kv.Key, Value: kv.Value}, nil)
        if err != nil {
            panic(err)
        }
    }

    //consul.WithPath(testPath)
    source, err := consul.New(client, consul.WithPath("configs/bootstrap.toml"))
    if err != nil {
        panic(err)
    }
    c := config.New(
        //config.WithSource(file.NewSource(flagconf), source),
        config.WithSource(source),
        //config.WithResolveActualTypes(true),
        config.WithDecoder(codec.SourceDecoder),
    )
    defer c.Close()
    if err := c.Load(); err != nil {
        panic(err)
    }

    var bc conf.Bootstrap
    if err := c.Scan(&bc); err != nil {
        panic(err)
    }

bootstrap.toml

ServiceName = "helloworld"
Version = "v1.0.0"
CryptoType = "argon2"
Id = ""

[Server]
[Server.Http]
Network = "0.0.0.0"
Addr = "8000"
UseTls = false
CertFile = ""
KeyFile = ""
[Server.Http.Timeout]
Seconds = 180
Nanos = 0
[Server.Http.ShutdownTimeout]
Seconds = 180
Nanos = 0
[Server.Http.ReadTimeout]
Seconds = 180
Nanos = 0
[Server.Http.WriteTimeout]
Seconds = 180
Nanos = 0
[Server.Http.IdleTimeout]
Seconds = 180
Nanos = 0
[Server.Grpc]
Network = "0.0.0.0"
Addr = "9000"
UseTls = false
CertFile = ""
KeyFile = ""
[Server.Grpc.Timeout]
Seconds = 180
Nanos = 0
[Server.Grpc.ShutdownTimeout]
Seconds = 180
Nanos = 0
[Server.Grpc.ReadTimeout]
Seconds = 180
Nanos = 0
[Server.Grpc.WriteTimeout]
Seconds = 180
Nanos = 0
[Server.Grpc.IdleTimeout]
Seconds = 180
Nanos = 0

proto

syntax = "proto3";
package kratos.api;

option go_package = "internal/mods/helloworld/conf;conf";

import "google/protobuf/duration.proto";
import "validate/validate.proto";

message Bootstrap {
  string service_name = 1 [json_name = "service_name"];
  string version = 2 [json_name = "version"];
  string crypto_type = 3 [json_name = "crypto_type"];
  Server server = 4;
  Data data = 5;
  Settings settings = 6;
  string id = 99 ;
}

What you expected to happen:

Converts profiles properly

How to reproduce it (as minimally and precisely as possible):

Anything else we need to know?:

Environment:

shenqidebaozi commented 1 month ago

I don't quite understand what the specific problem is

godcong commented 1 month ago

@shenqidebaozi Added an example of yml and toml at: https://github.com/godcong/example

github.com\go-kratos\kratos\v2@v2.8.1\config\reader.go:

1729627669602 This is mainly the case when the source is specialized to target v. 这种情况主要发生在将源代码专门化为目标v 时。

This is because the intermediate values are stored in a map. Therefore, they are not subject to the tag specification. My configuration file is in .toml format, and it converts the data to UpperCase like this. 我的配置文件是 .toml 格式,它将数据转换为 UpperCase 这样的格式。

example with value ServiceName = "helloworld"ServiceName = "helloworld"为例子

  1. defined in the proto file:
  2. 在proto文件中定义:
  string service_name = 1 [json_name = "service_name"];
  1. generated to go will like this:
  2. 生成后的go会像这样
   ServiceName string `json:"service_name"`

If the source is in a non-json format. Then it will be parsed according to the default parsing method for that format. 如果源是非 json 格式。 则将根据该格式的默认解析方法进行解析。

If the configuration is UpperCase in the .toml file, the fields are parsed into map["ServiceName"]"helloworld" Eventually, it will become a json source during the pass. 如果配置的 .toml 文件中的是 UpperCase这样,字段将被解析为 map[“ServiceName”]“helloworld” 最终,它将在传递过程中变成 json 源。

but the content will be 但内容将是这样

{
    "ServiceName": "helloworld"
}

But the tag of the json is service_name. Finally, it can't be parsed to the go file correctly. 但 json 的 tagservice_name。 最后,它就不能被正确地解析到 go 文件中。

So I think it should be more than just unmarshalJSON. It should be decoded in the format of the source file. 因此,我认为应该不仅仅是 unmarshalJSON。 它应该以源文件的格式解码。

Of course, if the configuration with service_name in the file, it can be resolved normally. But I don't have the ability to change the case. I just saved the bootstrap configuration as a file. 当然,如果文件中的配置包含 service_name,就可以正常解析。 但我特意去改变这个。 我只是将引导配置保存为一个文件。

czyt commented 1 month ago

you need two step

  1. register toml codec in kratos simpe toml codec
    
    import (
    "github.com/BurntSushi/toml"
    "github.com/go-kratos/kratos/v2/encoding"
    )

// Name is the name registered for the xml codec. const Name = "toml"

func init() { encoding.RegisterCodec(codec{}) }

// codec is a Codec implementation with xml. type codec struct{}

func (codec) Marshal(v interface{}) ([]byte, error) { return toml.Marshal(v) }

func (codec) Unmarshal(data []byte, v interface{}) error { return toml.Unmarshal(data, v) }

func (codec) Name() string { return Name }


2. define your resolver see more https://go-kratos.dev/docs/component/config#6%E9%85%8D%E7%BD%AE%E5%A4%84%E7%90%86resolver
![image](https://github.com/user-attachments/assets/e428f280-e0e9-4378-9a2b-095dcb6886a8)
godcong commented 1 month ago

@czyt 意思是,我实际需要一个自定义的resolver将输入的配置转换成对的map格式对吧? means, I actually need a custom resolver to convert the input configuration into a map format, right?

kratos-ci-bot commented 1 month ago

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


@czyt means, I actually need a custom resolver to convert the input configuration into a map format, right?

czyt commented 1 month ago

@czyt 意思是,我实际需要一个自定义的resolver将输入的配置转换成对的map格式对吧? means, I actually need a custom resolver to convert the input configuration into a map format, right?

yes,you can try

godcong commented 1 month ago

I tried to write my own Resolver, but I found a problem during the implementation. The main problem is that the input doesn't know what format my source format is, so it can't determine if it needs to be updated.

The idea is to update the fields according to the format after decoding. Then return the updated map

godcong commented 1 month ago

This doesn't solve the problem that yaml defaults to all lowercase... Changed the initialization logic... The local one is converted to a proto object. That's it for now...