From fdd1536a8a43f3cecbc786e67e36a11aa114a9b8 Mon Sep 17 00:00:00 2001 From: Waldemar Quevedo Date: Tue, 2 Oct 2018 15:14:15 -0700 Subject: [PATCH] Always parse config file with checks enabled Signed-off-by: Waldemar Quevedo --- server/config_check_test.go | 369 +++++++++++++++++++++++++++++++++++- server/opts.go | 322 ++++++++++++++----------------- 2 files changed, 502 insertions(+), 189 deletions(-) diff --git a/server/config_check_test.go b/server/config_check_test.go index fe7451f0..3301fd87 100644 --- a/server/config_check_test.go +++ b/server/config_check_test.go @@ -40,8 +40,11 @@ func TestConfigCheck(t *testing.T) { // errorPos is the position of the error. errorPos int - // warning errors also include a reason optionally + // warning errors also include a reason optionally. reason string + + // newDefaultErr is a configuration error that includes source of error. + newDefaultErr error }{ { name: "when unknown field is used at top level", @@ -491,6 +494,335 @@ func TestConfigCheck(t *testing.T) { errorPos: 5, reason: `setting "permissions" within cluster authorization block is deprecated`, }, + ///////////////////// + // ACCOUNTS // + ///////////////////// + { + name: "when accounts block is correctly configured", + config: ` + http_port = 8222 + + accounts { + + # + # synadia > nats.io, cncf + # + synadia { + # SAADJL5XAEM6BDYSWDTGVILJVY54CQXZM5ZLG4FRUAKB62HWRTPNSGXOHA + nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL" + + users [ + { + # SUAEL6RU3BSDAFKOHNTEOK5Q6FTM5FTAMWVIKBET6FHPO4JRII3CYELVNM + nkey = "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3" + } + ] + + exports = [ + { service: "synadia.requests", accounts: [nats, cncf] } + ] + } + + # + # nats < synadia + # + nats { + # SUAJTM55JH4BNYDA22DMDZJSRBRKVDGSLYK2HDIOCM3LPWCDXIDV5Q4CIE + nkey = "ADRZ42QBM7SXQDXXTSVWT2WLLFYOQGAFC4TO6WOAXHEKQHIXR4HFYJDS" + + users [ + { + # SUADZTYQAKTY5NQM7XRB5XR3C24M6ROGZLBZ6P5HJJSSOFUGC5YXOOECOM + nkey = "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI" + } + ] + + imports = [ + # This account has to send requests to 'nats.requests' subject + { service: { account: "synadia", subject: "synadia.requests" }, to: "nats.requests" } + ] + } + + # + # cncf < synadia + # + cncf { + # SAAFHDZX7SGZ2SWHPS22JRPPK5WX44NPLNXQHR5C5RIF6QRI3U65VFY6C4 + nkey = "AD4YRVUJF2KASKPGRMNXTYKIYSCB3IHHB4Y2ME6B2PDIV5QJ23C2ZRIT" + + users [ + { + # SUAKINP3Z2BPUXWOFSW2FZC7TFJCMMU7DHKP2C62IJQUDASOCDSTDTRMJQ + nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ" + } + ] + + imports = [ + # This account has to send requests to 'synadia.requests' subject + { service: { account: "synadia", subject: "synadia.requests" } } + ] + } + } + `, + defaultErr: nil, + pedanticErr: nil, + }, + { + name: "when accounts block has unknown fields", + config: ` + http_port = 8222 + + accounts { + foo = "bar" + } + `, + newDefaultErr: errors.New(`Expected map entries for accounts`), + errorLine: 4, + errorPos: 3, + }, + { + name: "when accounts block defines a global account", + config: ` + http_port = 8222 + + accounts { + $G = { + } + } + `, + newDefaultErr: errors.New(`"$G" is a Reserved Account`), + errorLine: 4, + errorPos: 3, + }, + { + name: "when accounts block uses an invalid public key", + config: ` + accounts { + synadia = { + nkey = "invalid" + } + } + `, + newDefaultErr: errors.New(`Not a valid public nkey for an account: "invalid"`), + errorLine: 4, + errorPos: 21, + }, + { + name: "when accounts list includes reserved account", + config: ` + port = 4222 + + accounts = [foo, bar, "$G"] + + http_port = 8222 + `, + newDefaultErr: errors.New(`"$G" is a Reserved Account`), + errorLine: 4, + errorPos: 3, + }, + { + name: "when accounts list includes a dupe entry", + config: ` + port = 4222 + + accounts = [foo, bar, bar] + + http_port = 8222 + `, + newDefaultErr: errors.New(`Duplicate Account Entry: bar`), + errorLine: 4, + errorPos: 3, + }, + { + name: "when accounts block includes a dupe user", + config: ` + port = 4222 + + accounts = { + nats { + users = [ + { user: "foo", pass: "bar" }, + { user: "hello", pass: "world" }, + { user: "foo", pass: "bar" } + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Duplicate user "foo" detected`), + errorLine: 6, + errorPos: 21, + }, + { + name: "when accounts block includes a dupe nkey", + config: ` + port = 4222 + + accounts = { + nats { + users = [ + { nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ" }, + { nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ" }, + { nkey = "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ" } + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Duplicate nkey "UB57IEMPG4KOTPFV5A66QKE2HZ3XBXFHVRCCVMJEWKECMVN2HSH3VTSJ" detected`), + errorLine: 6, + errorPos: 21, + }, + { + name: "when accounts block imports are not a list", + config: ` + port = 4222 + + accounts = { + nats { + imports = true + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Imports should be an array, got bool`), + errorLine: 6, + errorPos: 21, + }, + { + name: "when accounts block exports are not a list", + config: ` + port = 4222 + + accounts = { + nats { + exports = true + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Exports should be an array, got bool`), + errorLine: 6, + errorPos: 21, + }, + { + name: "when accounts block imports items are not a map", + config: ` + port = 4222 + + accounts = { + nats { + imports = [ + false + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Import Items should be a map with type entry, got bool`), + errorLine: 7, + errorPos: 23, + }, + { + name: "when accounts block export items are not a map", + config: ` + port = 4222 + + accounts = { + nats { + exports = [ + false + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Export Items should be a map with type entry, got bool`), + errorLine: 7, + errorPos: 23, + }, + { + name: "when accounts exports has a stream name that is not a string", + config: ` + port = 4222 + + accounts = { + nats { + exports = [ + { + stream: false + } + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Expected stream name to be string, got bool`), + errorLine: 8, + errorPos: 25, + }, + { + name: "when accounts exports has a service name that is not a string", + config: ` + accounts = { + nats { + exports = [ + { + service: false + } + ] + } + } + `, + newDefaultErr: errors.New(`Expected service name to be string, got bool`), + errorLine: 6, + errorPos: 25, + }, + { + name: "when accounts imports stream without name", + config: ` + port = 4222 + + accounts = { + nats { + imports = [ + { stream: { }} + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Expect an account name and a subject`), + errorLine: 7, + errorPos: 25, + }, + { + name: "when accounts imports service without name", + config: ` + port = 4222 + + accounts = { + nats { + imports = [ + { service: { }} + ] + } + } + + http_port = 8222 + `, + newDefaultErr: errors.New(`Expect an account name and a subject`), + errorLine: 7, + errorPos: 25, + }, } checkConfig := func(config string, pedantic bool) error { @@ -518,7 +850,16 @@ func TestConfigCheck(t *testing.T) { t.Run("with pedantic check enabled", func(t *testing.T) { err := checkConfig(conf, true) - expectedErr := test.pedanticErr + var expectedErr error + + // New default errors also include source of error + // like an error reported when running with pedantic flag. + if test.newDefaultErr != nil { + expectedErr = test.newDefaultErr + } else if test.pedanticErr != nil { + expectedErr = test.pedanticErr + } + if err != nil && expectedErr != nil { msg := fmt.Sprintf("%s:%d:%d: %s", conf, test.errorLine, test.errorPos, expectedErr.Error()) if test.reason != "" { @@ -528,16 +869,28 @@ func TestConfigCheck(t *testing.T) { t.Errorf("Expected %q, got %q", msg, err.Error()) } } - checkErr(t, err, test.pedanticErr) + + checkErr(t, err, expectedErr) }) t.Run("with pedantic check disabled", func(t *testing.T) { err := checkConfig(conf, false) - expectedErr := test.defaultErr - if err != nil && expectedErr != nil && err.Error() != expectedErr.Error() { - t.Errorf("Expected %q, got %q", expectedErr.Error(), err.Error()) + + // Gradually move to all errors including source of the error. + if err != nil && test.newDefaultErr != nil { + expectedErr := test.newDefaultErr + source := fmt.Sprintf("%s:%d:%d", conf, test.errorLine, test.errorPos) + expectedMsg := fmt.Sprintf("%s: %s", source, expectedErr.Error()) + if err.Error() != expectedMsg { + t.Errorf("\nExpected: \n%q, \ngot: \n%q", expectedMsg, err.Error()) + } + } else if err != nil && test.defaultErr != nil { + expectedErr := test.defaultErr + if err != nil && expectedErr != nil && err.Error() != expectedErr.Error() { + t.Errorf("Expected: \n%q, \ngot: \n%q", expectedErr.Error(), err.Error()) + } + checkErr(t, err, test.defaultErr) } - checkErr(t, err, test.defaultErr) }) }) } @@ -562,6 +915,6 @@ func TestConfigCheckIncludes(t *testing.T) { } expectedErr := errors.New(`configs/include_bad_conf_check_b.conf:10:19: unknown field "monitoring_port"`) if err != nil && expectedErr != nil && err.Error() != expectedErr.Error() { - t.Errorf("Expected %q, got %q", expectedErr.Error(), err.Error()) + t.Errorf("Expected: \n%q, got\n: %q", expectedErr.Error(), err.Error()) } } diff --git a/server/opts.go b/server/opts.go index 5aef8338..05b30547 100644 --- a/server/opts.go +++ b/server/opts.go @@ -199,34 +199,41 @@ type token interface { Position() int } +// configErr is a configuration error. +type configErr struct { + token token + reason string +} + +func (e *configErr) Source() string { + return fmt.Sprintf("%s:%d:%d", e.token.SourceFile(), e.token.Line(), e.token.Position()) +} + +func (e *configErr) Error() string { + if e.token != nil { + return fmt.Sprintf("%s: %s", e.Source(), e.reason) + } + return e.reason +} + +// unknownConfigFieldErr is an error reported in pedantic mode. type unknownConfigFieldErr struct { - field string - token token - configFile string + configErr + field string } func (e *unknownConfigFieldErr) Error() string { - msg := fmt.Sprintf("unknown field %q", e.field) - if e.token != nil { - return fmt.Sprintf("%s:%d:%d: %s", e.configFile, e.token.Line(), e.token.Position(), msg) - } - return msg + return fmt.Sprintf("%s: unknown field %q", e.Source(), e.field) } +// configWarningErr is an error reported in pedantic mode. type configWarningErr struct { - field string - token token - configFile string - reason string + configErr + field string } func (e *configWarningErr) Error() string { - msg := fmt.Sprintf("invalid use of field %q", e.field) - if e.token != nil { - msg = fmt.Sprintf("%s:%d:%d: %s", e.configFile, e.token.Line(), e.token.Position(), msg) - } - msg += ": " + e.reason - return msg + return fmt.Sprintf("%s: invalid use of field %q: %s", e.Source(), e.field, e.reason) } // ProcessConfigFile processes a configuration file. @@ -270,25 +277,14 @@ func (o *Options) ProcessConfigFile(configFile string) error { if configFile == "" { return nil } - - var ( - m map[string]interface{} - tk token - pedantic bool = o.CheckConfig - err error - ) - if pedantic { - m, err = conf.ParseFileWithChecks(configFile) - } else { - m, err = conf.ParseFile(configFile) - } + m, err := conf.ParseFileWithChecks(configFile) if err != nil { return err } for k, v := range m { // When pedantic checks are enabled then need to unwrap // to get the value along with reported error line. - tk, v = unwrapValue(v) + tk, v := unwrapValue(v) switch strings.ToLower(k) { case "listen": hp, err := parseListen(v) @@ -310,17 +306,12 @@ func (o *Options) ProcessConfigFile(configFile string) error { case "logtime": o.Logtime = v.(bool) case "accounts": - err = parseAccounts(v, o) + err := parseAccounts(tk, o) if err != nil { return err } case "authorization": - var auth *authorization - if pedantic { - auth, err = parseAuthorization(tk, o) - } else { - auth, err = parseAuthorization(v, o) - } + auth, err := parseAuthorization(tk, o) if err != nil { return err } @@ -366,12 +357,7 @@ func (o *Options) ProcessConfigFile(configFile string) error { case "https_port": o.HTTPSPort = int(v.(int64)) case "cluster": - var err error - if pedantic { - err = parseCluster(tk, o) - } else { - err = parseCluster(v, o) - } + err := parseCluster(tk, o) if err != nil { return err } @@ -406,11 +392,7 @@ func (o *Options) ProcessConfigFile(configFile string) error { tc *TLSConfigOpts err error ) - if pedantic { - tc, err = parseTLS(tk, o) - } else { - tc, err = parseTLS(v, o) - } + tc, err = parseTLS(tk, o) if err != nil { return err } @@ -433,11 +415,13 @@ func (o *Options) ProcessConfigFile(configFile string) error { fmt.Printf("WARNING: write_deadline should be converted to a duration\n") } default: + pedantic := o.CheckConfig if pedantic && tk != nil && !tk.IsUsedVariable() { return &unknownConfigFieldErr{ - field: k, - token: tk, - configFile: tk.SourceFile(), + field: k, + configErr: configErr{ + token: tk, + }, } } } @@ -497,15 +481,7 @@ func parseCluster(v interface{}, opts *Options) error { case "host", "net": opts.Cluster.Host = mv.(string) case "authorization": - var ( - auth *authorization - err error - ) - if pedantic { - auth, err = parseAuthorization(tk, opts) - } else { - auth, err = parseAuthorization(mv, opts) - } + auth, err := parseAuthorization(tk, opts) if err != nil { return err } @@ -519,10 +495,11 @@ func parseCluster(v interface{}, opts *Options) error { if auth.defaultPermissions != nil { if pedantic { return &configWarningErr{ - field: mk, - token: tk, - reason: `setting "permissions" within cluster authorization block is deprecated`, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + reason: `setting "permissions" within cluster authorization block is deprecated`, + }, } } @@ -548,11 +525,7 @@ func parseCluster(v interface{}, opts *Options) error { tc *TLSConfigOpts err error ) - if pedantic { - tc, err = parseTLS(tk, opts) - } else { - tc, err = parseTLS(mv, opts) - } + tc, err = parseTLS(tk, opts) if err != nil { return err } @@ -581,9 +554,10 @@ func parseCluster(v interface{}, opts *Options) error { default: if pedantic && tk != nil && !tk.IsUsedVariable() { return &unknownConfigFieldErr{ - field: mk, - token: tk, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + }, } } } @@ -641,19 +615,20 @@ func parseAccounts(v interface{}, opts *Options) error { exportStreams []*export exportServices []*export ) - _, v = unwrapValue(v) - switch v.(type) { + tk, v := unwrapValue(v) + switch vv := v.(type) { // Simple array of account names. case []interface{}, []string: m := make(map[string]struct{}, len(v.([]interface{}))) - for _, name := range v.([]interface{}) { + for _, n := range v.([]interface{}) { + _, name := unwrapValue(n) ns := name.(string) // Check for reserved names. if isReservedAccount(ns) { - return fmt.Errorf("%q is a Reserved Account", ns) + return &configErr{tk, fmt.Sprintf("%q is a Reserved Account", ns)} } if _, ok := m[ns]; ok { - return fmt.Errorf("Duplicate Account Entry: %s", ns) + return &configErr{tk, fmt.Sprintf("Duplicate Account Entry: %s", ns)} } opts.Accounts = append(opts.Accounts, &Account{Name: ns}) m[ns] = struct{}{} @@ -663,15 +638,15 @@ func parseAccounts(v interface{}, opts *Options) error { // Track users across accounts, must be unique across // accounts and nkeys vs users. uorn := make(map[string]struct{}) - for aname, mv := range v.(map[string]interface{}) { + for aname, mv := range vv { _, amv := unwrapValue(mv) // These should be maps. mv, ok := amv.(map[string]interface{}) if !ok { - return fmt.Errorf("Expected map entries for accounts") + return &configErr{tk, "Expected map entries for accounts"} } if isReservedAccount(aname) { - return fmt.Errorf("%q is a Reserved Account", aname) + return &configErr{tk, fmt.Sprintf("%q is a Reserved Account", aname)} } acc := &Account{Name: aname} opts.Accounts = append(opts.Accounts, acc) @@ -682,40 +657,31 @@ func parseAccounts(v interface{}, opts *Options) error { case "nkey": nk, ok := mv.(string) if !ok || !nkeys.IsValidPublicAccountKey(nk) { - return fmt.Errorf("Not a valid public nkey for an account: %q", v) + return &configErr{tk, fmt.Sprintf("Not a valid public nkey for an account: %q", mv)} } acc.Nkey = nk case "imports": - streams, services, err := parseAccountImports(mv, acc, pedantic) + streams, services, err := parseAccountImports(tk, acc, pedantic) if err != nil { return err } importStreams = append(importStreams, streams...) importServices = append(importServices, services...) case "exports": - streams, services, err := parseAccountExports(mv, acc, pedantic) + streams, services, err := parseAccountExports(tk, acc, pedantic) if err != nil { return err } exportStreams = append(exportStreams, streams...) exportServices = append(exportServices, services...) case "users": - var ( - users []*User - err error - nkeys []*NkeyUser - ) - if pedantic { - nkeys, users, err = parseUsers(tk, opts) - } else { - nkeys, users, err = parseUsers(mv, opts) - } + nkeys, users, err := parseUsers(mv, opts) if err != nil { return err } for _, u := range users { if _, ok := uorn[u.Username]; ok { - return fmt.Errorf("Duplicate user %q detected", u.Username) + return &configErr{tk, fmt.Sprintf("Duplicate user %q detected", u.Username)} } uorn[u.Username] = struct{}{} u.Account = acc @@ -724,18 +690,19 @@ func parseAccounts(v interface{}, opts *Options) error { for _, u := range nkeys { if _, ok := uorn[u.Nkey]; ok { - return fmt.Errorf("Duplicate nkey %q detected", u.Nkey) + return &configErr{tk, fmt.Sprintf("Duplicate nkey %q detected", u.Nkey)} } uorn[u.Nkey] = struct{}{} u.Account = acc } opts.Nkeys = append(opts.Nkeys, nkeys...) default: - if pedantic && tk != nil && !tk.IsUsedVariable() { + if pedantic && !tk.IsUsedVariable() { return &unknownConfigFieldErr{ - field: k, - token: tk, - configFile: tk.SourceFile(), + field: k, + configErr: configErr{ + token: tk, + }, } } } @@ -809,23 +776,18 @@ func parseAccounts(v interface{}, opts *Options) error { // Parse the account imports func parseAccountExports(v interface{}, acc *Account, pedantic bool) ([]*export, []*export, error) { // This should be an array of objects/maps. - _, v = unwrapValue(v) + tk, v := unwrapValue(v) ims, ok := v.([]interface{}) if !ok { - return nil, nil, fmt.Errorf("Exports should be an array, got %T", v) + return nil, nil, &configErr{tk, fmt.Sprintf("Exports should be an array, got %T", v)} } var services []*export var streams []*export for _, v := range ims { - _, mv := unwrapValue(v) - io, ok := mv.(map[string]interface{}) - if !ok { - return nil, nil, fmt.Errorf("Export Items should be a map with type entry, got %T", mv) - } // Should have stream or service - stream, service, err := parseExportStreamOrService(io, pedantic) + stream, service, err := parseExportStreamOrService(v, pedantic) if err != nil { return nil, nil, err } @@ -844,23 +806,18 @@ func parseAccountExports(v interface{}, acc *Account, pedantic bool) ([]*export, // Parse the account imports func parseAccountImports(v interface{}, acc *Account, pedantic bool) ([]*importStream, []*importService, error) { // This should be an array of objects/maps. - _, v = unwrapValue(v) + tk, v := unwrapValue(v) ims, ok := v.([]interface{}) if !ok { - return nil, nil, fmt.Errorf("Imports should be an array, got %T", v) + return nil, nil, &configErr{tk, fmt.Sprintf("Imports should be an array, got %T", v)} } var services []*importService var streams []*importStream for _, v := range ims { - _, mv := unwrapValue(v) - io, ok := mv.(map[string]interface{}) - if !ok { - return nil, nil, fmt.Errorf("Import Items should be a map with type entry, got %T", mv) - } // Should have stream or service - stream, service, err := parseImportStreamOrService(io, pedantic) + stream, service, err := parseImportStreamOrService(v, pedantic) if err != nil { return nil, nil, err } @@ -889,16 +846,14 @@ func parseAccount(v map[string]interface{}, pedantic bool) (string, string, erro default: if pedantic && tk != nil && !tk.IsUsedVariable() { return "", "", &unknownConfigFieldErr{ - field: mk, - token: tk, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + }, } } } } - if accountName == "" || subject == "" { - return "", "", fmt.Errorf("Expect an account name and a subject") - } return accountName, subject, nil } @@ -908,31 +863,40 @@ func parseAccount(v map[string]interface{}, pedantic bool) (string, string, erro // {stream: "synadia.private.>", accounts: [cncf, natsio]} // {service: "pub.request"} # No accounts means public. // {service: "pub.special.request", accounts: [nats.io]} -func parseExportStreamOrService(v map[string]interface{}, pedantic bool) (*export, *export, error) { +func parseExportStreamOrService(v interface{}, pedantic bool) (*export, *export, error) { var ( curStream *export curService *export accounts []string ) - - for mk, mv := range v { + tk, v := unwrapValue(v) + vv, ok := v.(map[string]interface{}) + if !ok { + return nil, nil, &configErr{tk, fmt.Sprintf("Export Items should be a map with type entry, got %T", v)} + } + for mk, mv := range vv { tk, mv := unwrapValue(mv) switch strings.ToLower(mk) { case "stream": if curService != nil { - return nil, nil, fmt.Errorf("Detected stream but already saw a service: %+v", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Detected stream %q but already saw a service", mv)} } - curStream = &export{sub: mv.(string)} + + mvs, ok := mv.(string) + if !ok { + return nil, nil, &configErr{tk, fmt.Sprintf("Expected stream name to be string, got %T", mv)} + } + curStream = &export{sub: mvs} if accounts != nil { curStream.accs = accounts } case "service": if curStream != nil { - return nil, nil, fmt.Errorf("Detected service but already saw a stream: %+v", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Detected service %q but already saw a stream", mv)} } mvs, ok := mv.(string) if !ok { - return nil, nil, fmt.Errorf("Expected service to be string name, got %T", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Expected service name to be string, got %T", mv)} } curService = &export{sub: mvs} if accounts != nil { @@ -951,9 +915,10 @@ func parseExportStreamOrService(v map[string]interface{}, pedantic bool) (*expor default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, nil, &unknownConfigFieldErr{ - field: mk, - token: tk, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + }, } } return nil, nil, fmt.Errorf("Unknown field %q parsing export service or stream", mk) @@ -968,46 +933,56 @@ func parseExportStreamOrService(v map[string]interface{}, pedantic bool) (*expor // {stream: {account: "synadia", subject:"public.synadia"}, prefix: "imports.synadia"} // {stream: {account: "synadia", subject:"synadia.private.*"}} // {service: {account: "synadia", subject: "pub.special.request"}, subject: "synadia.request"} -func parseImportStreamOrService(v map[string]interface{}, pedantic bool) (*importStream, *importService, error) { +func parseImportStreamOrService(v interface{}, pedantic bool) (*importStream, *importService, error) { var ( curStream *importStream curService *importService pre, to string ) - - for mk, mv := range v { + tk, mv := unwrapValue(v) + vv, ok := mv.(map[string]interface{}) + if !ok { + return nil, nil, &configErr{tk, fmt.Sprintf("Import Items should be a map with type entry, got %T", mv)} + } + for mk, mv := range vv { tk, mv := unwrapValue(mv) switch strings.ToLower(mk) { case "stream": if curService != nil { - return nil, nil, fmt.Errorf("Detected stream but already saw a service: %+v", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Detected stream but already saw a service")} } ac, ok := mv.(map[string]interface{}) if !ok { - return nil, nil, fmt.Errorf("Stream entry should be an account map, got %T", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Stream entry should be an account map, got %T", mv)} } // Make sure this is a map with account and subject accountName, subject, err := parseAccount(ac, pedantic) if err != nil { return nil, nil, err } + if accountName == "" || subject == "" { + return nil, nil, &configErr{tk, fmt.Sprintf("Expect an account name and a subject")} + } curStream = &importStream{an: accountName, sub: subject} if pre != "" { curStream.pre = pre } case "service": if curStream != nil { - return nil, nil, fmt.Errorf("Detected service but already saw a stream: %+v", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Detected service but already saw a stream")} } ac, ok := mv.(map[string]interface{}) if !ok { - return nil, nil, fmt.Errorf("Service entry should be an account map, got %T", mv) + return nil, nil, &configErr{tk, fmt.Sprintf("Service entry should be an account map, got %T", mv)} } // Make sure this is a map with account and subject accountName, subject, err := parseAccount(ac, pedantic) if err != nil { return nil, nil, err } + if accountName == "" || subject == "" { + return nil, nil, &configErr{tk, fmt.Sprintf("Expect an account name and a subject")} + } curService = &importService{an: accountName, sub: subject} if to != "" { curService.to = to @@ -1025,9 +1000,10 @@ func parseImportStreamOrService(v map[string]interface{}, pedantic bool) (*impor default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, nil, &unknownConfigFieldErr{ - field: mk, - token: tk, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + }, } } return nil, nil, fmt.Errorf("Unknown field %q parsing import service or stream", mk) @@ -1068,31 +1044,14 @@ func parseAuthorization(v interface{}, opts *Options) (*authorization, error) { } auth.timeout = at case "users": - var ( - users []*User - err error - nkeys []*NkeyUser - ) - if pedantic { - nkeys, users, err = parseUsers(tk, opts) - } else { - nkeys, users, err = parseUsers(mv, opts) - } + nkeys, users, err := parseUsers(tk, opts) if err != nil { return nil, err } auth.users = users auth.nkeys = nkeys case "default_permission", "default_permissions", "permissions": - var ( - permissions *Permissions - err error - ) - if pedantic { - permissions, err = parseUserPermissions(tk, opts) - } else { - permissions, err = parseUserPermissions(mv, opts) - } + permissions, err := parseUserPermissions(tk, opts) if err != nil { return nil, err } @@ -1100,9 +1059,10 @@ func parseAuthorization(v interface{}, opts *Options) (*authorization, error) { default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, &unknownConfigFieldErr{ - field: mk, - token: tk, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + }, } } } @@ -1162,20 +1122,17 @@ func parseUsers(mv interface{}, opts *Options) ([]*NkeyUser, []*User, error) { case "pass", "password": user.Password = v.(string) case "permission", "permissions", "authorization": - if pedantic { - perms, err = parseUserPermissions(tk, opts) - } else { - perms, err = parseUserPermissions(v, opts) - } + perms, err = parseUserPermissions(v, opts) if err != nil { return nil, nil, err } default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, nil, &unknownConfigFieldErr{ - field: k, - token: tk, - configFile: tk.SourceFile(), + field: k, + configErr: configErr{ + token: tk, + }, } } } @@ -1246,9 +1203,10 @@ func parseUserPermissions(mv interface{}, opts *Options) (*Permissions, error) { default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, &unknownConfigFieldErr{ - field: k, - token: tk, - configFile: tk.SourceFile(), + field: k, + configErr: configErr{ + token: tk, + }, } } return nil, fmt.Errorf("Unknown field %s parsing permissions", k) @@ -1331,9 +1289,10 @@ func parseSubjectPermission(v interface{}, opts *Options) (*SubjectPermission, e default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, &unknownConfigFieldErr{ - field: k, - token: tk, - configFile: tk.SourceFile(), + field: k, + configErr: configErr{ + token: tk, + }, } } return nil, fmt.Errorf("Unknown field name %q parsing subject permissions, only 'allow' or 'deny' are permitted", k) @@ -1459,9 +1418,10 @@ func parseTLS(v interface{}, opts *Options) (*TLSConfigOpts, error) { default: if pedantic && tk != nil && !tk.IsUsedVariable() { return nil, &unknownConfigFieldErr{ - field: mk, - token: tk, - configFile: tk.SourceFile(), + field: mk, + configErr: configErr{ + token: tk, + }, } }