diff --git a/Makefile b/Makefile
index 8762b1b1..6fa589b7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,26 +1,25 @@
.PHONY: all
-all: go-build
+all: build
ifneq (on,$(GO111MODULE))
export GO111MODULE := on
endif
-#.PHONY: go-dep
-go-dep:
+#.PHONY: dep
+dep:
go mod download && go mod verify
-.PHONY: go-build
-go-build:
+.PHONY: build
+build:
git config core.hooksPath hooks
go build .
#
# Tests-related tasks
#
-.PHONY: go-unittest
-go-unittest: go-build
+.PHONY: unit-test
+unit-test: build
go test -json ./... -run ^Test
-.PHONY: go-coverage
-go-coverage: go-build
+.PHONY: coverage
+coverage: build
go test -json -covermode=atomic -coverpkg=./... -coverprofile goisilon_coverprofile.out ./... -run ^Test
-
diff --git a/acls_test.go b/acls_test.go
index 626a340a..51f6b41d 100755
--- a/acls_test.go
+++ b/acls_test.go
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2022 Dell Inc, or its subsidiaries.
+Copyright (c) 2022-2025 Dell Inc, or its subsidiaries.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,105 +16,38 @@ limitations under the License.
package goisilon
import (
- "fmt"
"testing"
+ "github.com/dell/goisilon/mocks"
"github.com/stretchr/testify/assert"
-
- api "github.com/dell/goisilon/api/v2"
)
func TestGetVolumeACL(t *testing.T) {
- volumeName := "test_get_volume_acl"
-
- // make sure the volume exists
- client.CreateVolume(defaultCtx, volumeName)
- volume, err := client.GetVolume(defaultCtx, volumeName, volumeName)
- assertNoError(t, err)
- assertNotNil(t, volume)
-
- defer client.DeleteVolume(defaultCtx, volume.Name)
-
- username := client.API.User()
- user, err := client.GetUserByNameOrUID(defaultCtx, &username, nil)
- assertNoError(t, err)
- assertNotNil(t, user)
-
- acl, err := client.GetVolumeACL(defaultCtx, volume.Name)
- assertNoError(t, err)
- assertNotNil(t, acl)
-
- assertNotNil(t, acl.Owner)
- assertNotNil(t, acl.Owner.Name)
- assert.Equal(t, user.Name, *acl.Owner.Name)
- assertNotNil(t, acl.Owner.Type)
- assert.Equal(t, api.PersonaTypeUser, *acl.Owner.Type)
- assertNotNil(t, acl.Owner.ID)
- assert.Equal(t, user.OnDiskUserIdentity.ID, fmt.Sprintf("UID:%s", acl.Owner.ID.ID))
- assert.Equal(t, api.PersonaIDTypeUID, acl.Owner.ID.Type)
+ client.API.(*mocks.Client).On("VolumesPath", anyArgs[0:6]...).Return("").Once()
+ client.API.(*mocks.Client).On("Get", anyArgs[0:6]...).Return(nil).Once()
+ _, err := client.GetVolumeACL(defaultCtx, "test_get_volume_acl")
+ assert.Nil(t, err)
}
func TestSetVolumeOwnerToCurrentUser(t *testing.T) {
- volumeName := "test_set_volume_owner"
-
- // make sure the volume exists
- client.CreateVolume(defaultCtx, volumeName)
- volume, err := client.GetVolume(defaultCtx, volumeName, volumeName)
- assertNoError(t, err)
- assertNotNil(t, volume)
-
- defer client.DeleteVolume(defaultCtx, volume.Name)
-
- username := client.API.User()
- user, err := client.GetUserByNameOrUID(defaultCtx, &username, nil)
- assertNoError(t, err)
- assertNotNil(t, user)
-
- acl, err := client.GetVolumeACL(defaultCtx, volume.Name)
- assertNoError(t, err)
- assertNotNil(t, acl)
-
- assertNotNil(t, acl.Owner)
- assertNotNil(t, acl.Owner.Name)
- assert.Equal(t, user.Name, *acl.Owner.Name)
- assertNotNil(t, acl.Owner.Type)
- assert.Equal(t, api.PersonaTypeUser, *acl.Owner.Type)
- assertNotNil(t, acl.Owner.ID)
- assert.Equal(t, user.OnDiskUserIdentity.ID, fmt.Sprintf("UID:%s", acl.Owner.ID.ID))
- assert.Equal(t, api.PersonaIDTypeUID, acl.Owner.ID.Type)
-
- err = client.SetVolumeOwner(defaultCtx, volume.Name, "rexray")
- if err != nil {
- t.Skipf("Unable to change volume owner: %s - is efs.bam.chown_unrestricted set?", err)
- }
- assertNoError(t, err)
-
- acl, err = client.GetVolumeACL(defaultCtx, volume.Name)
- assertNoError(t, err)
- assertNotNil(t, acl)
-
- assertNotNil(t, acl.Owner)
- assertNotNil(t, acl.Owner.Name)
- assert.Equal(t, "rexray", *acl.Owner.Name)
- assertNotNil(t, acl.Owner.Type)
- assert.Equal(t, api.PersonaTypeUser, *acl.Owner.Type)
- assertNotNil(t, acl.Owner.ID)
- assert.Equal(t, "2000", acl.Owner.ID.ID)
- assert.Equal(t, api.PersonaIDTypeUID, acl.Owner.ID.Type)
-
- err = client.SetVolumeOwnerToCurrentUser(defaultCtx, volume.Name)
- assertNoError(t, err)
+ client.API.(*mocks.Client).On("VolumesPath", anyArgs[0:6]...).Return("").Once()
+ client.API.(*mocks.Client).On("Get", anyArgs[0:6]...).Return(nil).Once()
+ client.API.(*mocks.Client).On("User", anyArgs[0:6]...).Return("").Once()
+ client.API.(*mocks.Client).On("Put", anyArgs...).Return(nil).Once()
+ err := client.SetVolumeOwnerToCurrentUser(defaultCtx, "test_set_volume_owner")
+ assert.Nil(t, err)
+}
- acl, err = client.GetVolumeACL(defaultCtx, volume.Name)
- assertNoError(t, err)
- assertNotNil(t, acl)
+func TestSetVolumeOwner(t *testing.T) {
+ client.API.(*mocks.Client).On("VolumesPath", anyArgs[0:6]...).Return("").Once()
+ client.API.(*mocks.Client).On("Put", anyArgs...).Return(nil).Once()
+ err := client.SetVolumeOwner(defaultCtx, "test_set_volume_owner", "rexray")
+ assert.Nil(t, err)
+}
- assertNotNil(t, acl.Owner)
- assertNotNil(t, acl.Owner.Name)
- assert.Equal(t, client.API.User(), *acl.Owner.Name)
- assertNotNil(t, acl.Owner.Type)
- assert.Equal(t, api.PersonaTypeUser, *acl.Owner.Type)
- assertNotNil(t, acl.Owner.ID)
- assert.Equal(t, "10", acl.Owner.ID.ID)
- assert.Equal(t, api.PersonaIDTypeUID, acl.Owner.ID.Type)
+func TestSetVolumeMode(t *testing.T) {
+ client.API.(*mocks.Client).On("VolumesPath", anyArgs[0:6]...).Return("").Once()
+ client.API.(*mocks.Client).On("Put", anyArgs...).Return(nil).Once()
+ err := client.SetVolumeMode(defaultCtx, "test_set_volume_owner", 777)
+ assert.Nil(t, err)
}
diff --git a/api/api.go b/api/api.go
index b299bdb6..8b271256 100755
--- a/api/api.go
+++ b/api/api.go
@@ -20,6 +20,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -35,7 +36,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/PuerkitoBio/goquery"
- "github.com/dell/goisilon/api/json"
)
const (
@@ -389,6 +389,10 @@ func (c *client) DoWithHeaders(
params OrderedValues, headers map[string]string,
body, resp interface{},
) error {
+ return doWithHeadersFunc(c, ctx, method, uri, id, params, headers, body, resp)
+}
+
+var doWithHeadersFunc = func(c *client, ctx context.Context, method string, uri string, id string, params OrderedValues, headers map[string]string, body, resp interface{}) error {
res, _, err := c.DoAndGetResponseBody(
ctx, method, uri, id, params, headers, body)
if err != nil {
@@ -425,6 +429,16 @@ func (c *client) DoAndGetResponseBody(
method, uri, id string,
params OrderedValues, headers map[string]string,
body interface{},
+) (*http.Response, bool, error) {
+ return doAndGetResponseBodyFunc(c, ctx, method, uri, id, params, headers, body)
+}
+
+var doAndGetResponseBodyFunc = func(
+ c *client,
+ ctx context.Context,
+ method, uri, id string,
+ params OrderedValues, headers map[string]string,
+ body interface{},
) (*http.Response, bool, error) {
var (
err error
@@ -650,6 +664,10 @@ func parseJSONHTMLError(r *http.Response) error {
// Authenticate make a REST API call [/session/1/session] to PowerScale to authenticate the given credentials.
// The response contains the session Cookie, X-CSRF-Token and the client uses it for further communication.
func (c *client) authenticate(ctx context.Context, username string, password string, endpoint string) error {
+ return authenticateFunc(c, ctx, username, password, endpoint)
+}
+
+var authenticateFunc = func(c *client, ctx context.Context, username string, password string, endpoint string) error {
headers := make(map[string]string, 1)
headers[headerKeyContentType] = headerValContentTypeJSON
data := &setupConnection{Services: []string{"platform", "namespace"}, Username: username, Password: password}
@@ -722,9 +740,9 @@ func (c *client) executeWithRetryAuthenticate(ctx context.Context, method, uri s
return fmt.Errorf("authentication failure due to: %v", err)
}
return c.DoWithHeaders(ctx, method, uri, id, params, headers, body, resp)
- } else {
- log.Error(ctx, "Error in response. Method:%s URI:%s Error: %v JSON Error: %+v", method, uri, err, e)
}
+ log.Error(ctx, "Error in response. Method:%s URI:%s Error: %v JSON Error: %+v", method, uri, err, e)
+
case *HTMLError:
if e.StatusCode == 401 {
log.Debug(ctx, "Authentication failed. Trying to re-authenticate")
@@ -732,9 +750,8 @@ func (c *client) executeWithRetryAuthenticate(ctx context.Context, method, uri s
return fmt.Errorf("authentication failure due to: %v", err)
}
return c.DoWithHeaders(ctx, method, uri, id, params, headers, body, resp)
- } else {
- log.Error(ctx, "Error in response. Method:%s URI:%s Error: %v HTML Error: %+v", method, uri, err, e)
}
+ log.Error(ctx, "Error in response. Method:%s URI:%s Error: %v HTML Error: %+v", method, uri, err, e)
default:
log.Error(ctx, "Error is not a type of \"*JSONError or *HTMLError\". Error:", err)
}
diff --git a/api/api_logging_test.go b/api/api_logging_test.go
new file mode 100644
index 00000000..4ce6da2a
--- /dev/null
+++ b/api/api_logging_test.go
@@ -0,0 +1,222 @@
+/*
+Copyright (c) 2025 Dell Inc, or its subsidiaries.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIsBinOctetBody(t *testing.T) {
+ header := http.Header{}
+ header.Add(headerKeyContentType, headerValContentTypeBinaryOctetStream)
+ assert.True(t, isBinOctetBody(header))
+
+ header.Set(headerKeyContentType, "application/json")
+ assert.False(t, isBinOctetBody(header))
+}
+
+func TestLogRequest(t *testing.T) {
+ req := httptest.NewRequest("GET", "/test-url", nil)
+ ctx := context.Background()
+ var out bytes.Buffer
+
+ t.Run("VerboseLow", func(t *testing.T) {
+ out.Reset()
+ logRequest(ctx, &out, req, VerboseLow)
+ assert.Contains(t, out.String(), "GET /test-url ")
+ })
+
+ t.Run("VerboseHigh", func(t *testing.T) {
+ out.Reset()
+ logRequest(ctx, &out, req, VerboseHigh)
+ assert.Contains(t, out.String(), "GET /test-url ")
+ assert.Contains(t, out.String(), "Host: example.com")
+ })
+}
+
+func TestLogResponse(t *testing.T) {
+ res := httptest.NewRecorder().Result()
+ ctx := context.Background()
+ var out bytes.Buffer
+
+ t.Run("VerboseLow", func(t *testing.T) {
+ out.Reset()
+ logResponse(ctx, res, VerboseLow)
+ assert.Contains(t, out.String(), "")
+ })
+
+ t.Run("VerboseMedium", func(t *testing.T) {
+ out.Reset()
+ logResponse(ctx, res, VerboseMedium)
+ assert.Contains(t, out.String(), "")
+ })
+
+ t.Run("VerboseHigh", func(t *testing.T) {
+ out.Reset()
+ logResponse(ctx, res, VerboseHigh)
+ assert.Contains(t, out.String(), "")
+ })
+}
+
+func TestWriteIndented(t *testing.T) {
+ data := []byte("line1\nline2\nline3")
+ var out bytes.Buffer
+ err := WriteIndented(&out, data)
+ assert.NoError(t, err)
+ assert.Equal(t, " line1\n line2\n line3", out.String())
+}
+
+type errorWriter struct {
+ failAfter int
+ writes int
+}
+
+func (ew *errorWriter) Write(p []byte) (n int, err error) {
+ if ew.writes >= ew.failAfter {
+ return 0, errors.New("forced write error")
+ }
+ ew.writes++
+ return len(p), nil
+}
+
+func TestWriteIndentedN(t *testing.T) {
+ data := []byte("line1\nline2\nline3")
+
+ // Original test case to ensure it still passes
+ t.Run("normal case", func(t *testing.T) {
+ var out bytes.Buffer
+ err := WriteIndentedN(&out, data, 2)
+ assert.NoError(t, err)
+ assert.Equal(t, " line1\n line2\n line3", out.String())
+ })
+
+ // Test case to cover error scenarios
+ t.Run("error after writing space", func(t *testing.T) {
+ ew := &errorWriter{failAfter: 1}
+ err := WriteIndentedN(ew, data, 2)
+ assert.Error(t, err)
+ assert.Equal(t, "forced write error", err.Error())
+ })
+
+ t.Run("error after writing line content", func(t *testing.T) {
+ ew := &errorWriter{failAfter: 3} // 2 spaces + 1 line content = 3 writes
+ err := WriteIndentedN(ew, data, 2)
+ assert.Error(t, err)
+ assert.Equal(t, "forced write error", err.Error())
+ })
+
+ t.Run("error after writing newline", func(t *testing.T) {
+ ew := &errorWriter{failAfter: 4} // 2 spaces + 1 line content + 1 newline = 4 writes
+ err := WriteIndentedN(ew, data, 2)
+ assert.Error(t, err)
+ assert.Equal(t, "forced write error", err.Error())
+ })
+}
+
+func TestEncryptPassword(t *testing.T) {
+ reqData := "GET / HTTP/1.1\nAuthorization: Basic " + base64.StdEncoding.EncodeToString([]byte("user:password")) + "\n"
+
+ result := encryptPassword([]byte(reqData))
+ expected := "GET / HTTP/1.1\nAuthorization: user:******\n"
+ assert.Equal(t, expected, string(result))
+
+ cases := []struct {
+ name string
+ input string
+ expected string
+ }{
+ {
+ name: "password",
+ input: `{"password":"my-secret-password"}`,
+ expected: `{"password":"****"}` + "\n",
+ },
+ {
+ name: "session id",
+ input: "Cookie: isisessid=my-session-id",
+ expected: "Cookie: isisessid=****-session-id\n",
+ },
+ {
+ name: "CSRF Token",
+ input: "X-Csrf-Token: my-csrf-token",
+ expected: "X-Csrf-Token:****-csrf-token\n",
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ result := encryptPassword([]byte(c.input))
+ assert.Contains(t, c.expected, string(result))
+ })
+ }
+}
+
+func TestFetchValueIndexForKey(t *testing.T) {
+ cases := []struct {
+ name string
+ line string
+ key string
+ separator string
+ expStart int
+ expEnd int
+ expMatchLen int
+ }{
+ {
+ name: "no separator",
+ line: `"password":"my-secret-password"`,
+ key: `"password":"`,
+ separator: `"`,
+ expStart: 0,
+ expEnd: 18,
+ expMatchLen: 12,
+ },
+ {
+ name: "with separator",
+ line: `Cookie: isisessid=my-session-id; path=/`,
+ key: `isisessid=`,
+ separator: `;`,
+ expStart: 8,
+ expEnd: 13,
+ expMatchLen: 10,
+ },
+ {
+ name: "full key",
+ line: `X-Csrf-Token: my-csrf-token`,
+ key: `X-Csrf-Token:`,
+ separator: ` `,
+ expStart: 0,
+ expEnd: 0,
+ expMatchLen: 13,
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ start, end, matchLen := FetchValueIndexForKey(c.line, c.key, c.separator)
+ assert.Equal(t, c.expStart, start)
+ assert.Equal(t, c.expEnd, end)
+ assert.Equal(t, c.expMatchLen, matchLen)
+ })
+ }
+}
diff --git a/api/api_ordered_values_test.go b/api/api_ordered_values_test.go
index 9ba1f0e6..34446472 100644
--- a/api/api_ordered_values_test.go
+++ b/api/api_ordered_values_test.go
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2022 Dell Inc, or its subsidiaries.
+Copyright (c) 2022-2025 Dell Inc, or its subsidiaries.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -245,3 +245,16 @@ func TestStructToOrderedValues(t *testing.T) {
t.Errorf("StructToOrderedValue(%v) = %v; want %v", params, actual, expected)
}
}
+
+func TestStringSet(t *testing.T) {
+ var v OrderedValues
+ v.Set([]byte("query"), nil)
+ v.StringSet("", "")
+ assert.Equal(t, "query", v.Encode())
+
+ v.StringSet("key", "")
+ assert.Equal(t, "query&key", v.Encode())
+
+ v.StringSet("key", "value")
+ assert.Equal(t, "query&key=value", v.Encode())
+}
diff --git a/api/api_test.go b/api/api_test.go
index 6216f396..418bac26 100755
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2022 Dell Inc, or its subsidiaries.
+Copyright (c) 2022-2025 Dell Inc, or its subsidiaries.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,11 +16,37 @@ limitations under the License.
package api
import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "strings"
"testing"
+ "time"
"github.com/stretchr/testify/assert"
)
+type (
+ EmptyMockBody struct{}
+ MockBody struct {
+ ReadFunc func(p []byte) (n int, err error)
+ CloseFunc func() error
+ }
+)
+
+func (m *MockBody) Read(p []byte) (n int, err error) {
+ return m.ReadFunc(p)
+}
+
+func (m *MockBody) Close() error {
+ return m.CloseFunc()
+}
+
func assertLen(t *testing.T, obj interface{}, expLen int) {
if !assert.Len(t, obj, expLen) {
t.FailNow()
@@ -50,3 +76,633 @@ func assertNotNil(t *testing.T, i interface{}) {
t.FailNow()
}
}
+
+func newMockHTTPServer(handleReq func(http.ResponseWriter, *http.Request)) *httptest.Server {
+ return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ handleReq(w, r)
+ }))
+}
+
+func TestNew(t *testing.T) {
+ getReqHandler := func(serverVersion string) func(http.ResponseWriter, *http.Request) {
+ if serverVersion != "" {
+ return func(w http.ResponseWriter, _ *http.Request) {
+ res := &apiVerResponse{Latest: &serverVersion}
+ w.WriteHeader(http.StatusOK)
+ body, err := json.Marshal(res)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(body)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ }
+ }
+ return func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ }
+ }
+
+ serverURL := "server.URL"
+
+ testData := []struct {
+ testName string
+ hostname string
+ username string
+ password string
+ groupName string
+ verboseLogging uint
+ authType uint8
+ opts *ClientOptions
+ reqHandler func(http.ResponseWriter, *http.Request)
+ expectedErr string
+ }{
+ {
+ testName: "Negative: empty call params",
+ expectedErr: "missing endpoint, username, or password",
+ },
+ {
+ testName: "Negative: bad hostname",
+ hostname: "test",
+ username: "testuser",
+ password: "testpassword",
+ authType: 42, // unknown auth type should default to basic
+ opts: &ClientOptions{
+ VolumesPath: "test/volumes",
+ VolumesPathPermissions: "test/permissions",
+ IgnoreUnresolvableHosts: true,
+ Timeout: 10 * time.Second,
+ Insecure: true,
+ },
+ expectedErr: "unsupported protocol scheme",
+ },
+ {
+ testName: "Negative: empty server response",
+ hostname: serverURL,
+ username: "testuser",
+ password: "testpassword",
+ groupName: "testgroup",
+ verboseLogging: 1,
+ authType: authTypeSessionBased,
+ opts: &ClientOptions{
+ Insecure: false,
+ },
+ reqHandler: getReqHandler(""),
+ expectedErr: "OneFS releases older than",
+ },
+ {
+ testName: "Negative: malformed major version in response",
+ hostname: serverURL,
+ username: "testuser",
+ password: "testpassword",
+ reqHandler: getReqHandler("a.3"),
+ expectedErr: "strconv.ParseUint: parsing ",
+ },
+ {
+ testName: "Negative: malformed minor version in response",
+ hostname: serverURL,
+ username: "testuser",
+ password: "testpassword",
+ reqHandler: getReqHandler("8.b"),
+ expectedErr: "strconv.ParseUint: parsing ",
+ },
+ {
+ testName: "Positive: correct version in response",
+ hostname: serverURL,
+ username: "testuser",
+ password: "testpassword",
+ reqHandler: getReqHandler("8.3"),
+ expectedErr: "",
+ },
+ }
+
+ for _, td := range testData {
+ t.Run(td.testName, func(t *testing.T) {
+ if td.reqHandler != nil {
+ server := newMockHTTPServer(td.reqHandler)
+ if td.hostname == serverURL {
+ td.hostname = server.URL
+ }
+ defer server.Close()
+ }
+ c, err := New(
+ context.Background(),
+ td.hostname,
+ td.username,
+ td.password,
+ td.groupName,
+ td.verboseLogging,
+ td.authType,
+ td.opts)
+ if td.expectedErr != "" {
+ assert.ErrorContains(t, err, td.expectedErr)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, c)
+ }
+ })
+ }
+}
+
+func TestDoAndGetResponseBody(t *testing.T) {
+ // Create a mock client
+ c := &client{
+ hostname: "https://example.com",
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+
+ res := &http.Response{
+ StatusCode: http.StatusOK,
+ Body: io.NopCloser(nil),
+ }
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method)
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ headers := map[string]string{
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ }
+ orderedValues := [][][]byte{
+ {
+ []byte("value1"),
+ []byte("value2"),
+ },
+ }
+ res, _, err := c.DoAndGetResponseBody(ctx, http.MethodGet, "api/v1/endpoint", "", orderedValues, headers, EmptyMockBody{})
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusOK, res.StatusCode)
+
+ body := &MockBody{
+ ReadFunc: func(_ []byte) (n int, err error) {
+ return 0, io.EOF
+ },
+ CloseFunc: func() error {
+ return nil
+ },
+ }
+ res, _, _ = c.DoAndGetResponseBody(ctx, http.MethodGet, "api/v1/endpoint", "ID", orderedValues, headers, body)
+ assert.Equal(t, http.StatusOK, res.StatusCode)
+}
+
+func TestAuthenticate(t *testing.T) {
+ defaultDoAndGetResponseBodyFunc := doAndGetResponseBodyFunc
+ defer func() {
+ doAndGetResponseBodyFunc = defaultDoAndGetResponseBodyFunc
+ }()
+
+ c := &client{
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+ username := "testuser"
+ password := "testpassword"
+ endpoint := "https://example.com"
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, http.MethodPost, r.Method)
+ assert.Equal(t, "/session/1/session/", r.URL.Path)
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"Authentication successful"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ err := c.authenticate(ctx, username, password, endpoint)
+ assert.Equal(t, errors.New("authenticate error. response-"), err)
+ assert.Equal(t, "", c.GetReferer())
+
+ // Create a mock server for 201 response code
+ server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, http.MethodPost, r.Method)
+ assert.Equal(t, "/session/1/session/", r.URL.Path)
+
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte(`{"message":"Authentication successful"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ _ = c.authenticate(ctx, username, password, endpoint)
+ assert.Equal(t, "", c.GetReferer())
+
+ // create a mock server for 401 response code
+ server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPost, r.Method)
+ assert.Equal(t, "/session/1/session/", r.URL.Path)
+ w.WriteHeader(http.StatusUnauthorized)
+ w.Write([]byte(`{"message":"Authentication failed"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ err = c.authenticate(ctx, username, password, endpoint)
+ assert.EqualError(t, err, "authentication failed. unable to login to powerscale. verify username and password")
+
+ // force an error with a failed doAndGetResponseBody
+ doAndGetResponseBodyFunc = func(
+ _ *client,
+ _ context.Context,
+ _, _, _ string,
+ _ OrderedValues, _ map[string]string,
+ _ interface{},
+ ) (*http.Response, bool, error) {
+ return nil, false, errors.New("failed doAndGetResponseBody")
+ }
+ err = c.authenticate(ctx, username, password, endpoint)
+ assert.EqualError(t, err, "Authentication error: failed doAndGetResponseBody")
+}
+
+func TestExecuteWithRetryAuthenticate(t *testing.T) {
+ // error injection
+ defaultDoWithHeadersFunc := doWithHeadersFunc
+ defaultAuthenticateFunc := authenticateFunc
+ defer func() {
+ doWithHeadersFunc = defaultDoWithHeadersFunc
+ authenticateFunc = defaultAuthenticateFunc
+ }()
+ // Create a mock client
+ c := &client{
+ http: http.DefaultClient,
+ authType: authTypeBasic,
+ username: "testuser",
+ password: "testpassword",
+ }
+ ctx := context.Background()
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodGet {
+ if strings.HasPrefix(r.URL.Path, "/bad-auth-") {
+ var res *JSONError
+ if strings.HasSuffix(r.URL.Path, "401/") {
+ res = &JSONError{StatusCode: http.StatusUnauthorized, Err: []Error{{Message: "Unauthorized", Code: "401"}}}
+ w.WriteHeader(http.StatusUnauthorized)
+ } else if strings.HasSuffix(r.URL.Path, "400/") {
+ res = &JSONError{StatusCode: http.StatusBadRequest, Err: []Error{{Message: "Bad Request", Code: "400"}}}
+ w.WriteHeader(http.StatusBadRequest)
+ } else {
+ res = &JSONError{StatusCode: http.StatusNotFound, Err: []Error{{Message: "Unknown URL", Code: "404"}}}
+ w.WriteHeader(http.StatusNotFound)
+ }
+ body, err := json.Marshal(res)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(body)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ return
+ } else if strings.HasPrefix(r.URL.Path, "/bad-html-auth-") {
+ var body string
+ w.Header().Set("Content-Type", "text/html")
+ if strings.HasSuffix(r.URL.Path, "401/") {
+ body = "
HTML error 401 title"
+ w.WriteHeader(http.StatusUnauthorized)
+ } else if strings.HasSuffix(r.URL.Path, "400/") {
+ body = "HTML error 400 title"
+ w.WriteHeader(http.StatusBadRequest)
+ } else {
+ body = "HTML error title"
+ w.WriteHeader(http.StatusNotFound)
+ }
+ _, err := w.Write([]byte(body))
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ return
+ } else if r.URL.Path == "/good-path/" {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ } else if r.Method == http.MethodPost {
+ // Authentication successful
+ w.Header().Set(isiSessCsrfToken, "isisessid=123;isicsrf=abc;")
+ w.WriteHeader(http.StatusCreated)
+ return
+ }
+ w.WriteHeader(http.StatusInternalServerError)
+ }))
+ defer server.Close()
+
+ c.hostname = server.URL
+ headers := map[string]string{
+ "Content-Type": "text/html",
+ }
+
+ err := c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/good-path", "", nil, headers, nil, nil)
+ assert.NoError(t, err)
+
+ c.authType = authTypeSessionBased
+
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/good-path", "", nil, headers, nil, nil)
+ assert.NoError(t, err)
+
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-auth-401", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-auth-400", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-html-auth-401", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-html-auth-400", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+
+ // force doWithHeaders to return unexpected error
+ doWithHeadersFunc = func(_ *client, _ context.Context, _ string, _ string, _ string, _ OrderedValues, _ map[string]string, _, _ interface{}) error {
+ return fmt.Errorf("mock error")
+ }
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-html-auth-400", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+ doWithHeadersFunc = defaultDoWithHeadersFunc
+
+ // force authenticate to return an error
+ authenticateFunc = func(_ *client, _ context.Context, _, _, _ string) error {
+ return errors.New("failed auth")
+ }
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-html-auth-401", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+ err = c.executeWithRetryAuthenticate(ctx, http.MethodGet, "/bad-auth-401", "", nil, headers, nil, nil)
+ assert.Error(t, err)
+}
+
+func TestDoWithHeaders(t *testing.T) {
+ // Create a mock client
+ c := &client{
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, http.MethodGet, r.Method)
+ assert.Equal(t, "/api/v1/endpoint/", r.URL.String())
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"Success"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ resp := &struct {
+ Message string `json:"message"`
+ }{}
+
+ err := c.DoWithHeaders(ctx, http.MethodGet, "api/v1/endpoint", "", nil, nil, nil, resp)
+ assert.NoError(t, err)
+ expectedResp := &struct {
+ Message string `json:"message"`
+ }{
+ Message: "Success",
+ }
+ assert.Equal(t, expectedResp, resp)
+}
+
+func TestClient_APIVersion(t *testing.T) {
+ c := &client{apiVersion: 1}
+ assert.Equal(t, uint8(1), c.APIVersion())
+}
+
+func TestClient_User(t *testing.T) {
+ c := &client{username: "testuser"}
+ assert.Equal(t, "testuser", c.User())
+}
+
+func TestClient_Group(t *testing.T) {
+ c := &client{groupname: "testgroup"}
+ assert.Equal(t, "testgroup", c.Group())
+}
+
+func TestClient_VolumesPath(t *testing.T) {
+ c := &client{volumePath: "/mnt/volumes"}
+ assert.Equal(t, "/mnt/volumes", c.VolumesPath())
+}
+
+func TestClient_VolumePath(t *testing.T) {
+ c := &client{volumePath: "/mnt/volumes"}
+ assert.Equal(t, "/mnt/volumes/volume1", c.VolumePath("volume1"))
+}
+
+func TestHTMLError_Error(t *testing.T) {
+ err := &HTMLError{Message: "HTML error message"}
+ assert.Equal(t, "HTML error message", err.Error())
+}
+
+func TestClient_SetAuthToken(t *testing.T) {
+ c := &client{}
+ c.SetAuthToken("testcookie")
+ assert.Equal(t, "testcookie", c.sessionCredentials.sessionCookies)
+}
+
+func TestClient_SetCSRFToken(t *testing.T) {
+ c := &client{}
+ c.SetCSRFToken("testcsrf")
+ assert.Equal(t, "testcsrf", c.sessionCredentials.sessionCSRF)
+}
+
+func TestClient_SetReferer(t *testing.T) {
+ c := &client{}
+ c.SetReferer("testreferer")
+ assert.Equal(t, "testreferer", c.sessionCredentials.referer)
+}
+
+func TestClient_GetCSRFToken(t *testing.T) {
+ c := &client{}
+ c.GetCSRFToken()
+ assert.Equal(t, "", c.sessionCredentials.sessionCSRF)
+}
+
+func TestParseJSONHTMLError(t *testing.T) {
+ tests := []struct {
+ name string
+ contentType string
+ body string
+ expectedErr error
+ expectedStatus int
+ }{
+ {
+ name: "HTML error response",
+ contentType: "text/html",
+ body: `HTML error titleHTML error message
`,
+ expectedErr: &HTMLError{Message: "HTML error message"},
+ expectedStatus: 401,
+ },
+ {
+ name: "HTML error without h1",
+ contentType: "text/html",
+ body: `HTML error title`,
+ expectedErr: &HTMLError{Message: "HTML error title"},
+ expectedStatus: 403,
+ },
+ {
+ name: "Invalid JSON",
+ contentType: "application/json",
+ body: `{invalid json`,
+ expectedErr: &JSONError{Err: []Error{{Message: "invalid character 'i' looking for beginning of object key string"}}},
+ expectedStatus: 400,
+ },
+ {
+ name: "Invalid HTML",
+ contentType: "text/html",
+ body: ``,
+ expectedErr: &HTMLError{Message: ""},
+ expectedStatus: 500,
+ },
+ {
+ name: "JSON error with empty message",
+ contentType: "application/json",
+ body: `{"errors":[{"message":""}]}`,
+ expectedErr: &JSONError{Err: []Error{{Message: "400"}}},
+ expectedStatus: 400,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ body := bytes.NewBufferString(tt.body)
+ resp := httptest.NewRecorder()
+ resp.Body = body
+ resp.Header().Set("Content-Type", tt.contentType)
+ resp.Code = tt.expectedStatus
+
+ err := parseJSONHTMLError(resp.Result())
+
+ if tt.expectedErr != nil {
+ assert.NotNil(t, err)
+
+ switch expected := tt.expectedErr.(type) {
+ case *JSONError:
+ assert.Contains(t, err.Error(), expected.Error())
+ default:
+ assert.IsType(t, expected, err)
+ assert.EqualError(t, err, expected.Error())
+ }
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestClient_Put(t *testing.T) {
+ // Create a mock client
+ c := &client{
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, http.MethodPut, r.Method)
+ assert.Equal(t, "/PUT/api/v1/endpoint", r.URL.String())
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"Success"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ body := map[string]string{
+ "Content-Type": "application/json",
+ }
+ resp := &struct {
+ Message string `json:"message"`
+ }{}
+ // Call the Put method
+ err := c.Put(ctx, http.MethodPut, "api/v1/endpoint", nil, nil, body, resp)
+ assert.NoError(t, err)
+}
+
+func TestClient_Post(t *testing.T) {
+ // Create a mock client
+ c := &client{
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, http.MethodPost, r.Method)
+ assert.Equal(t, "/POST/api/v1/endpoint", r.URL.String())
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"Success"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ body := map[string]string{
+ "Content-Type": "application/json",
+ }
+ resp := &struct {
+ Message string `json:"message"`
+ }{}
+ // Call the Post method
+ err := c.Post(ctx, http.MethodPost, "api/v1/endpoint", nil, nil, body, resp)
+
+ // Assertions
+ assert.NoError(t, err)
+}
+
+func TestClient_Delete(t *testing.T) {
+ // Create a mock client
+ c := &client{
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, http.MethodDelete, r.Method)
+ assert.Equal(t, "/DELETE/api/v1/endpoint", r.URL.String())
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"Success"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ resp := &struct {
+ Message string `json:"message"`
+ }{}
+ // Call the Delete method
+ err := c.Delete(ctx, http.MethodDelete, "api/v1/endpoint", nil, nil, resp)
+
+ // Assertions
+ assert.NoError(t, err)
+}
+
+func TestClient_Do(t *testing.T) {
+ // Create a mock client
+ c := &client{
+ http: http.DefaultClient,
+ }
+ ctx := context.Background()
+
+ // Create a mock server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Assert that the request is as expected
+ assert.Equal(t, "method", r.Method)
+ assert.Equal(t, "/api/v1/endpoint/", r.URL.String())
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"message":"Success"}`))
+ }))
+ defer server.Close()
+ c.hostname = server.URL
+ resp := &struct {
+ Message string `json:"message"`
+ }{}
+ // Call the Do method
+ err := c.Do(ctx, "method", "api/v1/endpoint", "", nil, resp, resp)
+
+ // Assertions
+ assert.NoError(t, err)
+}
diff --git a/api/common/utils/poll_test.go b/api/common/utils/poll_test.go
new file mode 100644
index 00000000..22608808
--- /dev/null
+++ b/api/common/utils/poll_test.go
@@ -0,0 +1,166 @@
+/*
+Copyright (c) 2025 Dell Inc, or its subsidiaries.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package utils
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPollImmediateWithContext_Success(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // Condition function that returns true immediately
+ condition := func(context.Context) (bool, error) {
+ return true, nil
+ }
+
+ err := PollImmediateWithContext(ctx, 1*time.Second, 3*time.Second, condition)
+ assert.NoError(t, err)
+}
+
+func TestPollImmediateWithContext_Timeout(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
+ defer cancel()
+
+ // Condition function that never returns true
+ condition := func(context.Context) (bool, error) {
+ return false, nil
+ }
+
+ err := PollImmediateWithContext(ctx, 1*time.Second, 2*time.Second, condition)
+ assert.Error(t, err)
+ assert.Equal(t, ErrWaitTimeout, err)
+}
+
+func TestWaitForWithContext_Success(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // Wait function that uses a short interval
+ wait := func(ctx context.Context) <-chan struct{} {
+ ch := make(chan struct{})
+ go func() {
+ ticker := time.NewTicker(500 * time.Millisecond)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ ch <- struct{}{}
+ case <-ctx.Done():
+ close(ch)
+ return
+ }
+ }
+ }()
+ return ch
+ }
+
+ // Condition function that returns true after a short delay
+ condition := func(context.Context) (bool, error) {
+ time.Sleep(1 * time.Second)
+ return true, nil
+ }
+
+ err := WaitForWithContext(ctx, wait, condition)
+ assert.NoError(t, err)
+}
+
+func TestWaitForWithContext_Timeout(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+ defer cancel()
+
+ // Wait function that uses a short interval
+ wait := func(ctx context.Context) <-chan struct{} {
+ ch := make(chan struct{})
+ go func() {
+ ticker := time.NewTicker(500 * time.Millisecond)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ ch <- struct{}{}
+ case <-ctx.Done():
+ close(ch)
+ return
+ }
+ }
+ }()
+ return ch
+ }
+
+ // Condition function that never returns true
+ condition := func(context.Context) (bool, error) {
+ return false, nil
+ }
+
+ err := WaitForWithContext(ctx, wait, condition)
+ assert.Error(t, err)
+ assert.Equal(t, ErrWaitTimeout, err)
+}
+
+func TestPollImmediateWithContext_Error(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // Condition function that returns an error
+ condition := func(context.Context) (bool, error) {
+ return false, errors.New("condition error")
+ }
+
+ err := PollImmediateWithContext(ctx, 1*time.Second, 3*time.Second, condition)
+ assert.Error(t, err)
+ assert.Equal(t, "condition error", err.Error())
+}
+
+func TestWaitForWithContext_Error(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ // Wait function that uses a short interval
+ wait := func(ctx context.Context) <-chan struct{} {
+ ch := make(chan struct{})
+ go func() {
+ ticker := time.NewTicker(500 * time.Millisecond)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ ch <- struct{}{}
+ case <-ctx.Done():
+ close(ch)
+ return
+ }
+ }
+ }()
+ return ch
+ }
+
+ // Condition function that returns an error
+ condition := func(context.Context) (bool, error) {
+ return false, errors.New("condition error")
+ }
+
+ err := WaitForWithContext(ctx, wait, condition)
+ assert.Error(t, err)
+ assert.Equal(t, "condition error", err.Error())
+}
diff --git a/api/common/utils/utils_test.go b/api/common/utils/utils_test.go
index 3f5b18e7..9f8fdeb4 100644
--- a/api/common/utils/utils_test.go
+++ b/api/common/utils/utils_test.go
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2022 Dell Inc, or its subsidiaries.
+Copyright (c) 2022-2025 Dell Inc, or its subsidiaries.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,16 +16,11 @@ limitations under the License.
package utils
import (
- "fmt"
"testing"
"github.com/stretchr/testify/assert"
)
-func TestMain(_ *testing.M) {
- fmt.Print("executing TestMain\n")
-}
-
func TestIsStringInSlice(t *testing.T) {
list := []string{"hello", "world", "jason"}
@@ -34,6 +29,14 @@ func TestIsStringInSlice(t *testing.T) {
assert.False(t, IsStringInSlice("harry", nil))
}
+func TestIsStringInSlices(t *testing.T) {
+ list := []string{"hello", "world", "jason"}
+
+ assert.True(t, IsStringInSlices("world", list))
+ assert.False(t, IsStringInSlices("mary", list))
+ assert.False(t, IsStringInSlices("harry", nil))
+}
+
func TestRemoveStringFromSlice(t *testing.T) {
list := []string{"hello", "world", "jason"}
diff --git a/api/json/json_decode.go b/api/json/json_decode.go
deleted file mode 100644
index 8278e06c..00000000
--- a/api/json/json_decode.go
+++ /dev/null
@@ -1,1213 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Represents JSON data structure using native Go types: booleans, floats,
-// strings, arrays, and maps.
-
-package json
-
-import (
- "bytes"
- "encoding"
- "encoding/base64"
- "errors"
- "fmt"
- "reflect"
- "runtime"
- "strconv"
- "unicode"
- "unicode/utf16"
- "unicode/utf8"
-)
-
-// Unmarshal parses the JSON-encoded data and stores the result
-// in the value pointed to by v.
-//
-// Unmarshal uses the inverse of the encodings that
-// Marshal uses, allocating maps, slices, and pointers as necessary,
-// with the following additional rules:
-//
-// To unmarshal JSON into a pointer, Unmarshal first handles the case of
-// the JSON being the JSON literal null. In that case, Unmarshal sets
-// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
-// the value pointed at by the pointer. If the pointer is nil, Unmarshal
-// allocates a new value for it to point to.
-//
-// To unmarshal JSON into a struct, Unmarshal matches incoming object
-// keys to the keys used by Marshal (either the struct field name or its tag),
-// preferring an exact match but also accepting a case-insensitive match.
-// Unmarshal will only set exported fields of the struct.
-//
-// To unmarshal JSON into an interface value,
-// Unmarshal stores one of these in the interface value:
-//
-// bool, for JSON booleans
-// float64, for JSON numbers
-// string, for JSON strings
-// []interface{}, for JSON arrays
-// map[string]interface{}, for JSON objects
-// nil for JSON null
-//
-// To unmarshal a JSON array into a slice, Unmarshal resets the slice length
-// to zero and then appends each element to the slice.
-// As a special case, to unmarshal an empty JSON array into a slice,
-// Unmarshal replaces the slice with a new empty slice.
-//
-// To unmarshal a JSON array into a Go array, Unmarshal decodes
-// JSON array elements into corresponding Go array elements.
-// If the Go array is smaller than the JSON array,
-// the additional JSON array elements are discarded.
-// If the JSON array is smaller than the Go array,
-// the additional Go array elements are set to zero values.
-//
-// To unmarshal a JSON object into a map, Unmarshal first establishes a map to
-// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal
-// reuses the existing map, keeping existing entries. Unmarshal then stores key-
-// value pairs from the JSON object into the map. The map's key type must
-// either be a string, an integer, or implement encoding.TextUnmarshaler.
-//
-// If a JSON value is not appropriate for a given target type,
-// or if a JSON number overflows the target type, Unmarshal
-// skips that field and completes the unmarshaling as best it can.
-// If no more serious errors are encountered, Unmarshal returns
-// an UnmarshalTypeError describing the earliest such error.
-//
-// The JSON null value unmarshals into an interface, map, pointer, or slice
-// by setting that Go value to nil. Because null is often used in JSON to mean
-// “not present,” unmarshaling a JSON null into any other Go type has no effect
-// on the value and produces no error.
-//
-// When unmarshaling quoted strings, invalid UTF-8 or
-// invalid UTF-16 surrogate pairs are not treated as an error.
-// Instead, they are replaced by the Unicode replacement
-// character U+FFFD.
-func Unmarshal(data []byte, v interface{}) error {
- // Check for well-formedness.
- // Avoids filling out half a data structure
- // before discovering a JSON syntax error.
- var d decodeState
- err := checkValid(data, &d.scan)
- if err != nil {
- return err
- }
-
- d.init(data)
- return d.unmarshal(v)
-}
-
-// Unmarshaler is the interface implemented by types
-// that can unmarshal a JSON description of themselves.
-// The input can be assumed to be a valid encoding of
-// a JSON value. UnmarshalJSON must copy the JSON data
-// if it wishes to retain the data after returning.
-type Unmarshaler interface {
- UnmarshalJSON([]byte) error
-}
-
-// An UnmarshalTypeError describes a JSON value that was
-// not appropriate for a value of a specific Go type.
-type UnmarshalTypeError struct {
- Value string // description of JSON value - "bool", "array", "number -5"
- Type reflect.Type // type of Go value it could not be assigned to
- Offset int64 // error occurred after reading Offset bytes
-}
-
-func (e *UnmarshalTypeError) Error() string {
- return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
-}
-
-// An UnmarshalFieldError describes a JSON object key that
-// led to an unexported (and therefore unwritable) struct field.
-// (No longer used; kept for compatibility.)
-type UnmarshalFieldError struct {
- Key string
- Type reflect.Type
- Field reflect.StructField
-}
-
-func (e *UnmarshalFieldError) Error() string {
- return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String()
-}
-
-// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
-// (The argument to Unmarshal must be a non-nil pointer.)
-type InvalidUnmarshalError struct {
- Type reflect.Type
-}
-
-func (e *InvalidUnmarshalError) Error() string {
- if e.Type == nil {
- return "json: Unmarshal(nil)"
- }
-
- if e.Type.Kind() != reflect.Ptr {
- return "json: Unmarshal(non-pointer " + e.Type.String() + ")"
- }
- return "json: Unmarshal(nil " + e.Type.String() + ")"
-}
-
-func (d *decodeState) unmarshal(v interface{}) (err error) {
- defer func() {
- if r := recover(); r != nil {
- if _, ok := r.(runtime.Error); ok {
- panic(r)
- }
- err = r.(error)
- }
- }()
-
- rv := reflect.ValueOf(v)
- if rv.Kind() != reflect.Ptr || rv.IsNil() {
- return &InvalidUnmarshalError{reflect.TypeOf(v)}
- }
-
- d.scan.reset()
- // We decode rv not rv.Elem because the Unmarshaler interface
- // test must be applied at the top level of the value.
- d.value(rv)
- return d.savedError
-}
-
-// A Number represents a JSON number literal.
-type Number string
-
-// String returns the literal text of the number.
-func (n Number) String() string { return string(n) }
-
-// Float64 returns the number as a float64.
-func (n Number) Float64() (float64, error) {
- return strconv.ParseFloat(string(n), 64)
-}
-
-// Int64 returns the number as an int64.
-func (n Number) Int64() (int64, error) {
- return strconv.ParseInt(string(n), 10, 64)
-}
-
-// isValidNumber reports whether s is a valid JSON number literal.
-func isValidNumber(s string) bool {
- // This function implements the JSON numbers grammar.
- // See https://tools.ietf.org/html/rfc7159#section-6
- // and http://json.org/number.gif
-
- if s == "" {
- return false
- }
-
- // Optional -
- if s[0] == '-' {
- s = s[1:]
- if s == "" {
- return false
- }
- }
-
- // Digits
- switch {
- default:
- return false
-
- case s[0] == '0':
- s = s[1:]
-
- case '1' <= s[0] && s[0] <= '9':
- s = s[1:]
- for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
- s = s[1:]
- }
- }
-
- // . followed by 1 or more digits.
- if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' {
- s = s[2:]
- for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
- s = s[1:]
- }
- }
-
- // e or E followed by an optional - or + and
- // 1 or more digits.
- if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') {
- s = s[1:]
- if s[0] == '+' || s[0] == '-' {
- s = s[1:]
- if s == "" {
- return false
- }
- }
- for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
- s = s[1:]
- }
- }
-
- // Make sure we are at the end.
- return s == ""
-}
-
-// decodeState represents the state while decoding a JSON value.
-type decodeState struct {
- data []byte
- off int // read offset in data
- scan scanner
- nextscan scanner // for calls to nextValue
- savedError error
- useNumber bool
-}
-
-// errPhase is used for errors that should not happen unless
-// there is a bug in the JSON decoder or something is editing
-// the data slice while the decoder executes.
-var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?")
-
-func (d *decodeState) init(data []byte) *decodeState {
- d.data = data
- d.off = 0
- d.savedError = nil
- return d
-}
-
-// error aborts the decoding by panicking with err.
-func (d *decodeState) error(err error) {
- panic(err)
-}
-
-// saveError saves the first err it is called with,
-// for reporting at the end of the unmarshal.
-func (d *decodeState) saveError(err error) {
- if d.savedError == nil {
- d.savedError = err
- }
-}
-
-// next cuts off and returns the next full JSON value in d.data[d.off:].
-// The next value is known to be an object or array, not a literal.
-func (d *decodeState) next() []byte {
- c := d.data[d.off]
- item, rest, err := nextValue(d.data[d.off:], &d.nextscan)
- if err != nil {
- d.error(err)
- }
- d.off = len(d.data) - len(rest)
-
- // Our scanner has seen the opening brace/bracket
- // and thinks we're still in the middle of the object.
- // invent a closing brace/bracket to get it out.
- if c == '{' {
- d.scan.step(&d.scan, '}')
- } else {
- d.scan.step(&d.scan, ']')
- }
-
- return item
-}
-
-// scanWhile processes bytes in d.data[d.off:] until it
-// receives a scan code not equal to op.
-// It updates d.off and returns the new scan code.
-func (d *decodeState) scanWhile(op int) int {
- var newOp int
- for {
- if d.off >= len(d.data) {
- newOp = d.scan.eof()
- d.off = len(d.data) + 1 // mark processed EOF with len+1
- } else {
- c := d.data[d.off]
- d.off++
- newOp = d.scan.step(&d.scan, c)
- }
- if newOp != op {
- break
- }
- }
- return newOp
-}
-
-// value decodes a JSON value from d.data[d.off:] into the value.
-// it updates d.off to point past the decoded value.
-func (d *decodeState) value(v reflect.Value) {
- if !v.IsValid() {
- _, rest, err := nextValue(d.data[d.off:], &d.nextscan)
- if err != nil {
- d.error(err)
- }
- d.off = len(d.data) - len(rest)
-
- // d.scan thinks we're still at the beginning of the item.
- // Feed in an empty string - the shortest, simplest value -
- // so that it knows we got to the end of the value.
- if d.scan.redo {
- // rewind.
- d.scan.redo = false
- d.scan.step = stateBeginValue
- }
- d.scan.step(&d.scan, '"')
- d.scan.step(&d.scan, '"')
-
- n := len(d.scan.parseState)
- if n > 0 && d.scan.parseState[n-1] == parseObjectKey {
- // d.scan thinks we just read an object key; finish the object
- d.scan.step(&d.scan, ':')
- d.scan.step(&d.scan, '"')
- d.scan.step(&d.scan, '"')
- d.scan.step(&d.scan, '}')
- }
-
- return
- }
-
- switch op := d.scanWhile(scanSkipSpace); op {
- default:
- d.error(errPhase)
-
- case scanBeginArray:
- d.array(v)
-
- case scanBeginObject:
- d.object(v)
-
- case scanBeginLiteral:
- d.literal(v)
- }
-}
-
-type unquotedValue struct{}
-
-// valueQuoted is like value but decodes a
-// quoted string literal or literal null into an interface value.
-// If it finds anything other than a quoted string literal or null,
-// valueQuoted returns unquotedValue{}.
-func (d *decodeState) valueQuoted() interface{} {
- switch op := d.scanWhile(scanSkipSpace); op {
- default:
- d.error(errPhase)
-
- case scanBeginArray:
- d.array(reflect.Value{})
-
- case scanBeginObject:
- d.object(reflect.Value{})
-
- case scanBeginLiteral:
- switch v := d.literalInterface().(type) {
- case nil, string:
- return v
- }
- }
- return unquotedValue{}
-}
-
-// indirect walks down v allocating pointers as needed,
-// until it gets to a non-pointer.
-// if it encounters an Unmarshaler, indirect stops and returns that.
-// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
-func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
- // If v is a named type and is addressable,
- // start with its address, so that if the type has pointer methods,
- // we find them.
- if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
- v = v.Addr()
- }
- for {
- // Load value from interface, but only if the result will be
- // usefully addressable.
- if v.Kind() == reflect.Interface && !v.IsNil() {
- e := v.Elem()
- if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
- v = e
- continue
- }
- }
-
- if v.Kind() != reflect.Ptr {
- break
- }
-
- if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
- break
- }
- if v.IsNil() {
- v.Set(reflect.New(v.Type().Elem()))
- }
- if v.Type().NumMethod() > 0 {
- if u, ok := v.Interface().(Unmarshaler); ok {
- return u, nil, reflect.Value{}
- }
- if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
- return nil, u, reflect.Value{}
- }
- }
- v = v.Elem()
- }
- return nil, nil, v
-}
-
-// array consumes an array from d.data[d.off-1:], decoding into the value v.
-// the first byte of the array ('[') has been read already.
-func (d *decodeState) array(v reflect.Value) {
- // Check for unmarshaler.
- u, ut, pv := d.indirect(v, false)
- if u != nil {
- d.off--
- err := u.UnmarshalJSON(d.next())
- if err != nil {
- d.error(err)
- }
- return
- }
- if ut != nil {
- d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)})
- d.off--
- d.next()
- return
- }
-
- v = pv
-
- // Check type of target.
- switch v.Kind() {
- case reflect.Interface:
- if v.NumMethod() == 0 {
- // Decoding into nil interface? Switch to non-reflect code.
- v.Set(reflect.ValueOf(d.arrayInterface()))
- return
- }
- // Otherwise it's invalid.
- fallthrough
- default:
- d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)})
- d.off--
- d.next()
- return
- case reflect.Array:
- case reflect.Slice:
- break
- }
-
- i := 0
- for {
- // Look ahead for ] - can only happen on first iteration.
- op := d.scanWhile(scanSkipSpace)
- if op == scanEndArray {
- break
- }
-
- // Back up so d.value can have the byte we just read.
- d.off--
- d.scan.undo(op)
-
- // Get element of array, growing if necessary.
- if v.Kind() == reflect.Slice {
- // Grow slice if necessary
- if i >= v.Cap() {
- newcap := v.Cap() + v.Cap()/2
- if newcap < 4 {
- newcap = 4
- }
- newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
- reflect.Copy(newv, v)
- v.Set(newv)
- }
- if i >= v.Len() {
- v.SetLen(i + 1)
- }
- }
-
- if i < v.Len() {
- // Decode into element.
- d.value(v.Index(i))
- } else {
- // Ran out of fixed array: skip.
- d.value(reflect.Value{})
- }
- i++
-
- // Next token must be , or ].
- op = d.scanWhile(scanSkipSpace)
- if op == scanEndArray {
- break
- }
- if op != scanArrayValue {
- d.error(errPhase)
- }
- }
-
- if i < v.Len() {
- if v.Kind() == reflect.Array {
- // Array. Zero the rest.
- z := reflect.Zero(v.Type().Elem())
- for ; i < v.Len(); i++ {
- v.Index(i).Set(z)
- }
- } else {
- v.SetLen(i)
- }
- }
- if i == 0 && v.Kind() == reflect.Slice {
- v.Set(reflect.MakeSlice(v.Type(), 0, 0))
- }
-}
-
-var (
- nullLiteral = []byte("null")
- textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
-)
-
-// object consumes an object from d.data[d.off-1:], decoding into the value v.
-// the first byte ('{') of the object has been read already.
-func (d *decodeState) object(v reflect.Value) {
- // Check for unmarshaler.
- u, ut, pv := d.indirect(v, false)
- if u != nil {
- d.off--
- err := u.UnmarshalJSON(d.next())
- if err != nil {
- d.error(err)
- }
- return
- }
- if ut != nil {
- d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
- d.off--
- d.next() // skip over { } in input
- return
- }
- v = pv
-
- // Decoding into nil interface? Switch to non-reflect code.
- if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
- v.Set(reflect.ValueOf(d.objectInterface()))
- return
- }
-
- // Check type of target:
- // struct or
- // map[T1]T2 where T1 is string, an integer type,
- // or an encoding.TextUnmarshaler
- switch v.Kind() {
- case reflect.Map:
- // Map key must either have string kind, have an integer kind,
- // or be an encoding.TextUnmarshaler.
- t := v.Type()
- switch t.Key().Kind() {
- case reflect.String,
- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- default:
- if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) {
- d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
- d.off--
- d.next() // skip over { } in input
- return
- }
- }
- if v.IsNil() {
- v.Set(reflect.MakeMap(t))
- }
- case reflect.Struct:
-
- default:
- d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)})
- d.off--
- d.next() // skip over { } in input
- return
- }
-
- var mapElem reflect.Value
-
- for {
- // Read opening " of string key or closing }.
- op := d.scanWhile(scanSkipSpace)
- if op == scanEndObject {
- // closing } - can only happen on first iteration.
- break
- }
- if op != scanBeginLiteral {
- d.error(errPhase)
- }
-
- // Read key.
- start := d.off - 1
- op = d.scanWhile(scanContinue)
- item := d.data[start : d.off-1]
- key, ok := unquoteBytes(item)
- if !ok {
- d.error(errPhase)
- }
-
- // Figure out field corresponding to key.
- var subv reflect.Value
- destring := false // whether the value is wrapped in a string to be decoded first
-
- if v.Kind() == reflect.Map {
- elemType := v.Type().Elem()
- if !mapElem.IsValid() {
- mapElem = reflect.New(elemType).Elem()
- } else {
- mapElem.Set(reflect.Zero(elemType))
- }
- subv = mapElem
- } else {
- var f *field
- fields := cachedTypeFields(v.Type())
- for i := range fields {
- if fields[i].omitUnmarshal {
- continue
- }
- ff := &fields[i]
- if bytes.Equal(ff.nameBytes, key) {
- f = ff
- break
- }
- if f == nil && ff.equalFold(ff.nameBytes, key) {
- f = ff
- }
- }
- if f != nil {
- subv = v
- destring = f.quoted
- for _, i := range f.index {
- if subv.Kind() == reflect.Ptr {
- if subv.IsNil() {
- subv.Set(reflect.New(subv.Type().Elem()))
- }
- subv = subv.Elem()
- }
- subv = subv.Field(i)
- }
- }
- }
-
- // Read : before value.
- if op == scanSkipSpace {
- op = d.scanWhile(scanSkipSpace)
- }
- if op != scanObjectKey {
- d.error(errPhase)
- }
-
- // Read value.
- if destring {
- switch qv := d.valueQuoted().(type) {
- case nil:
- d.literalStore(nullLiteral, subv, false)
- case string:
- d.literalStore([]byte(qv), subv, true)
- default:
- d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type()))
- }
- } else {
- d.value(subv)
- }
-
- // Write value back to map;
- // if using struct, subv points into struct already.
- if v.Kind() == reflect.Map {
- kt := v.Type().Key()
- var kv reflect.Value
- switch {
- case kt.Kind() == reflect.String:
- kv = reflect.ValueOf(key).Convert(kt)
- case reflect.PtrTo(kt).Implements(textUnmarshalerType):
- kv = reflect.New(v.Type().Key())
- d.literalStore(item, kv, true)
- kv = kv.Elem()
- default:
- switch kt.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- s := string(key)
- n, err := strconv.ParseInt(s, 10, 64)
- if err != nil || reflect.Zero(kt).OverflowInt(n) {
- d.saveError(&UnmarshalTypeError{"number " + s, kt, int64(start + 1)})
- return
- }
- kv = reflect.ValueOf(n).Convert(kt)
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- s := string(key)
- n, err := strconv.ParseUint(s, 10, 64)
- if err != nil || reflect.Zero(kt).OverflowUint(n) {
- d.saveError(&UnmarshalTypeError{"number " + s, kt, int64(start + 1)})
- return
- }
- kv = reflect.ValueOf(n).Convert(kt)
- default:
- panic("json: Unexpected key type") // should never occur
- }
- }
- v.SetMapIndex(kv, subv)
- }
-
- // Next token must be , or }.
- op = d.scanWhile(scanSkipSpace)
- if op == scanEndObject {
- break
- }
- if op != scanObjectValue {
- d.error(errPhase)
- }
- }
-}
-
-// literal consumes a literal from d.data[d.off-1:], decoding into the value v.
-// The first byte of the literal has been read already
-// (that's how the caller knows it's a literal).
-func (d *decodeState) literal(v reflect.Value) {
- // All bytes inside literal return scanContinue op code.
- start := d.off - 1
- op := d.scanWhile(scanContinue)
-
- // Scan read one byte too far; back up.
- d.off--
- d.scan.undo(op)
-
- d.literalStore(d.data[start:d.off], v, false)
-}
-
-// convertNumber converts the number literal s to a float64 or a Number
-// depending on the setting of d.useNumber.
-func (d *decodeState) convertNumber(s string) (interface{}, error) {
- if d.useNumber {
- return Number(s), nil
- }
- f, err := strconv.ParseFloat(s, 64)
- if err != nil {
- return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
- }
- return f, nil
-}
-
-var numberType = reflect.TypeOf(Number(""))
-
-// literalStore decodes a literal stored in item into v.
-//
-// fromQuoted indicates whether this literal came from unwrapping a
-// string from the ",string" struct tag option. this is used only to
-// produce more helpful error messages.
-func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) {
- // Check for unmarshaler.
- if len(item) == 0 {
- // Empty string given
- d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- return
- }
- wantptr := item[0] == 'n' // null
- u, ut, pv := d.indirect(v, wantptr)
- if u != nil {
- err := u.UnmarshalJSON(item)
- if err != nil {
- d.error(err)
- }
- return
- }
- if ut != nil {
- if item[0] != '"' {
- if fromQuoted {
- d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
- }
- return
- }
- s, ok := unquoteBytes(item)
- if !ok {
- if fromQuoted {
- d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- d.error(errPhase)
- }
- }
- err := ut.UnmarshalText(s)
- if err != nil {
- d.error(err)
- }
- return
- }
-
- v = pv
-
- switch c := item[0]; c {
- case 'n': // null
- switch v.Kind() {
- case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
- v.Set(reflect.Zero(v.Type()))
- // otherwise, ignore null for primitives/string
- }
- case 't', 'f': // true, false
- value := c == 't'
- switch v.Kind() {
- default:
- if fromQuoted {
- d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)})
- }
- case reflect.Bool:
- v.SetBool(value)
- case reflect.Interface:
- if v.NumMethod() == 0 {
- v.Set(reflect.ValueOf(value))
- } else {
- d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)})
- }
- }
-
- case '"': // string
- s, ok := unquoteBytes(item)
- if !ok {
- if fromQuoted {
- d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- d.error(errPhase)
- }
- }
- switch v.Kind() {
- default:
- d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
- case reflect.Slice:
- if v.Type().Elem().Kind() != reflect.Uint8 {
- d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
- break
- }
- b := make([]byte, base64.StdEncoding.DecodedLen(len(s)))
- n, err := base64.StdEncoding.Decode(b, s)
- if err != nil {
- d.saveError(err)
- break
- }
- v.SetBytes(b[:n])
- case reflect.String:
- v.SetString(string(s))
- case reflect.Interface:
- if v.NumMethod() == 0 {
- v.Set(reflect.ValueOf(string(s)))
- } else {
- d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)})
- }
- }
-
- default: // number
- if c != '-' && (c < '0' || c > '9') {
- if fromQuoted {
- d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- d.error(errPhase)
- }
- }
- s := string(item)
- switch v.Kind() {
- default:
- if v.Kind() == reflect.String && v.Type() == numberType {
- v.SetString(s)
- if !isValidNumber(s) {
- d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item))
- }
- break
- }
- if fromQuoted {
- d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
- } else {
- d.error(&UnmarshalTypeError{"number", v.Type(), int64(d.off)})
- }
- case reflect.Interface:
- n, err := d.convertNumber(s)
- if err != nil {
- d.saveError(err)
- break
- }
- if v.NumMethod() != 0 {
- d.saveError(&UnmarshalTypeError{"number", v.Type(), int64(d.off)})
- break
- }
- v.Set(reflect.ValueOf(n))
-
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- n, err := strconv.ParseInt(s, 10, 64)
- if err != nil || v.OverflowInt(n) {
- d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
- break
- }
- v.SetInt(n)
-
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- n, err := strconv.ParseUint(s, 10, 64)
- if err != nil || v.OverflowUint(n) {
- d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
- break
- }
- v.SetUint(n)
-
- case reflect.Float32, reflect.Float64:
- n, err := strconv.ParseFloat(s, v.Type().Bits())
- if err != nil || v.OverflowFloat(n) {
- d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)})
- break
- }
- v.SetFloat(n)
- }
- }
-}
-
-// The xxxInterface routines build up a value to be stored
-// in an empty interface. They are not strictly necessary,
-// but they avoid the weight of reflection in this common case.
-
-// valueInterface is like value but returns interface{}
-func (d *decodeState) valueInterface() interface{} {
- switch d.scanWhile(scanSkipSpace) {
- default:
- d.error(errPhase)
- panic("unreachable")
- case scanBeginArray:
- return d.arrayInterface()
- case scanBeginObject:
- return d.objectInterface()
- case scanBeginLiteral:
- return d.literalInterface()
- }
-}
-
-// arrayInterface is like array but returns []interface{}.
-func (d *decodeState) arrayInterface() []interface{} {
- v := make([]interface{}, 0)
- for {
- // Look ahead for ] - can only happen on first iteration.
- op := d.scanWhile(scanSkipSpace)
- if op == scanEndArray {
- break
- }
-
- // Back up so d.value can have the byte we just read.
- d.off--
- d.scan.undo(op)
-
- v = append(v, d.valueInterface())
-
- // Next token must be , or ].
- op = d.scanWhile(scanSkipSpace)
- if op == scanEndArray {
- break
- }
- if op != scanArrayValue {
- d.error(errPhase)
- }
- }
- return v
-}
-
-// objectInterface is like object but returns map[string]interface{}.
-func (d *decodeState) objectInterface() map[string]interface{} {
- m := make(map[string]interface{})
- for {
- // Read opening " of string key or closing }.
- op := d.scanWhile(scanSkipSpace)
- if op == scanEndObject {
- // closing } - can only happen on first iteration.
- break
- }
- if op != scanBeginLiteral {
- d.error(errPhase)
- }
-
- // Read string key.
- start := d.off - 1
- op = d.scanWhile(scanContinue)
- item := d.data[start : d.off-1]
- key, ok := unquote(item)
- if !ok {
- d.error(errPhase)
- }
-
- // Read : before value.
- if op == scanSkipSpace {
- op = d.scanWhile(scanSkipSpace)
- }
- if op != scanObjectKey {
- d.error(errPhase)
- }
-
- // Read value.
- m[key] = d.valueInterface()
-
- // Next token must be , or }.
- op = d.scanWhile(scanSkipSpace)
- if op == scanEndObject {
- break
- }
- if op != scanObjectValue {
- d.error(errPhase)
- }
- }
- return m
-}
-
-// literalInterface is like literal but returns an interface value.
-func (d *decodeState) literalInterface() interface{} {
- // All bytes inside literal return scanContinue op code.
- start := d.off - 1
- op := d.scanWhile(scanContinue)
-
- // Scan read one byte too far; back up.
- d.off--
- d.scan.undo(op)
- item := d.data[start:d.off]
-
- switch c := item[0]; c {
- case 'n': // null
- return nil
-
- case 't', 'f': // true, false
- return c == 't'
-
- case '"': // string
- s, ok := unquote(item)
- if !ok {
- d.error(errPhase)
- }
- return s
-
- default: // number
- if c != '-' && (c < '0' || c > '9') {
- d.error(errPhase)
- }
- n, err := d.convertNumber(string(item))
- if err != nil {
- d.saveError(err)
- }
- return n
- }
-}
-
-// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
-// or it returns -1.
-func getu4(s []byte) rune {
- if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
- return -1
- }
- r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
- if err != nil {
- return -1
- }
- return rune(r)
-}
-
-// unquote converts a quoted JSON string literal s into an actual string t.
-// The rules are different than for Go, so cannot use strconv.Unquote.
-func unquote(s []byte) (t string, ok bool) {
- s, ok = unquoteBytes(s)
- t = string(s)
- return
-}
-
-func unquoteBytes(s []byte) (t []byte, ok bool) {
- if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
- return
- }
- s = s[1 : len(s)-1]
-
- // Check for unusual characters. If there are none,
- // then no unquoting is needed, so return a slice of the
- // original bytes.
- r := 0
- for r < len(s) {
- c := s[r]
- if c == '\\' || c == '"' || c < ' ' {
- break
- }
- if c < utf8.RuneSelf {
- r++
- continue
- }
- rr, size := utf8.DecodeRune(s[r:])
- if rr == utf8.RuneError && size == 1 {
- break
- }
- r += size
- }
- if r == len(s) {
- return s, true
- }
-
- b := make([]byte, len(s)+2*utf8.UTFMax)
- w := copy(b, s[0:r])
- for r < len(s) {
- // Out of room? Can only happen if s is full of
- // malformed UTF-8 and we're replacing each
- // byte with RuneError.
- if w >= len(b)-2*utf8.UTFMax {
- nb := make([]byte, (len(b)+utf8.UTFMax)*2)
- copy(nb, b[0:w])
- b = nb
- }
- switch c := s[r]; {
- case c == '\\':
- r++
- if r >= len(s) {
- return
- }
- switch s[r] {
- default:
- return
- case '"', '\\', '/', '\'':
- b[w] = s[r]
- r++
- w++
- case 'b':
- b[w] = '\b'
- r++
- w++
- case 'f':
- b[w] = '\f'
- r++
- w++
- case 'n':
- b[w] = '\n'
- r++
- w++
- case 'r':
- b[w] = '\r'
- r++
- w++
- case 't':
- b[w] = '\t'
- r++
- w++
- case 'u':
- r--
- rr := getu4(s[r:])
- if rr < 0 {
- return
- }
- r += 6
- if utf16.IsSurrogate(rr) {
- rr1 := getu4(s[r:])
- if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
- // A valid pair; consume.
- r += 6
- w += utf8.EncodeRune(b[w:], dec)
- break
- }
- // Invalid surrogate; fall back to replacement rune.
- rr = unicode.ReplacementChar
- }
- w += utf8.EncodeRune(b[w:], rr)
- }
-
- // Quote, control characters are invalid.
- case c == '"', c < ' ':
- return
-
- // ASCII
- case c < utf8.RuneSelf:
- b[w] = c
- r++
- w++
-
- // Coerce to well-formed UTF-8.
- default:
- rr, size := utf8.DecodeRune(s[r:])
- r += size
- w += utf8.EncodeRune(b[w:], rr)
- }
- }
- return b[0:w], true
-}
diff --git a/api/json/json_encode.go b/api/json/json_encode.go
deleted file mode 100644
index d4b1b0a6..00000000
--- a/api/json/json_encode.go
+++ /dev/null
@@ -1,1271 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package json implements encoding and decoding of JSON as defined in
-// RFC 4627. The mapping between JSON and Go values is described
-// in the documentation for the Marshal and Unmarshal functions.
-//
-// See "JSON and Go" for an introduction to this package:
-// https://golang.org/doc/articles/json_and_go.html
-package json
-
-import (
- "bytes"
- "encoding"
- "encoding/base64"
- "fmt"
- "math"
- "reflect"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "unicode"
- "unicode/utf8"
-)
-
-// Marshal returns the JSON encoding of v.
-//
-// Marshal traverses the value v recursively.
-// If an encountered value implements the Marshaler interface
-// and is not a nil pointer, Marshal calls its MarshalJSON method
-// to produce JSON. If no MarshalJSON method is present but the
-// value implements encoding.TextMarshaler instead, Marshal calls
-// its MarshalText method.
-// The nil pointer exception is not strictly necessary
-// but mimics a similar, necessary exception in the behavior of
-// UnmarshalJSON.
-//
-// Otherwise, Marshal uses the following type-dependent default encodings:
-//
-// Boolean values encode as JSON booleans.
-//
-// Floating point, integer, and Number values encode as JSON numbers.
-//
-// String values encode as JSON strings coerced to valid UTF-8,
-// replacing invalid bytes with the Unicode replacement rune.
-// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e"
-// to keep some browsers from misinterpreting JSON output as HTML.
-// Ampersand "&" is also escaped to "\u0026" for the same reason.
-// This escaping can be disabled using an Encoder with DisableHTMLEscaping.
-//
-// Array and slice values encode as JSON arrays, except that
-// []byte encodes as a base64-encoded string, and a nil slice
-// encodes as the null JSON value.
-//
-// Struct values encode as JSON objects. Each exported struct field
-// becomes a member of the object unless
-// - the field's tag is "-", or
-// - the field is empty and its tag specifies the "omitempty" option.
-//
-// The empty values are false, 0, any
-// nil pointer or interface value, and any array, slice, map, or string of
-// length zero. The object's default key string is the struct field name
-// but can be specified in the struct field's tag value. The "json" key in
-// the struct field's tag value is the key name, followed by an optional comma
-// and options. Examples:
-//
-// // Field is ignored by this package.
-// Field int `json:"-"`
-//
-// // Field appears in JSON as key "myName".
-// Field int `json:"myName"`
-//
-// // Field appears in JSON as key "myName" and
-// // the field is omitted from the object if its value is empty,
-// // as defined above.
-// Field int `json:"myName,omitempty"`
-//
-// // Field appears in JSON as key "Field" (the default), but
-// // the field is skipped if empty.
-// // Note the leading comma.
-// Field int `json:",omitempty"`
-//
-// The "string" option signals that a field is stored as JSON inside a
-// JSON-encoded string. It applies only to fields of string, floating point,
-// integer, or boolean types. This extra level of encoding is sometimes used
-// when communicating with JavaScript programs:
-//
-// Int64String int64 `json:",string"`
-//
-// The key name will be used if it's a non-empty string consisting of
-// only Unicode letters, digits, and ASCII punctuation except quotation
-// marks, backslash, and comma.
-//
-// Anonymous struct fields are usually marshaled as if their inner exported fields
-// were fields in the outer struct, subject to the usual Go visibility rules amended
-// as described in the next paragraph.
-// An anonymous struct field with a name given in its JSON tag is treated as
-// having that name, rather than being anonymous.
-// An anonymous struct field of interface type is treated the same as having
-// that type as its name, rather than being anonymous.
-//
-// The Go visibility rules for struct fields are amended for JSON when
-// deciding which field to marshal or unmarshal. If there are
-// multiple fields at the same level, and that level is the least
-// nested (and would therefore be the nesting level selected by the
-// usual Go rules), the following extra rules apply:
-//
-// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered,
-// even if there are multiple untagged fields that would otherwise conflict.
-// 2) If there is exactly one field (tagged or not according to the first rule), that is selected.
-// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
-//
-// Handling of anonymous struct fields is new in Go 1.1.
-// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of
-// an anonymous struct field in both current and earlier versions, give the field
-// a JSON tag of "-".
-//
-// Map values encode as JSON objects. The map's key type must either be a
-// string, an integer type, or implement encoding.TextMarshaler. The map keys
-// are sorted and used as JSON object keys by applying the following rules,
-// subject to the UTF-8 coercion described for string values above:
-// - string keys are used directly
-// - encoding.TextMarshalers are marshaled
-// - integer keys are converted to strings
-//
-// Pointer values encode as the value pointed to.
-// A nil pointer encodes as the null JSON value.
-//
-// Interface values encode as the value contained in the interface.
-// A nil interface value encodes as the null JSON value.
-//
-// Channel, complex, and function values cannot be encoded in JSON.
-// Attempting to encode such a value causes Marshal to return
-// an UnsupportedTypeError.
-//
-// JSON cannot represent cyclic data structures and Marshal does not
-// handle them. Passing cyclic structures to Marshal will result in
-// an infinite recursion.
-func Marshal(v interface{}) ([]byte, error) {
- e := &encodeState{}
- err := e.marshal(v, encOpts{escapeHTML: true})
- if err != nil {
- return nil, err
- }
- return e.Bytes(), nil
-}
-
-// MarshalIndent is like Marshal but applies Indent to format the output.
-func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
- b, err := Marshal(v)
- if err != nil {
- return nil, err
- }
- var buf bytes.Buffer
- err = Indent(&buf, b, prefix, indent)
- if err != nil {
- return nil, err
- }
- return buf.Bytes(), nil
-}
-
-// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
-// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
-// so that the JSON will be safe to embed inside HTML