chore: return error for untested fields in tools.yaml (#239)

This only checks within `SourceConfig`, `ToolConfig`, and
`AuthSourceConfig`.

Error when an unknown field is provided:
`2025-01-27T22:43:46.988401-08:00 ERROR "unable to parse tool file at
\"tools.yaml\": unable to parse as \"cloud-sql-postgres\": [2:1] unknown
field \"extra\"\n 1 | database: test_database\n> 2 | extra: here\n ^\n 3
| instance: toolbox-cloudsql\n 4 | kind: cloud-sql-postgres\n 5 |
password: postgres\n 6 | "`

Error when a required field is not provided:
`2025-01-27T17:49:47.584846-08:00 ERROR "unable to parse tool file at
\"tools.yaml\": validation failed: Key: 'Config.Region' Error:Field
validation for 'Region' failed on the 'required' tag"`

---------

Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com>
This commit is contained in:
Yuan
2025-02-03 15:30:27 -08:00
committed by GitHub
parent d2efd8c11d
commit a0ac5334d1
35 changed files with 824 additions and 208 deletions

View File

@@ -30,9 +30,9 @@ var _ auth.AuthSourceConfig = Config{}
// Auth source configuration
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
ClientID string `yaml:"clientId"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
ClientID string `yaml:"clientId" validate:"required"`
}
// Returns the auth source kind

View File

@@ -135,76 +135,84 @@ func (c *SourceConfigs) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
for name, u := range raw {
var k struct {
Kind string `yaml:"kind"`
// Unmarshal to a general type that ensure it capture all fields
var v map[string]any
if err := u.Unmarshal(&v); err != nil {
return fmt.Errorf("unable to unmarshal %q: %w", name, err)
}
err := u.Unmarshal(&k)
kind, ok := v["kind"]
if !ok {
return fmt.Errorf("missing 'kind' field for %q", name)
}
dec, err := util.NewStrictDecoder(v)
if err != nil {
return fmt.Errorf("missing 'kind' field for %q", k)
return fmt.Errorf("error creating decoder: %w", err)
}
switch k.Kind {
switch kind {
case alloydbpgsrc.SourceKind:
actual := alloydbpgsrc.Config{Name: name, IPType: "public"}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case cloudsqlpgsrc.SourceKind:
actual := cloudsqlpgsrc.Config{Name: name, IPType: "public"}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case postgressrc.SourceKind:
actual := postgressrc.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case cloudsqlmysqlsrc.SourceKind:
actual := cloudsqlmysqlsrc.Config{Name: name, IPType: "public"}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case mysqlsrc.SourceKind:
actual := mysqlsrc.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case spannersrc.SourceKind:
actual := spannersrc.Config{Name: name, Dialect: "googlesql"}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case neo4jrc.SourceKind:
actual := neo4jrc.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case cloudsqlmssqlsrc.SourceKind:
actual := cloudsqlmssqlsrc.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
actual := cloudsqlmssqlsrc.Config{Name: name, IPType: "public"}
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case mssqlsrc.SourceKind:
actual := mssqlsrc.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case dgraphsrc.SourceKind:
actual := dgraphsrc.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
default:
return fmt.Errorf("%q is not a valid kind of data source", k.Kind)
return fmt.Errorf("%q is not a valid kind of data source", kind)
}
}
@@ -226,22 +234,29 @@ func (c *AuthSourceConfigs) UnmarshalYAML(unmarshal func(interface{}) error) err
}
for name, u := range raw {
var k struct {
Kind string `yaml:"kind"`
var v map[string]any
if err := u.Unmarshal(&v); err != nil {
return fmt.Errorf("unable to unmarshal %q: %w", name, err)
}
err := u.Unmarshal(&k)
kind, ok := v["kind"]
if !ok {
return fmt.Errorf("missing 'kind' field for %q", name)
}
dec, err := util.NewStrictDecoder(v)
if err != nil {
return fmt.Errorf("missing 'kind' field for %q", k)
return fmt.Errorf("error creating decoder: %w", err)
}
switch k.Kind {
switch kind {
case google.AuthSourceKind:
actual := google.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
default:
return fmt.Errorf("%q is not a valid kind of auth source", k.Kind)
return fmt.Errorf("%q is not a valid kind of auth source", kind)
}
}
return nil
@@ -262,52 +277,59 @@ func (c *ToolConfigs) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
for name, u := range raw {
var k struct {
Kind string `yaml:"kind"`
var v map[string]any
if err := u.Unmarshal(&v); err != nil {
return fmt.Errorf("unable to unmarshal %q: %w", name, err)
}
err := u.Unmarshal(&k)
if err != nil {
kind, ok := v["kind"]
if !ok {
return fmt.Errorf("missing 'kind' field for %q", name)
}
switch k.Kind {
dec, err := util.NewStrictDecoder(v)
if err != nil {
return fmt.Errorf("error creating decoder: %w", err)
}
switch kind {
case postgressql.ToolKind:
actual := postgressql.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case mysqlsql.ToolKind:
actual := mysqlsql.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case spanner.ToolKind:
actual := spanner.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case neo4jtool.ToolKind:
actual := neo4jtool.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case mssqlsql.ToolKind:
actual := mssqlsql.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
case dgraph.ToolKind:
actual := dgraph.Config{Name: name}
if err := u.Unmarshal(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", k.Kind, err)
if err := dec.Decode(&actual); err != nil {
return fmt.Errorf("unable to parse as %q: %w", kind, err)
}
(*c)[name] = actual
default:
return fmt.Errorf("%q is not a valid kind of tool", k.Kind)
return fmt.Errorf("%q is not a valid kind of tool", kind)
}
}

View File

@@ -33,16 +33,16 @@ const SourceKind string = "alloydb-postgres"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Project string `yaml:"project"`
Region string `yaml:"region"`
Cluster string `yaml:"cluster"`
Instance string `yaml:"instance"`
IPType sources.IPType `yaml:"ipType"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Cluster string `yaml:"cluster" validate:"required"`
Instance string `yaml:"instance" validate:"required"`
IPType sources.IPType `yaml:"ipType" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -42,6 +42,8 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
cluster: my-cluster
instance: my-instance
database: my_db
user: my_user
password: my_pass
`,
want: map[string]sources.SourceConfig{
"my-pg-instance": alloydbpg.Config{
@@ -53,6 +55,8 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
Instance: "my-instance",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -68,6 +72,8 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
instance: my-instance
ipType: Public
database: my_db
user: my_user
password: my_pass
`,
want: map[string]sources.SourceConfig{
"my-pg-instance": alloydbpg.Config{
@@ -79,6 +85,8 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
Instance: "my-instance",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -94,6 +102,8 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
instance: my-instance
ipType: private
database: my_db
user: my_user
password: my_pass
`,
want: map[string]sources.SourceConfig{
"my-pg-instance": alloydbpg.Config{
@@ -105,6 +115,8 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
Instance: "my-instance",
IPType: "private",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -126,10 +138,11 @@ func TestParseFromYamlAlloyDBPg(t *testing.T) {
}
}
func FailParseFromYamlAlloyDBPg(t *testing.T) {
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid ipType",
@@ -143,7 +156,42 @@ func FailParseFromYamlAlloyDBPg(t *testing.T) {
instance: my-instance
ipType: fail
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"alloydb-postgres\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
in: `
sources:
my-pg-instance:
kind: alloydb-postgres
project: my-project
region: my-region
cluster: my-cluster
instance: my-instance
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"alloydb-postgres\": [3:1] unknown field \"foo\"\n 1 | cluster: my-cluster\n 2 | database: my_db\n> 3 | foo: bar\n ^\n 4 | instance: my-instance\n 5 | kind: alloydb-postgres\n 6 | password: my_pass\n 7 | ",
},
{
desc: "missing required field",
in: `
sources:
my-pg-instance:
kind: alloydb-postgres
region: my-region
cluster: my-cluster
instance: my-instance
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"alloydb-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {
@@ -154,7 +202,11 @@ func FailParseFromYamlAlloyDBPg(t *testing.T) {
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail: %s", err)
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}

View File

@@ -33,16 +33,16 @@ var _ sources.SourceConfig = Config{}
type Config struct {
// Cloud SQL MSSQL configs
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Project string `yaml:"project"`
Region string `yaml:"region"`
Instance string `yaml:"instance"`
IPAddress string `yaml:"ipAddress"`
IPType string `yaml:"ipType"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Instance string `yaml:"instance" validate:"required"`
IPAddress string `yaml:"ipAddress" validate:"required"`
IPType sources.IPType `yaml:"ipType" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {
@@ -52,7 +52,7 @@ func (r Config) SourceConfigKind() string {
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
// Initializes a Cloud SQL MSSQL source
db, err := initCloudSQLMssqlConnection(ctx, tracer, r.Name, r.Project, r.Region, r.Instance, r.IPAddress, r.IPType, r.User, r.Password, r.Database)
db, err := initCloudSQLMssqlConnection(ctx, tracer, r.Name, r.Project, r.Region, r.Instance, r.IPAddress, r.IPType.String(), r.User, r.Password, r.Database)
if err != nil {
return nil, fmt.Errorf("unable to create db connection: %w", err)
}

View File

@@ -41,7 +41,8 @@ func TestParseFromYamlCloudSQLMssql(t *testing.T) {
instance: my-instance
database: my_db
ipAddress: localhost
ipType: public
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-instance": cloudsqlmssql.Config{
@@ -53,6 +54,8 @@ func TestParseFromYamlCloudSQLMssql(t *testing.T) {
IPAddress: "localhost",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -74,3 +77,77 @@ func TestParseFromYamlCloudSQLMssql(t *testing.T) {
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid ipType",
in: `
sources:
my-instance:
kind: cloud-sql-mssql
project: my-project
region: my-region
instance: my-instance
ipType: fail
database: my_db
ipAddress: localhost
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mssql\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
in: `
sources:
my-instance:
kind: cloud-sql-mssql
project: my-project
region: my-region
instance: my-instance
database: my_db
ipAddress: localhost
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"cloud-sql-mssql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | ipAddress: localhost\n 5 | kind: cloud-sql-mssql\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-instance:
kind: cloud-sql-mssql
region: my-region
instance: my-instance
database: my_db
ipAddress: localhost
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mssql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -32,15 +32,15 @@ const SourceKind string = "cloud-sql-mysql"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Project string `yaml:"project"`
Region string `yaml:"region"`
Instance string `yaml:"instance"`
IPType sources.IPType `yaml:"ipType"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Instance string `yaml:"instance" validate:"required"`
IPType sources.IPType `yaml:"ipType" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -40,6 +40,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-mysql-instance": cloudsqlmysql.Config{
@@ -50,6 +52,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
Instance: "my-instance",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -64,6 +68,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
instance: my-instance
ipType: Public
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-mysql-instance": cloudsqlmysql.Config{
@@ -74,6 +80,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
Instance: "my-instance",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -88,6 +96,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
instance: my-instance
ipType: private
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-mysql-instance": cloudsqlmysql.Config{
@@ -98,6 +108,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
Instance: "my-instance",
IPType: "private",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -120,10 +132,11 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
}
func FailParseFromYamlCloudSQLMySQL(t *testing.T) {
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid ipType",
@@ -136,7 +149,40 @@ func FailParseFromYamlCloudSQLMySQL(t *testing.T) {
instance: my-instance
ipType: fail
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mysql\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
in: `
sources:
my-mysql-instance:
kind: cloud-sql-mysql
project: my-project
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"cloud-sql-mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-mysql\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-mysql-instance:
kind: cloud-sql-mysql
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-mysql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {
@@ -147,7 +193,11 @@ func FailParseFromYamlCloudSQLMySQL(t *testing.T) {
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail: %s", err)
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}

View File

@@ -32,15 +32,15 @@ const SourceKind string = "cloud-sql-postgres"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Project string `yaml:"project"`
Region string `yaml:"region"`
Instance string `yaml:"instance"`
IPType sources.IPType `yaml:"ipType"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Instance string `yaml:"instance" validate:"required"`
IPType sources.IPType `yaml:"ipType" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -40,6 +40,8 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-pg-instance": cloudsqlpg.Config{
@@ -50,6 +52,8 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
Instance: "my-instance",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -64,6 +68,8 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
instance: my-instance
ipType: Public
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-pg-instance": cloudsqlpg.Config{
@@ -74,6 +80,8 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
Instance: "my-instance",
IPType: "public",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -88,6 +96,8 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
instance: my-instance
ipType: private
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-pg-instance": cloudsqlpg.Config{
@@ -98,6 +108,8 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
Instance: "my-instance",
IPType: "private",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -120,10 +132,11 @@ func TestParseFromYamlCloudSQLPg(t *testing.T) {
}
func FailParseFromYamlCloudSQLPg(t *testing.T) {
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid ipType",
@@ -136,7 +149,40 @@ func FailParseFromYamlCloudSQLPg(t *testing.T) {
instance: my-instance
ipType: fail
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-postgres\": ipType invalid: must be one of \"public\", or \"private\"",
},
{
desc: "extra field",
in: `
sources:
my-pg-instance:
kind: cloud-sql-postgres
project: my-project
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"cloud-sql-postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-postgres\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-pg-instance:
kind: cloud-sql-postgres
region: my-region
instance: my-instance
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"cloud-sql-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
},
}
for _, tc := range tcs {
@@ -147,7 +193,11 @@ func FailParseFromYamlCloudSQLPg(t *testing.T) {
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail: %s", err)
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}

View File

@@ -50,9 +50,9 @@ type DgraphClient struct {
}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
DgraphUrl string `yaml:"dgraphUrl"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
DgraphUrl string `yaml:"dgraphUrl" validate:"required"`
User string `yaml:"user"`
Password string `yaml:"password"`
Namespace uint64 `yaml:"namespace"`
@@ -108,7 +108,7 @@ func initDgraphHttpClient(ctx context.Context, tracer trace.Tracer, r Config) (*
hc := &DgraphClient{
httpClient: &http.Client{},
baseUrl: r.DgraphUrl,
baseUrl: r.DgraphUrl,
HttpToken: &HttpToken{
UserId: r.User,
Namespace: r.Namespace,

View File

@@ -54,6 +54,22 @@ func TestParseFromYamlDgraph(t *testing.T) {
},
},
},
{
desc: "basic example minimal field",
in: `
sources:
my-dgraph-instance:
kind: dgraph
dgraphUrl: https://localhost:8080
`,
want: server.SourceConfigs{
"my-dgraph-instance": dgraph.Config{
Name: "my-dgraph-instance",
Kind: dgraph.SourceKind,
DgraphUrl: "https://localhost:8080",
},
},
},
}
for _, tc := range tcs {
@@ -74,3 +90,48 @@ func TestParseFromYamlDgraph(t *testing.T) {
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "extra field",
in: `
sources:
my-dgraph-instance:
kind: dgraph
dgraphUrl: https://localhost:8080
foo: bar
`,
err: "unable to parse as \"dgraph\": [2:1] unknown field \"foo\"\n 1 | dgraphUrl: https://localhost:8080\n> 2 | foo: bar\n ^\n 3 | kind: dgraph",
},
{
desc: "missing required field",
in: `
sources:
my-dgraph-instance:
kind: dgraph
`,
err: "unable to parse as \"dgraph\": Key: 'Config.DgraphUrl' Error:Field validation for 'DgraphUrl' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -31,13 +31,13 @@ var _ sources.SourceConfig = Config{}
type Config struct {
// Cloud SQL MSSQL configs
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Host string `yaml:"host"`
Port string `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Host string `yaml:"host" validate:"required"`
Port string `yaml:"port" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -39,6 +39,8 @@ func TestParseFromYamlMssql(t *testing.T) {
host: 0.0.0.0
port: my-port
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-mssql-instance": mssql.Config{
@@ -47,6 +49,8 @@ func TestParseFromYamlMssql(t *testing.T) {
Host: "0.0.0.0",
Port: "my-port",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -68,3 +72,56 @@ func TestParseFromYamlMssql(t *testing.T) {
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "extra field",
in: `
sources:
my-mssql-instance:
kind: mssql
host: 0.0.0.0
port: my-port
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"mssql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: mssql\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-mssql-instance:
kind: mssql
host: 0.0.0.0
port: my-port
database: my_db
user: my_user
`,
err: "unable to parse as \"mssql\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -30,13 +30,13 @@ const SourceKind string = "mysql"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Host string `yaml:"host"`
Port string `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Host string `yaml:"host" validate:"required"`
Port string `yaml:"port" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -39,6 +39,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
host: 0.0.0.0
port: my-host
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-mysql-instance": mysql.Config{
@@ -47,6 +49,8 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
Host: "0.0.0.0",
Port: "my-host",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -68,3 +72,56 @@ func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "extra field",
in: `
sources:
my-mysql-instance:
kind: mysql
host: 0.0.0.0
port: my-host
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: mysql\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-mysql-instance:
kind: mysql
port: my-host
database: my_db
user: my_user
password: my_pass
`,
err: "unable to parse as \"mysql\": Key: 'Config.Host' Error:Field validation for 'Host' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -29,12 +29,12 @@ const SourceKind string = "neo4j"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Uri string `yaml:"uri"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Uri string `yaml:"uri" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -38,6 +38,8 @@ func TestParseFromYamlNeo4j(t *testing.T) {
kind: neo4j
uri: neo4j+s://my-host:7687
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-neo4j-instance": neo4j.Config{
@@ -45,6 +47,8 @@ func TestParseFromYamlNeo4j(t *testing.T) {
Kind: neo4j.SourceKind,
Uri: "neo4j+s://my-host:7687",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -66,3 +70,54 @@ func TestParseFromYamlNeo4j(t *testing.T) {
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "extra field",
in: `
sources:
my-neo4j-instance:
kind: neo4j
uri: neo4j+s://my-host:7687
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"neo4j\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | kind: neo4j\n 4 | password: my_pass\n 5 | uri: neo4j+s://my-host:7687\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-neo4j-instance:
kind: neo4j
uri: neo4j+s://my-host:7687
database: my_db
user: my_user
`,
err: "unable to parse as \"neo4j\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -29,13 +29,13 @@ const SourceKind string = "postgres"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Host string `yaml:"host"`
Port string `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Host string `yaml:"host" validate:"required"`
Port string `yaml:"port" validate:"required"`
User string `yaml:"user" validate:"required"`
Password string `yaml:"password" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -39,6 +39,8 @@ func TestParseFromYamlPostgres(t *testing.T) {
host: my-host
port: 0.0.0.0
database: my_db
user: my_user
password: my_pass
`,
want: server.SourceConfigs{
"my-pg-instance": postgres.Config{
@@ -47,6 +49,8 @@ func TestParseFromYamlPostgres(t *testing.T) {
Host: "my-host",
Port: "0.0.0.0",
Database: "my_db",
User: "my_user",
Password: "my_pass",
},
},
},
@@ -68,3 +72,56 @@ func TestParseFromYamlPostgres(t *testing.T) {
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "extra field",
in: `
sources:
my-pg-instance:
kind: postgres
host: my-host
port: 0.0.0.0
database: my_db
user: my_user
password: my_pass
foo: bar
`,
err: "unable to parse as \"postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: my-host\n 4 | kind: postgres\n 5 | password: my_pass\n 6 | ",
},
{
desc: "missing required field",
in: `
sources:
my-pg-instance:
kind: postgres
host: my-host
port: 0.0.0.0
database: my_db
user: my_user
`,
err: "unable to parse as \"postgres\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -30,12 +30,12 @@ const SourceKind string = "spanner"
var _ sources.SourceConfig = Config{}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Project string `yaml:"project"`
Instance string `yaml:"instance"`
Dialect sources.Dialect `yaml:"dialect"`
Database string `yaml:"database"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Project string `yaml:"project" validate:"required"`
Instance string `yaml:"instance" validate:"required"`
Dialect sources.Dialect `yaml:"dialect" validate:"required"`
Database string `yaml:"database" validate:"required"`
}
func (r Config) SourceConfigKind() string {

View File

@@ -115,10 +115,11 @@ func TestParseFromYamlSpannerDb(t *testing.T) {
}
func FailParseFromYamlSpanner(t *testing.T) {
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "invalid dialect",
@@ -128,9 +129,34 @@ func FailParseFromYamlSpanner(t *testing.T) {
kind: spanner
project: my-project
instance: my-instance
dialect: fail
dialect: fail
database: my_db
`,
err: "unable to parse as \"spanner\": dialect invalid: must be one of \"googlesql\", or \"postgresql\"",
},
{
desc: "extra field",
in: `
sources:
my-spanner-instance:
kind: spanner
project: my-project
instance: my-instance
database: my_db
foo: bar
`,
err: "unable to parse as \"spanner\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: spanner\n 5 | project: my-project",
},
{
desc: "missing required field",
in: `
sources:
my-spanner-instance:
kind: spanner
project: my-project
instance: my-instance
`,
err: "unable to parse as \"spanner\": Key: 'Config.Database' Error:Field validation for 'Database' failed on the 'required' tag",
},
}
for _, tc := range tcs {
@@ -141,7 +167,11 @@ func FailParseFromYamlSpanner(t *testing.T) {
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail: %s", err)
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}

View File

@@ -35,11 +35,11 @@ var _ compatibleSource = &dgraph.Source{}
var compatibleSources = [...]string{dgraph.SourceKind}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Source string `yaml:"source"`
Description string `yaml:"description"`
Statement string `yaml:"statement"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
IsQuery bool `yaml:"isQuery"`
Timeout string `yaml:"timeout"`
Parameters tools.Parameters `yaml:"parameters"`

View File

@@ -39,11 +39,11 @@ var _ compatibleSource = &mssql.Source{}
var compatibleSources = [...]string{cloudsqlmssql.SourceKind, mssql.SourceKind}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Source string `yaml:"source"`
Description string `yaml:"description"`
Statement string `yaml:"statement"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
}

View File

@@ -39,11 +39,11 @@ var _ compatibleSource = &mysql.Source{}
var compatibleSources = [...]string{cloudsqlmysql.SourceKind, mysql.SourceKind}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Source string `yaml:"source"`
Description string `yaml:"description"`
Statement string `yaml:"statement"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
}

View File

@@ -39,11 +39,11 @@ var _ compatibleSource = &neo4jsc.Source{}
var compatibleSources = [...]string{neo4jsc.SourceKind}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Source string `yaml:"source"`
Description string `yaml:"description"`
Statement string `yaml:"statement"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
Parameters tools.Parameters `yaml:"parameters"`
}

View File

@@ -84,16 +84,16 @@ func (p ParamValues) AsMapByOrderedKeys() map[string]interface{} {
// Input: {"role": "admin", "$age": 30}
// Output: {"$role": "admin", "$age": 30}
func (p ParamValues) AsMapWithDollarPrefix() map[string]interface{} {
params := make(map[string]interface{})
params := make(map[string]interface{})
for _, param := range p {
key := param.Name
if !strings.HasPrefix(key, "$") {
for _, param := range p {
key := param.Name
if !strings.HasPrefix(key, "$") {
key = "$" + key
}
params[key] = param.Value
}
return params
params[key] = param.Value
}
return params
}
func parseFromAuthSource(paramAuthSources []ParamAuthSource, claimsMap map[string]map[string]any) (any, error) {
@@ -178,44 +178,50 @@ func (c *Parameters) UnmarshalYAML(unmarshal func(interface{}) error) error {
// parseParamFromDelayedUnmarshaler is a helper function that is required to parse
// parameters because there are multiple different types
func parseParamFromDelayedUnmarshaler(u *util.DelayedUnmarshaler) (Parameter, error) {
var p CommonParameter
var p map[string]any
err := u.Unmarshal(&p)
if err != nil {
return nil, fmt.Errorf("parameter missing required fields: %w", err)
return nil, fmt.Errorf("error parsing parameters: %w", err)
}
switch p.Type {
t, ok := p["type"]
if !ok {
return nil, fmt.Errorf("parameter is missing 'type' field: %w", err)
}
switch t {
case typeString:
a := &StringParameter{}
if err := u.Unmarshal(a); err != nil {
return nil, fmt.Errorf("unable to parse as %q: %w", p.Type, err)
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
}
return a, nil
case typeInt:
a := &IntParameter{}
if err := u.Unmarshal(a); err != nil {
return nil, fmt.Errorf("unable to parse as %q: %w", p.Type, err)
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
}
return a, nil
case typeFloat:
a := &FloatParameter{}
if err := u.Unmarshal(a); err != nil {
return nil, fmt.Errorf("unable to parse as %q: %w", p.Type, err)
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
}
return a, nil
case typeBool:
a := &BooleanParameter{}
if err := u.Unmarshal(a); err != nil {
return nil, fmt.Errorf("unable to parse as %q: %w", p.Type, err)
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
}
return a, nil
case typeArray:
a := &ArrayParameter{}
if err := u.Unmarshal(a); err != nil {
return nil, fmt.Errorf("unable to parse as %q: %w", p.Type, err)
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
}
return a, nil
}
return nil, fmt.Errorf("%q is not valid type for a parameter!", p.Type)
return nil, fmt.Errorf("%q is not valid type for a parameter!", t)
}
func (ps Parameters) Manifest() []ParameterManifest {
@@ -515,15 +521,14 @@ type ArrayParameter struct {
}
func (p *ArrayParameter) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&p.CommonParameter); err != nil {
return err
}
var rawItem struct {
Items util.DelayedUnmarshaler `yaml:"items"`
CommonParameter `yaml:",inline"`
Items util.DelayedUnmarshaler `yaml:"items"`
}
if err := unmarshal(&rawItem); err != nil {
return err
}
p.CommonParameter = rawItem.CommonParameter
i, err := parseParamFromDelayedUnmarshaler(&rawItem.Items)
if err != nil {
return fmt.Errorf("unable to parse 'items' field: %w", err)

View File

@@ -608,12 +608,12 @@ func TestAuthParametersParse(t *testing.T) {
func TestParamValues(t *testing.T) {
tcs := []struct {
name string
in tools.ParamValues
wantSlice []any
wantMap map[string]interface{}
wantMapOrdered map[string]interface{}
wantMapWithDollar map[string]interface{}
name string
in tools.ParamValues
wantSlice []any
wantMap map[string]interface{}
wantMapOrdered map[string]interface{}
wantMapWithDollar map[string]interface{}
}{
{
name: "string",

View File

@@ -41,11 +41,11 @@ var _ compatibleSource = &postgres.Source{}
var compatibleSources = [...]string{alloydbpg.SourceKind, cloudsqlpg.SourceKind, postgres.SourceKind}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Source string `yaml:"source"`
Description string `yaml:"description"`
Statement string `yaml:"statement"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
}

View File

@@ -39,11 +39,11 @@ var _ compatibleSource = &spannerdb.Source{}
var compatibleSources = [...]string{spannerdb.SourceKind}
type Config struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Source string `yaml:"source"`
Description string `yaml:"description"`
Statement string `yaml:"statement"`
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
}

View File

@@ -14,6 +14,10 @@
package util
import (
"bytes"
"fmt"
"github.com/go-playground/validator/v10"
yaml "github.com/goccy/go-yaml"
)
@@ -39,3 +43,17 @@ type contextKey string
// UserAgentKey is the key used to store userAgent within context
const UserAgentKey contextKey = "userAgent"
func NewStrictDecoder(v interface{}) (*yaml.Decoder, error) {
b, err := yaml.Marshal(v)
if err != nil {
return nil, fmt.Errorf("fail to marshal %q: %w", v, err)
}
dec := yaml.NewDecoder(
bytes.NewReader(b),
yaml.Strict(),
yaml.Validator(validator.New()),
)
return dec, nil
}