diff --git a/conflate_test.go b/conflate_test.go index 7ad1f48..da2fb48 100644 --- a/conflate_test.go +++ b/conflate_test.go @@ -3,7 +3,6 @@ package conflate import ( gocontext "context" "net/http" - "os" "sync" "testing" "time" @@ -56,14 +55,12 @@ func TestAddData_Expand(t *testing.T) { c := New() c.Expand(true) - err := os.Setenv("X", "123") - assert.Nil(t, err) - err = os.Setenv("Y", "str") - assert.Nil(t, err) + t.Setenv("X", "123") + t.Setenv("Y", "str") inJSON := []byte(`{ "x": $X, "y": "$Y", "z": "$Z"}`) - err = c.AddData(inJSON) + err := c.AddData(inJSON) assert.Nil(t, err) outJSON, err := c.MarshalJSON() assert.Nil(t, err) @@ -79,14 +76,12 @@ func TestAddData_NoExpand(t *testing.T) { c := New() c.Expand(false) - err := os.Setenv("X", "123") - assert.Nil(t, err) - err = os.Setenv("Y", "str") - assert.Nil(t, err) + t.Setenv("X", "123") + t.Setenv("Y", "str") inJSON := []byte(`{ "x": "$X" }`) - err = c.AddData(inJSON) + err := c.AddData(inJSON) assert.Nil(t, err) outJSON, err := c.MarshalJSON() diff --git a/error.go b/error.go index a0ac8f1..70e314e 100644 --- a/error.go +++ b/error.go @@ -6,11 +6,11 @@ import ( type context string -type errWithContext struct { +type contextError struct { msg string context context } -func (e errWithContext) Error() string { +func (e contextError) Error() string { return fmt.Sprintf("%v (%v)", e.msg, e.context) } diff --git a/filedata.go b/filedata.go index da47ac8..cd3f692 100644 --- a/filedata.go +++ b/filedata.go @@ -133,7 +133,7 @@ func recursiveExpand(b []byte) []byte { var c int - for i := 0; i < maxExpansions; i++ { + for range maxExpansions { b, c = expand(b) if c == 0 { return b diff --git a/filedata_test.go b/filedata_test.go index 427788a..c198989 100644 --- a/filedata_test.go +++ b/filedata_test.go @@ -3,7 +3,6 @@ package conflate import ( "errors" pkgurl "net/url" - "os" "testing" "github.com/stretchr/testify/assert" @@ -12,6 +11,8 @@ import ( var errTest = errors.New("my error") func testFiledataNew(t *testing.T, data []byte, path string) (filedata, error) { + t.Helper() + url, err := pkgurl.Parse(path) assert.Nil(t, err) @@ -19,6 +20,8 @@ func testFiledataNew(t *testing.T, data []byte, path string) (filedata, error) { } func testFiledataNewAssert(t *testing.T, data []byte, path string) filedata { + t.Helper() + fd, err := testFiledataNew(t, data, path) assert.Nil(t, err) @@ -188,30 +191,10 @@ func TestFiledata_IncludesError(t *testing.T) { } func TestFiledata_Expand(t *testing.T) { - w := os.Getenv("W") - x := os.Getenv("X") - y := os.Getenv("Y") - z := os.Getenv("Z") - - err := os.Setenv("W", "$W") - assert.Nil(t, err) - err = os.Setenv("X", `"x"`) - assert.Nil(t, err) - err = os.Setenv("Y", `y`) - assert.Nil(t, err) - err = os.Setenv("Z", `$Y`) - assert.Nil(t, err) - - defer func() { - err = os.Setenv("W", w) - assert.Nil(t, err) - err = os.Setenv("X", x) - assert.Nil(t, err) - err = os.Setenv("Y", y) - assert.Nil(t, err) - err = os.Setenv("Z", z) - assert.Nil(t, err) - }() + t.Setenv("W", "$W") + t.Setenv("X", `"x"`) + t.Setenv("Y", `y`) + t.Setenv("Z", `$Y`) b := recursiveExpand([]byte(`{"W":"$W","X":$X,"Y":"$Y","Z":"$Z"}`)) assert.Equal(t, string(b), string(`{"W":"$W","X":"x","Y":"y","Z":"y"}`)) diff --git a/format.go b/format.go index 6679232..c00c28f 100644 --- a/format.go +++ b/format.go @@ -100,7 +100,7 @@ func (f xmlTemplateFormatChecker) IsFormat(input interface{}) bool { if s, ok := input.(string); ok { s = f.tags.ReplaceAllString(s, "") - if len(s) > 0 { + if s != "" { var v interface{} if err := xml.Unmarshal([]byte(s), &v); err != nil { ferr = fmt.Errorf("failed to parse xml: %w", err) diff --git a/format_test.go b/format_test.go index 7c045a6..3c9f09a 100644 --- a/format_test.go +++ b/format_test.go @@ -216,6 +216,8 @@ func TestHtmlFormatCheckerIsFormat_NotValid(t *testing.T) { // -------- func testCryptoFormatCheckerIsFormatNotString(t *testing.T, cryptoType cryptoType) { + t.Helper() + givenName := cryptoName givenValue := 1 @@ -243,6 +245,8 @@ func TestCryptoFormatCheckerIsFormat_NotString(t *testing.T) { } func testCryptoFormatCheckerIsFormatNotValid(t *testing.T, cryptoType cryptoType) { + t.Helper() + givenName := cryptoName givenValue := "dGhpcyBpcyBub3QgYSB2YWxpZCBjZXJ0aWZpY2F0ZQo=" @@ -270,6 +274,8 @@ func TestCryptoFormatCheckerIsFormat_NotValid(t *testing.T) { } func testCryptoFormatCheckerIsFormatValid(t *testing.T, cryptoType cryptoType, givenValue string) { + t.Helper() + givenName := cryptoName formatErrs.clear() diff --git a/go.mod b/go.mod index 5bf25b6..3ee402a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/miracl/conflate -go 1.21 +go 1.22 require ( cloud.google.com/go/storage v1.33.0 diff --git a/loader.go b/loader.go index b3d3123..a833a5a 100644 --- a/loader.go +++ b/loader.go @@ -68,8 +68,6 @@ func (l *loader) loadDataRecursive(parentUrls []*pkgurl.URL, data ...filedata) ( var allData filedatas for _, datum := range data { - datum := datum - childData, err := l.loadDatumRecursive(parentUrls, nil, &datum) if err != nil { return nil, err @@ -150,7 +148,7 @@ func loadURL(url *pkgurl.URL) ([]byte, error) { client := http.Client{Transport: newTransport()} - resp, err := client.Get(url.String()) + resp, err := client.Get(url.String()) //nolint:noctx // we don't have ctx anywhere if err != nil { return nil, err } @@ -301,7 +299,7 @@ func workingDir() (*pkgurl.URL, error) { func setPath(path string) string { if goos == windowsOS { // https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/ - path = strings.Replace(path, `\`, `/`, -1) + path = strings.ReplaceAll(path, `\`, `/`) path = strings.TrimLeft(path, `/`) if driveLetter.MatchString(path) { @@ -321,7 +319,7 @@ func getPath(path string) string { path = `//` + path } - path = strings.Replace(path, `/`, `\`, -1) + path = strings.ReplaceAll(path, `/`, `\`) } return path diff --git a/loader_test.go b/loader_test.go index 1b5304b..b3fb3cd 100644 --- a/loader_test.go +++ b/loader_test.go @@ -201,9 +201,11 @@ func testServer() func() { } func testWaitForURL(t *testing.T, urlPath string) { + t.Helper() + // wait for a couple of seconds for server to come up - for i := 0; i < 4; i++ { - resp, err := http.Get(urlPath) //nolint:gosec // ok for a test + for range 4 { + resp, err := http.Get(urlPath) //nolint:gosec,noctx // ok for a test if err == nil { //nolint:gocritic // ok for a test with small loop defer func() { @@ -382,6 +384,8 @@ func TestLoadURLsRecursive_BlankChildToml(t *testing.T) { } func testPath(t *testing.T, urlPath, filePath string) { + t.Helper() + assert.Equal(t, urlPath, setPath(filePath)) assert.Equal(t, filePath, getPath(urlPath)) } diff --git a/marshal_test.go b/marshal_test.go index 540820a..6fa7a2e 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -18,7 +18,7 @@ func TestJSONMarshalAll(t *testing.T) { } func TestJSONMarshalAll_Error(t *testing.T) { - mockMarshal := func(obj interface{}) ([]byte, error) { + mockMarshal := func(_ interface{}) ([]byte, error) { return nil, errTest } data, err := jsonMarshalAll(mockMarshal, "a") diff --git a/merge.go b/merge.go index 6759cad..f22cdab 100644 --- a/merge.go +++ b/merge.go @@ -22,7 +22,7 @@ func merge(pToData, fromData interface{}) error { func mergeRecursive(ctx context, pToData, fromData interface{}) error { if pToData == nil { - return &errWithContext{ + return &contextError{ context: ctx, msg: "the destination variable must not be nil", } @@ -30,7 +30,7 @@ func mergeRecursive(ctx context, pToData, fromData interface{}) error { pToVal := reflect.ValueOf(pToData) if pToVal.Kind() != reflect.Ptr { - return &errWithContext{ + return &contextError{ context: ctx, msg: "the destination variable must be a pointer", } @@ -69,15 +69,15 @@ func mergeRecursive(ctx context, pToData, fromData interface{}) error { func mergeMapRecursive(ctx context, toData, fromData interface{}) error { fromProps, ok := fromData.(map[string]interface{}) if !ok { - return &errWithContext{ + return &contextError{ context: ctx, msg: "the source value must be a map[string]interface{}", } } - toProps, _ := toData.(map[string]interface{}) - if toProps == nil { - return &errWithContext{ + toProps, ok := toData.(map[string]interface{}) + if toProps == nil || !ok { + return &contextError{ context: ctx, msg: "the destination value must be a map[string]interface{}", } @@ -89,7 +89,7 @@ func mergeMapRecursive(ctx context, toData, fromData interface{}) error { } else { err := merge(&val, fromProp) if err != nil { - return &errWithContext{ + return &contextError{ context: ctx.add(name), msg: fmt.Sprintf("failed to merge object property : %v : %v", name, err.Error()), } @@ -105,15 +105,15 @@ func mergeMapRecursive(ctx context, toData, fromData interface{}) error { func mergeSliceRecursive(ctx context, toVal reflect.Value, toData, fromData interface{}) error { fromItems, ok := fromData.([]interface{}) if !ok { - return &errWithContext{ + return &contextError{ context: ctx, msg: "the source value must be a []interface{}", } } - toItems, _ := toData.([]interface{}) - if toItems == nil { - return &errWithContext{ + toItems, ok := toData.([]interface{}) + if toItems == nil || !ok { + return &contextError{ context: ctx, msg: "the destination value must be a []interface{}", } @@ -138,7 +138,7 @@ func mergeDefaultRecursive(ctx context, toVal, fromVal reflect.Value, toData, fr } if !fromType.AssignableTo(toType) { - return &errWithContext{ + return &contextError{ context: ctx, msg: fmt.Sprintf("the destination type (%v) must be the same as the source type (%v)", toType, fromType), } diff --git a/merge_test.go b/merge_test.go index 1bfb582..3401ba1 100644 --- a/merge_test.go +++ b/merge_test.go @@ -184,6 +184,8 @@ func TestMerge_Equal(t *testing.T) { } func testMergeCheck(t *testing.T, merged, data1, data2 interface{}) { + t.Helper() + mergedVal := reflect.ValueOf(merged) //nolint:exhaustive // test caseload @@ -193,31 +195,40 @@ func testMergeCheck(t *testing.T, merged, data1, data2 interface{}) { case reflect.Slice: testMergeCheckSlice(t, merged, data1, data2) default: - if data1 == nil && data2 != nil { + switch { + case data1 == nil && data2 != nil: assert.Equal(t, data2, merged) - } else if data1 != nil && data2 == nil { + case data1 != nil && data2 == nil: assert.Equal(t, data1, merged) - } else if data1 != nil && data2 != nil { + case data1 != nil && data2 != nil: assert.Equal(t, data2, merged) - } else { + default: assert.Nil(t, merged) } } } func testMergeCheckMap(t *testing.T, merged, data1, data2 interface{}) { - var mergedMap, data1Map, data2Map map[string]interface{} + t.Helper() + + var ( + mergedMap, data1Map, data2Map map[string]interface{} + ok bool + ) if merged != nil { - mergedMap = merged.(map[string]interface{}) + mergedMap, ok = merged.(map[string]interface{}) + assert.True(t, ok) } if data1 != nil { - data1Map = data1.(map[string]interface{}) + data1Map, ok = data1.(map[string]interface{}) + assert.True(t, ok) } if data2 != nil { - data2Map = data2.(map[string]interface{}) + data2Map, ok = data2.(map[string]interface{}) + assert.True(t, ok) } for name, mergedItem := range mergedMap { @@ -236,18 +247,26 @@ func testMergeCheckMap(t *testing.T, merged, data1, data2 interface{}) { } func testMergeCheckSlice(t *testing.T, merged, data1, data2 interface{}) { - var mergedArr, data1Arr, data2Arr []interface{} + t.Helper() + + var ( + mergedArr, data1Arr, data2Arr []interface{} + ok bool + ) if merged != nil { - mergedArr = merged.([]interface{}) + mergedArr, ok = merged.([]interface{}) + assert.True(t, ok) } if data1 != nil { - data1Arr = data1.([]interface{}) + data1Arr, ok = data1.([]interface{}) + assert.True(t, ok) } if data2 != nil { - data2Arr = data2.([]interface{}) + data2Arr, ok = data2.([]interface{}) + assert.True(t, ok) } assert.Equal(t, len(data1Arr)+len(data2Arr), len(mergedArr)) @@ -270,6 +289,8 @@ func testMergeCheckSlice(t *testing.T, merged, data1, data2 interface{}) { // ---------- func testMergeGetData(t *testing.T, data []byte) interface{} { + t.Helper() + var out interface{} err := json.Unmarshal(data, &out) diff --git a/schema.go b/schema.go index d6b2587..945f04a 100644 --- a/schema.go +++ b/schema.go @@ -167,9 +167,9 @@ func processResult(result *gojsonschema.Result) error { for _, rerr := range result.Errors() { ctx := convertJSONContext(rerr.Context().String()) - ctxErr := &errWithContext{msg: rerr.Description(), context: ctx} + ctxErr := &contextError{msg: rerr.Description(), context: ctx} - err = fmt.Errorf("%w: %v", err, ctxErr) + err = fmt.Errorf("%w: %w", err, ctxErr) ferr := formatErrs.get(rerr.Details()["format"], rerr.Value()) if ferr != nil { @@ -200,12 +200,12 @@ func applyDefaults(pData, schema interface{}) error { func applyDefaultsRecursive(ctx context, rootSchema, pData, schema interface{}) error { if pData == nil { - return &errWithContext{context: ctx, msg: "destination value must not be nil"} + return &contextError{context: ctx, msg: "destination value must not be nil"} } pDataVal := reflect.ValueOf(pData) if pDataVal.Kind() != reflect.Ptr { - return &errWithContext{context: ctx, msg: "destination value must be a pointer"} + return &contextError{context: ctx, msg: "destination value must be a pointer"} } dataVal := pDataVal.Elem() @@ -213,24 +213,24 @@ func applyDefaultsRecursive(ctx context, rootSchema, pData, schema interface{}) schemaNode, ok := schema.(map[string]interface{}) if !ok { - return &errWithContext{context: ctx, msg: "schema section is not a map"} + return &contextError{context: ctx, msg: "schema section is not a map"} } val, ok := schemaNode["$ref"] if ok { ref, ok := val.(string) if !ok { - return &errWithContext{context: ctx, msg: fmt.Sprintf("reference is not a string '%v'", ref)} + return &contextError{context: ctx, msg: fmt.Sprintf("reference is not a string '%v'", ref)} } jref, err := gojsonreference.NewJsonReference(ref) if err != nil { - return &errWithContext{context: ctx, msg: fmt.Sprintf("invalid reference '%v': %v", ref, err.Error())} + return &contextError{context: ctx, msg: fmt.Sprintf("invalid reference '%v': %v", ref, err.Error())} } subSchema, _, err := jref.GetPointer().Get(rootSchema) if subSchema == nil || err != nil { - return &errWithContext{context: ctx, msg: fmt.Sprintf("cannot find reference '%v': %v", ref, err.Error())} + return &contextError{context: ctx, msg: fmt.Sprintf("cannot find reference '%v': %v", ref, err.Error())} } return applyDefaultsRecursive(ctx.add(ref), rootSchema, pData, subSchema) @@ -243,7 +243,7 @@ func applyDefaultsRecursive(ctx context, rootSchema, pData, schema interface{}) return nil } - return &errWithContext{context: ctx, msg: "Schema section does not have a valid 'type' attribute"} + return &contextError{context: ctx, msg: "Schema section does not have a valid 'type' attribute"} } if value, ok := schemaNode["default"]; ok && data == nil { @@ -274,6 +274,9 @@ func hasKey(m map[string]interface{}, keys ...string) bool { return false } +var errNoMapString = errors.New("expected map of strings") + +//nolint:gocyclo // acceptable func applyObjectDefaults(ctx context, rootSchema, data interface{}, schemaNode map[string]interface{}) error { if data == nil { return nil @@ -281,7 +284,7 @@ func applyObjectDefaults(ctx context, rootSchema, data interface{}, schemaNode m dataProps, ok := data.(map[string]interface{}) if !ok { - return &errWithContext{context: ctx, msg: "node should be an 'object'"} + return &contextError{context: ctx, msg: "node should be an 'object'"} } if dataProps == nil { @@ -289,8 +292,15 @@ func applyObjectDefaults(ctx context, rootSchema, data interface{}, schemaNode m } var schemaProps map[string]interface{} + if props, ok := schemaNode["properties"]; ok { - schemaProps = props.(map[string]interface{}) + var ok bool + + schemaProps, ok = props.(map[string]interface{}) + if !ok { + return fmt.Errorf("%w: %v", errNoMapString, props) + } + for name, schemaProp := range schemaProps { dataProp := dataProps[name] @@ -310,7 +320,7 @@ func applyObjectDefaults(ctx context, rootSchema, data interface{}, schemaNode m if addProps, ok = addProps.(map[string]interface{}); ok { for name, dataProp := range dataProps { if schemaProps == nil || schemaProps[name] == nil { - err := applyDefaultsRecursive(ctx.add(name), rootSchema, &dataProp, addProps) //nolint:gosec,scopelint // to be refactored carefully + err := applyDefaultsRecursive(ctx.add(name), rootSchema, &dataProp, addProps) //nolint:scopelint // to be refactored carefully if err != nil { return fmt.Errorf("failed to apply defaults to additional object property: %w", err) } @@ -333,14 +343,17 @@ func applyArrayDefaults(ctx context, rootSchema, data interface{}, schemaNode ma dataItems, ok := data.([]interface{}) if !ok { - return &errWithContext{context: ctx, msg: "node should be an 'array'"} + return &contextError{context: ctx, msg: "node should be an 'array'"} } if items, ok := schemaNode["items"]; ok { - schemaItem := items.(map[string]interface{}) + schemaItem, ok := items.(map[string]interface{}) + if !ok { + return fmt.Errorf("%w: %v", errNoMapString, items) + } for i, dataItem := range dataItems { - err := applyDefaultsRecursive(ctx.addInt(i), rootSchema, &dataItem, schemaItem) //nolint:gosec,scopelint // to be refactored carefully + err := applyDefaultsRecursive(ctx.addInt(i), rootSchema, &dataItem, schemaItem) //nolint:scopelint // to be refactored carefully if err != nil { return fmt.Errorf("failed to apply defaults to array item: %w", err) } diff --git a/schema_test.go b/schema_test.go index 94a2f6c..929a987 100644 --- a/schema_test.go +++ b/schema_test.go @@ -178,7 +178,9 @@ func TestValidate_NotValid(t *testing.T) { err = JSONUnmarshal(testSchema, &schema) assert.Nil(t, err) - obj := data["obj"].(map[string]interface{}) + obj, ok := data["obj"].(map[string]interface{}) + assert.True(t, ok) + obj["str"] = 123 err = validate(data, schema) @@ -200,8 +202,11 @@ func TestValidate_CustomFormatError(t *testing.T) { err = JSONUnmarshal(testSchema, &schema) assert.Nil(t, err) - props := schema["properties"].(map[string]interface{}) - str := props["str"].(map[string]interface{}) + props, ok := schema["properties"].(map[string]interface{}) + assert.True(t, ok) + str, ok := props["str"].(map[string]interface{}) + assert.True(t, ok) + str["format"] = "xml-template" err = validate(data, schema) @@ -591,11 +596,14 @@ func TestApplyDefaults_MissingIntFields(t *testing.T) { delete(data, "int") delete(data, "array_of_int") - obj := data["obj"].(map[string]interface{}) + obj, ok := data["obj"].(map[string]interface{}) + assert.True(t, ok) delete(obj, "int") - arr := data["array_of_obj"].([]interface{}) - arrObj := arr[0].(map[string]interface{}) + arr, ok := data["array_of_obj"].([]interface{}) + assert.True(t, ok) + arrObj, ok := arr[0].(map[string]interface{}) + assert.True(t, ok) delete(arrObj, "int") err = applyDefaults(&data, schema)