server/public/ -- pre-requisite changes (#23278)

* invert depdendency: filestore -> model

* markdown: nolint:misspell

* inline jsonutils within model

* push model.GetInfoForBytes -> channels/app

* push channel/utils.CompileGo* -> plugin/utils

* push plugin/scheduler -> channels/jobs/plugins

* push utils.Copy(File|Dir) -> model

* oauthproiders/gitlab -> channels/app/oauthproviders/gitlab

* decouple plugin from einterfaces.MetricsInterface

* fix TestGetInfoForFile

* Revert "Run golangci in server CI (#23240)"

This reverts commit 349e5d4573.

* add model/utils

---------

Co-authored-by: Agniva De Sarker <agnivade@yahoo.co.in>
Этот коммит содержится в:
Jesse Hallam
2023-05-09 13:30:02 -03:00
коммит произвёл GitHub
родитель 5c28071fb3
Коммит f28a2bcca7
42 изменённых файлов: 491 добавлений и 450 удалений

118
server/model/utils/file.go Обычный файл
Просмотреть файл

@@ -0,0 +1,118 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils
import (
"fmt"
"io"
"os"
"path/filepath"
)
// CopyFile will copy a file from src path to dst path.
// Overwrites any existing files at dst.
// Permissions are copied from file at src to the new file at dst.
func CopyFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
return
}
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
if e := out.Close(); e != nil {
err = e
}
}()
_, err = io.Copy(out, in)
if err != nil {
return
}
err = out.Sync()
if err != nil {
return
}
stat, err := os.Stat(src)
if err != nil {
return
}
err = os.Chmod(dst, stat.Mode())
if err != nil {
return
}
return
}
// CopyDir will copy a directory and all contained files and directories.
// src must exist and dst must not exist.
// Permissions are preserved when possible. Symlinks are skipped.
func CopyDir(src string, dst string) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
stat, err := os.Stat(src)
if err != nil {
return
}
if !stat.IsDir() {
return fmt.Errorf("source must be a directory")
}
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
if err == nil {
return fmt.Errorf("destination already exists")
}
err = os.MkdirAll(dst, stat.Mode())
if err != nil {
return
}
items, err := os.ReadDir(src)
if err != nil {
return
}
for _, item := range items {
srcPath := filepath.Join(src, item.Name())
dstPath := filepath.Join(dst, item.Name())
if item.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return
}
} else {
info, ierr := item.Info()
if ierr != nil {
continue
}
if info.Mode()&os.ModeSymlink != 0 {
continue
}
err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}
return
}

63
server/model/utils/file_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCopyDir(t *testing.T) {
srcDir, err := os.MkdirTemp("", "src")
require.NoError(t, err)
defer os.RemoveAll(srcDir)
dstParentDir, err := os.MkdirTemp("", "dstparent")
require.NoError(t, err)
defer os.RemoveAll(dstParentDir)
dstDir := filepath.Join(dstParentDir, "dst")
tempFile := "temp.txt"
err = os.WriteFile(filepath.Join(srcDir, tempFile), []byte("test file"), 0655)
require.NoError(t, err)
childDir := "child"
err = os.Mkdir(filepath.Join(srcDir, childDir), 0777)
require.NoError(t, err)
childTempFile := "childtemp.txt"
err = os.WriteFile(filepath.Join(srcDir, childDir, childTempFile), []byte("test file"), 0755)
require.NoError(t, err)
err = CopyDir(srcDir, dstDir)
assert.NoError(t, err)
stat, err := os.Stat(filepath.Join(dstDir, tempFile))
assert.NoError(t, err)
assert.Equal(t, uint32(0655), uint32(stat.Mode()))
assert.False(t, stat.IsDir())
data, err := os.ReadFile(filepath.Join(dstDir, tempFile))
assert.NoError(t, err)
assert.Equal(t, "test file", string(data))
stat, err = os.Stat(filepath.Join(dstDir, childDir))
assert.NoError(t, err)
assert.True(t, stat.IsDir())
stat, err = os.Stat(filepath.Join(dstDir, childDir, childTempFile))
assert.NoError(t, err)
assert.Equal(t, uint32(0755), uint32(stat.Mode()))
assert.False(t, stat.IsDir())
data, err = os.ReadFile(filepath.Join(dstDir, childDir, childTempFile))
assert.NoError(t, err)
assert.Equal(t, "test file", string(data))
err = CopyDir(srcDir, dstDir)
assert.Error(t, err)
}

56
server/model/utils/json.go Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils
import (
"bytes"
"encoding/json"
"github.com/pkg/errors"
)
type HumanizedJSONError struct {
Err error
Line int
Character int
}
func (e *HumanizedJSONError) Error() string {
return e.Err.Error()
}
// HumanizeJSONError extracts error offsets and annotates the error with useful context
func HumanizeJSONError(err error, data []byte) error {
if syntaxError, ok := err.(*json.SyntaxError); ok {
return NewHumanizedJSONError(syntaxError, data, syntaxError.Offset)
} else if unmarshalError, ok := err.(*json.UnmarshalTypeError); ok {
return NewHumanizedJSONError(unmarshalError, data, unmarshalError.Offset)
} else {
return err
}
}
func NewHumanizedJSONError(err error, data []byte, offset int64) *HumanizedJSONError {
if err == nil {
return nil
}
if offset < 0 || offset > int64(len(data)) {
return &HumanizedJSONError{
Err: errors.Wrapf(err, "invalid offset %d", offset),
}
}
lineSep := []byte{'\n'}
line := bytes.Count(data[:offset], lineSep) + 1
lastLineOffset := bytes.LastIndex(data[:offset], lineSep)
character := int(offset) - (lastLineOffset + 1) + 1
return &HumanizedJSONError{
Line: line,
Character: character,
Err: errors.Wrapf(err, "parsing error at line %d, character %d", line, character),
}
}

235
server/model/utils/json_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,235 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils_test
import (
"encoding/json"
"reflect"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/server/v8/model/utils"
)
func TestHumanizeJsonError(t *testing.T) {
t.Parallel()
type testType struct{}
testCases := []struct {
Description string
Data []byte
Err error
ExpectedErr string
}{
{
"nil error",
[]byte{},
nil,
"",
},
{
"non-special error",
[]byte{},
errors.New("test"),
"test",
},
{
"syntax error, offset 17, middle of line 3",
[]byte("line 1\nline 2\nline 3"),
&json.SyntaxError{
// msg can't be set
Offset: 17,
},
"parsing error at line 3, character 4: ",
},
{
"unmarshal type error, offset 17, middle of line 3",
[]byte("line 1\nline 2\nline 3"),
&json.UnmarshalTypeError{
Value: "bool",
Type: reflect.TypeOf(testType{}),
Offset: 17,
Struct: "struct",
Field: "field",
},
"parsing error at line 3, character 4: json: cannot unmarshal bool into Go struct field struct.field of type utils_test.testType",
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.Description, func(t *testing.T) {
actual := utils.HumanizeJSONError(testCase.Err, testCase.Data)
if testCase.ExpectedErr == "" {
assert.NoError(t, actual)
} else {
assert.EqualError(t, actual, testCase.ExpectedErr)
}
})
}
}
func TestNewHumanizedJSONError(t *testing.T) {
t.Parallel()
testCases := []struct {
Description string
Data []byte
Offset int64
Err error
Expected *utils.HumanizedJSONError
}{
{
"nil error",
[]byte{},
0,
nil,
nil,
},
{
"offset -1, before start of string",
[]byte("line 1\nline 2\nline 3"),
-1,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "invalid offset -1"),
},
},
{
"offset 0, start of string",
[]byte("line 1\nline 2\nline 3"),
0,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 1"),
Line: 1,
Character: 1,
},
},
{
"offset 5, end of line 1",
[]byte("line 1\nline 2\nline 3"),
5,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 6"),
Line: 1,
Character: 6,
},
},
{
"offset 6, new line at end end of line 1",
[]byte("line 1\nline 2\nline 3"),
6,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 7"),
Line: 1,
Character: 7,
},
},
{
"offset 7, start of line 2",
[]byte("line 1\nline 2\nline 3"),
7,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 1"),
Line: 2,
Character: 1,
},
},
{
"offset 12, end of line 2",
[]byte("line 1\nline 2\nline 3"),
12,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 6"),
Line: 2,
Character: 6,
},
},
{
"offset 13, newline at end of line 2",
[]byte("line 1\nline 2\nline 3"),
13,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 7"),
Line: 2,
Character: 7,
},
},
{
"offset 17, middle of line 3",
[]byte("line 1\nline 2\nline 3"),
17,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 4"),
Line: 3,
Character: 4,
},
},
{
"offset 19, end of string",
[]byte("line 1\nline 2\nline 3"),
19,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 6"),
Line: 3,
Character: 6,
},
},
{
"offset 20, offset = length of string",
[]byte("line 1\nline 2\nline 3"),
20,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 7"),
Line: 3,
Character: 7,
},
},
{
"offset 21, offset = length of string, after newline",
[]byte("line 1\nline 2\nline 3\n"),
21,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "parsing error at line 4, character 1"),
Line: 4,
Character: 1,
},
},
{
"offset 21, offset > length of string",
[]byte("line 1\nline 2\nline 3"),
21,
errors.New("message"),
&utils.HumanizedJSONError{
Err: errors.Wrap(errors.New("message"), "invalid offset 21"),
},
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.Description, func(t *testing.T) {
actual := utils.NewHumanizedJSONError(testCase.Err, testCase.Data, testCase.Offset)
if testCase.Expected != nil && actual.Err != nil {
if assert.EqualValues(t, testCase.Expected.Err.Error(), actual.Err.Error()) {
actual.Err = testCase.Expected.Err
}
}
assert.Equal(t, testCase.Expected, actual)
})
}
}