mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-24 08:20:45 +00:00
Merge pull request #196 from 1Password/vzt/remove-vendor
Remove vendor folder
This commit is contained in:
21
vendor/github.com/1Password/connect-sdk-go/LICENSE
generated
vendored
21
vendor/github.com/1Password/connect-sdk-go/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021 1Password
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
869
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
869
vendor/github.com/1Password/connect-sdk-go/connect/client.go
generated
vendored
@@ -1,869 +0,0 @@
|
|||||||
package connect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/opentracing/opentracing-go"
|
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
|
||||||
jaegerClientConfig "github.com/uber/jaeger-client-go/config"
|
|
||||||
"github.com/uber/jaeger-client-go/zipkin"
|
|
||||||
|
|
||||||
"github.com/1Password/connect-sdk-go/onepassword"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultUserAgent = "connect-sdk-go/%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
vaultUUIDError = fmt.Errorf("malformed vault uuid provided")
|
|
||||||
itemUUIDError = fmt.Errorf("malformed item uuid provided")
|
|
||||||
fileUUIDError = fmt.Errorf("malformed file uuid provided")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client Represents an available 1Password Connect API to connect to
|
|
||||||
type Client interface {
|
|
||||||
GetVaults() ([]onepassword.Vault, error)
|
|
||||||
GetVault(uuid string) (*onepassword.Vault, error)
|
|
||||||
GetVaultByUUID(uuid string) (*onepassword.Vault, error)
|
|
||||||
GetVaultByTitle(title string) (*onepassword.Vault, error)
|
|
||||||
GetVaultsByTitle(uuid string) ([]onepassword.Vault, error)
|
|
||||||
GetItems(vaultQuery string) ([]onepassword.Item, error)
|
|
||||||
GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error)
|
|
||||||
GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error)
|
|
||||||
GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error)
|
|
||||||
GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error)
|
|
||||||
CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
|
||||||
UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error)
|
|
||||||
DeleteItem(item *onepassword.Item, vaultQuery string) error
|
|
||||||
DeleteItemByID(itemUUID string, vaultQuery string) error
|
|
||||||
DeleteItemByTitle(title string, vaultQuery string) error
|
|
||||||
GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error)
|
|
||||||
GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error)
|
|
||||||
GetFileContent(file *onepassword.File) ([]byte, error)
|
|
||||||
DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error)
|
|
||||||
LoadStructFromItemByUUID(config interface{}, itemUUID string, vaultQuery string) error
|
|
||||||
LoadStructFromItemByTitle(config interface{}, itemTitle string, vaultQuery string) error
|
|
||||||
LoadStructFromItem(config interface{}, itemQuery string, vaultQuery string) error
|
|
||||||
LoadStruct(config interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpClient interface {
|
|
||||||
Do(req *http.Request) (*http.Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
envHostVariable = "OP_CONNECT_HOST"
|
|
||||||
envTokenVariable = "OP_CONNECT_TOKEN"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewClientFromEnvironment Returns a Secret Service client assuming that your
|
|
||||||
// jwt is set in the OP_TOKEN environment variable
|
|
||||||
func NewClientFromEnvironment() (Client, error) {
|
|
||||||
host, found := os.LookupEnv(envHostVariable)
|
|
||||||
if !found {
|
|
||||||
return nil, fmt.Errorf("There is no hostname available in the %q variable", envHostVariable)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, found := os.LookupEnv(envTokenVariable)
|
|
||||||
if !found {
|
|
||||||
return nil, fmt.Errorf("There is no token available in the %q variable", envTokenVariable)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewClient(host, token), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient Returns a Secret Service client for a given url and jwt
|
|
||||||
func NewClient(url string, token string) Client {
|
|
||||||
return NewClientWithUserAgent(url, token, fmt.Sprintf(defaultUserAgent, SDKVersion))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientWithUserAgent Returns a Secret Service client for a given url and jwt and identifies with userAgent
|
|
||||||
func NewClientWithUserAgent(url string, token string, userAgent string) Client {
|
|
||||||
if !opentracing.IsGlobalTracerRegistered() {
|
|
||||||
cfg := jaegerClientConfig.Configuration{}
|
|
||||||
zipkinPropagator := zipkin.NewZipkinB3HTTPHeaderPropagator()
|
|
||||||
cfg.InitGlobalTracer(
|
|
||||||
userAgent,
|
|
||||||
jaegerClientConfig.Injector(opentracing.HTTPHeaders, zipkinPropagator),
|
|
||||||
jaegerClientConfig.Extractor(opentracing.HTTPHeaders, zipkinPropagator),
|
|
||||||
jaegerClientConfig.ZipkinSharedRPCSpan(true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &restClient{
|
|
||||||
URL: url,
|
|
||||||
Token: token,
|
|
||||||
|
|
||||||
userAgent: userAgent,
|
|
||||||
tracer: opentracing.GlobalTracer(),
|
|
||||||
|
|
||||||
client: http.DefaultClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type restClient struct {
|
|
||||||
URL string
|
|
||||||
Token string
|
|
||||||
userAgent string
|
|
||||||
tracer opentracing.Tracer
|
|
||||||
client httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVaults Get a list of all available vaults
|
|
||||||
func (rs *restClient) GetVaults() ([]onepassword.Vault, error) {
|
|
||||||
span := rs.tracer.StartSpan("GetVaults")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
vaultURL := fmt.Sprintf("/v1/vaults")
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var vaults []onepassword.Vault
|
|
||||||
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return vaults, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVault Get a vault based on its name or ID
|
|
||||||
func (rs *restClient) GetVault(vaultQuery string) (*onepassword.Vault, error) {
|
|
||||||
span := rs.tracer.StartSpan("GetVault")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
if vaultQuery == "" {
|
|
||||||
return nil, fmt.Errorf("Please provide either the vault name or its ID.")
|
|
||||||
}
|
|
||||||
if !isValidUUID(vaultQuery) {
|
|
||||||
return rs.GetVaultByTitle(vaultQuery)
|
|
||||||
}
|
|
||||||
return rs.GetVaultByUUID(vaultQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) {
|
|
||||||
if !isValidUUID(uuid) {
|
|
||||||
return nil, vaultUUIDError
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetVaultByUUID")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
vaultURL := fmt.Sprintf("/v1/vaults/%s", uuid)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var vault onepassword.Vault
|
|
||||||
if err := parseResponse(response, http.StatusOK, &vault); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &vault, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetVaultByTitle(vaultName string) (*onepassword.Vault, error) {
|
|
||||||
span := rs.tracer.StartSpan("GetVaultByTitle")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
vaults, err := rs.GetVaultsByTitle(vaultName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vaults) != 1 {
|
|
||||||
return nil, fmt.Errorf("Found %d vaults with title %q", len(vaults), vaultName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &vaults[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) {
|
|
||||||
span := rs.tracer.StartSpan("GetVaultsByTitle")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults?filter=%s", filter)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var vaults []onepassword.Vault
|
|
||||||
if err := parseResponse(response, http.StatusOK, &vaults); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return vaults, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) getVaultUUID(vaultQuery string) (string, error) {
|
|
||||||
if vaultQuery == "" {
|
|
||||||
return "", fmt.Errorf("Please provide either the vault name or its ID.")
|
|
||||||
}
|
|
||||||
if isValidUUID(vaultQuery) {
|
|
||||||
return vaultQuery, nil
|
|
||||||
}
|
|
||||||
vault, err := rs.GetVaultByTitle(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return vault.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetItem Get a specific Item from the 1Password Connect API by either title or UUID
|
|
||||||
func (rs *restClient) GetItem(itemQuery string, vaultQuery string) (*onepassword.Item, error) {
|
|
||||||
span := rs.tracer.StartSpan("GetItem")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
if itemQuery == "" {
|
|
||||||
return nil, fmt.Errorf("Please provide either the item name or its ID.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if isValidUUID(itemQuery) {
|
|
||||||
item, err := rs.GetItemByUUID(itemQuery, vaultQuery)
|
|
||||||
if item != nil {
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rs.GetItemByTitle(itemQuery, vaultQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetItemByUUID Get a specific Item from the 1Password Connect API by its UUID
|
|
||||||
func (rs *restClient) GetItemByUUID(uuid string, vaultQuery string) (*onepassword.Item, error) {
|
|
||||||
if !isValidUUID(uuid) {
|
|
||||||
return nil, itemUUIDError
|
|
||||||
}
|
|
||||||
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetItemByUUID")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", vaultUUID, uuid)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var item onepassword.Item
|
|
||||||
if err := parseResponse(response, http.StatusOK, &item); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &item, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetItemByTitle(title string, vaultQuery string) (*onepassword.Item, error) {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetItemByTitle")
|
|
||||||
defer span.Finish()
|
|
||||||
items, err := rs.GetItemsByTitle(title, vaultUUID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(items) != 1 {
|
|
||||||
return nil, fmt.Errorf("Found %d item(s) in vault %q with title %q", len(items), vaultUUID, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &items[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetItemsByTitle(title string, vaultQuery string) ([]onepassword.Item, error) {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetItemsByTitle")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
filter := url.QueryEscape(fmt.Sprintf("title eq \"%s\"", title))
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items?filter=%s", vaultUUID, filter)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemSummaries []onepassword.Item
|
|
||||||
if err := parseResponse(response, http.StatusOK, &itemSummaries); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
items := make([]onepassword.Item, len(itemSummaries))
|
|
||||||
for i, itemSummary := range itemSummaries {
|
|
||||||
tempItem, err := rs.GetItem(itemSummary.ID, itemSummary.Vault.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items[i] = *tempItem
|
|
||||||
}
|
|
||||||
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetItems")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items", vaultUUID)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var items []onepassword.Item
|
|
||||||
if err := parseResponse(response, http.StatusOK, &items); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) getItemUUID(itemQuery, vaultQuery string) (string, error) {
|
|
||||||
if itemQuery == "" {
|
|
||||||
return "", fmt.Errorf("Please provide either the item name or its ID.")
|
|
||||||
}
|
|
||||||
if isValidUUID(itemQuery) {
|
|
||||||
return itemQuery, nil
|
|
||||||
}
|
|
||||||
item, err := rs.GetItemByTitle(itemQuery, vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return item.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateItem Create a new item in a specified vault
|
|
||||||
func (rs *restClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("CreateItem")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items", vaultUUID)
|
|
||||||
itemBody, err := json.Marshal(item)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request, err := rs.buildRequest(http.MethodPost, itemURL, bytes.NewBuffer(itemBody), span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var newItem onepassword.Item
|
|
||||||
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &newItem, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateItem Update a new item in a specified vault
|
|
||||||
func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) {
|
|
||||||
span := rs.tracer.StartSpan("UpdateItem")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", item.Vault.ID, item.ID)
|
|
||||||
itemBody, err := json.Marshal(item)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request, err := rs.buildRequest(http.MethodPut, itemURL, bytes.NewBuffer(itemBody), span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var newItem onepassword.Item
|
|
||||||
if err := parseResponse(response, http.StatusOK, &newItem); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &newItem, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteItem Delete a new item in a specified vault
|
|
||||||
func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error {
|
|
||||||
span := rs.tracer.StartSpan("DeleteItem")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", item.Vault.ID, item.ID)
|
|
||||||
request, err := rs.buildRequest(http.MethodDelete, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteItemByID Delete a new item in a specified vault, specifying the item's uuid
|
|
||||||
func (rs *restClient) DeleteItemByID(itemUUID string, vaultQuery string) error {
|
|
||||||
if !isValidUUID(itemUUID) {
|
|
||||||
return itemUUIDError
|
|
||||||
}
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("DeleteItemByID")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s", vaultUUID, itemUUID)
|
|
||||||
request, err := rs.buildRequest(http.MethodDelete, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parseResponse(response, http.StatusNoContent, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteItemByTitle Delete a new item in a specified vault, specifying the item's title
|
|
||||||
func (rs *restClient) DeleteItemByTitle(title string, vaultQuery string) error {
|
|
||||||
span := rs.tracer.StartSpan("DeleteItemByTitle")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
item, err := rs.GetItemByTitle(title, vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs.DeleteItem(item, item.Vault.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) GetFiles(itemQuery string, vaultQuery string) ([]onepassword.File, error) {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
itemUUID, err := rs.getItemUUID(itemQuery, vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetFiles")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
jsonURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files", vaultUUID, itemUUID)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, jsonURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var files []onepassword.File
|
|
||||||
if err := parseResponse(response, http.StatusOK, &files); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile Get a specific File in a specified item.
|
|
||||||
// This does not include the file contents. Call GetFileContent() to load the file's content.
|
|
||||||
func (rs *restClient) GetFile(uuid string, itemQuery string, vaultQuery string) (*onepassword.File, error) {
|
|
||||||
if !isValidUUID(uuid) {
|
|
||||||
return nil, fileUUIDError
|
|
||||||
}
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
itemUUID, err := rs.getItemUUID(itemQuery, vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
span := rs.tracer.StartSpan("GetFile")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s", vaultUUID, itemUUID, uuid)
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var file onepassword.File
|
|
||||||
if err := parseResponse(response, http.StatusOK, &file); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileContent retrieves the file's content.
|
|
||||||
// If the file's content have previously been fetched, those contents are returned without making another request.
|
|
||||||
func (rs *restClient) GetFileContent(file *onepassword.File) ([]byte, error) {
|
|
||||||
if content, err := file.Content(); err == nil {
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
response, err := rs.retrieveDocumentContent(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
content, err := readResponseBody(response, http.StatusOK)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
file.SetContent(content)
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) DownloadFile(file *onepassword.File, targetDirectory string, overwriteIfExists bool) (string, error) {
|
|
||||||
response, err := rs.retrieveDocumentContent(file)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(targetDirectory, filepath.Base(file.Name))
|
|
||||||
|
|
||||||
var osFile *os.File
|
|
||||||
|
|
||||||
if overwriteIfExists {
|
|
||||||
osFile, err = createFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = os.Stat(path)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
osFile, err = createFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "", fmt.Errorf("a file already exists under the %s path. In order to overwrite it, set `overwriteIfExists` to true", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer osFile.Close()
|
|
||||||
if _, err = io.Copy(osFile, response.Body); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) retrieveDocumentContent(file *onepassword.File) (*http.Response, error) {
|
|
||||||
span := rs.tracer.StartSpan("GetFileContent")
|
|
||||||
defer span.Finish()
|
|
||||||
|
|
||||||
request, err := rs.buildRequest(http.MethodGet, file.ContentPath, http.NoBody, span)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := rs.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFile(path string) (*os.File, error) {
|
|
||||||
osFile, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = os.Chmod(path, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return osFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *restClient) buildRequest(method string, path string, body io.Reader, span opentracing.Span) (*http.Request, error) {
|
|
||||||
url := fmt.Sprintf("%s%s", rs.URL, path)
|
|
||||||
|
|
||||||
request, err := http.NewRequest(method, url, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request.Header.Set("Content-Type", "application/json")
|
|
||||||
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rs.Token))
|
|
||||||
request.Header.Set("User-Agent", rs.userAgent)
|
|
||||||
|
|
||||||
ext.SpanKindRPCClient.Set(span)
|
|
||||||
ext.HTTPUrl.Set(span, path)
|
|
||||||
ext.HTTPMethod.Set(span, method)
|
|
||||||
|
|
||||||
rs.tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(request.Header))
|
|
||||||
|
|
||||||
return request, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadToStruct(item *parsedItem, config reflect.Value) error {
|
|
||||||
t := config.Type()
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
value := config.Field(i)
|
|
||||||
field := t.Field(i)
|
|
||||||
|
|
||||||
if !value.CanSet() {
|
|
||||||
return fmt.Errorf("cannot load config into private fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
item.fields = append(item.fields, &field)
|
|
||||||
item.values = append(item.values, &value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadStructFromItem Load configuration values based on struct tag from one 1P item.
|
|
||||||
// It accepts as parameters item title/UUID and vault title/UUID.
|
|
||||||
func (rs *restClient) LoadStructFromItem(i interface{}, itemQuery string, vaultQuery string) error {
|
|
||||||
if itemQuery == "" {
|
|
||||||
return fmt.Errorf("Please provide either the item name or its ID.")
|
|
||||||
}
|
|
||||||
if isValidUUID(itemQuery) {
|
|
||||||
return rs.LoadStructFromItemByUUID(i, itemQuery, vaultQuery)
|
|
||||||
}
|
|
||||||
return rs.LoadStructFromItemByTitle(i, itemQuery, vaultQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadStructFromItemByUUID Load configuration values based on struct tag from one 1P item.
|
|
||||||
func (rs *restClient) LoadStructFromItemByUUID(i interface{}, itemUUID string, vaultQuery string) error {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !isValidUUID(itemUUID) {
|
|
||||||
return itemUUIDError
|
|
||||||
}
|
|
||||||
config, err := checkStruct(i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
item := parsedItem{}
|
|
||||||
item.itemUUID = itemUUID
|
|
||||||
item.vaultUUID = vaultUUID
|
|
||||||
|
|
||||||
if err := loadToStruct(&item, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := setValuesForTag(rs, &item, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadStructFromItemByTitle Load configuration values based on struct tag from one 1P item
|
|
||||||
func (rs *restClient) LoadStructFromItemByTitle(i interface{}, itemTitle string, vaultQuery string) error {
|
|
||||||
vaultUUID, err := rs.getVaultUUID(vaultQuery)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := checkStruct(i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
item := parsedItem{}
|
|
||||||
item.itemTitle = itemTitle
|
|
||||||
item.vaultUUID = vaultUUID
|
|
||||||
|
|
||||||
if err := loadToStruct(&item, config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := setValuesForTag(rs, &item, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadStruct Load configuration values based on struct tag
|
|
||||||
func (rs *restClient) LoadStruct(i interface{}) error {
|
|
||||||
config, err := checkStruct(i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := config.Type()
|
|
||||||
|
|
||||||
// Multiple fields may be from a single item so we will collect them
|
|
||||||
items := map[string]parsedItem{}
|
|
||||||
|
|
||||||
// Fetch the Vault from the environment
|
|
||||||
vaultUUID, envVarFound := os.LookupEnv(envVaultVar)
|
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
value := config.Field(i)
|
|
||||||
field := t.Field(i)
|
|
||||||
tag := field.Tag.Get(itemTag)
|
|
||||||
|
|
||||||
if tag == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !value.CanSet() {
|
|
||||||
return fmt.Errorf("Cannot load config into private fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
itemVault, err := vaultUUIDForField(&field, vaultUUID, envVarFound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !isValidUUID(itemVault) {
|
|
||||||
return vaultUUIDError
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf("%s/%s", itemVault, tag)
|
|
||||||
parsed := items[key]
|
|
||||||
parsed.vaultUUID = itemVault
|
|
||||||
parsed.itemTitle = tag
|
|
||||||
parsed.fields = append(parsed.fields, &field)
|
|
||||||
parsed.values = append(parsed.values, &value)
|
|
||||||
items[key] = parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
if err := setValuesForTag(rs, &item, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseResponse(resp *http.Response, expectedStatusCode int, result interface{}) error {
|
|
||||||
body, err := readResponseBody(resp, expectedStatusCode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if result != nil {
|
|
||||||
if err := json.Unmarshal(body, result); err != nil {
|
|
||||||
return fmt.Errorf("decoding response: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readResponseBody(resp *http.Response, expectedStatusCode int) ([]byte, error) {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != expectedStatusCode {
|
|
||||||
var errResp onepassword.Error
|
|
||||||
if json.Valid(body) {
|
|
||||||
if err := json.Unmarshal(body, &errResp); err != nil {
|
|
||||||
return nil, fmt.Errorf("decoding error response: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errResp.StatusCode = resp.StatusCode
|
|
||||||
errResp.Message = http.StatusText(resp.StatusCode)
|
|
||||||
}
|
|
||||||
return nil, &errResp
|
|
||||||
}
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidUUID(u string) bool {
|
|
||||||
r := regexp.MustCompile("^[a-z0-9]{26}$")
|
|
||||||
return r.MatchString(u)
|
|
||||||
}
|
|
||||||
209
vendor/github.com/1Password/connect-sdk-go/connect/config_helper.go
generated
vendored
209
vendor/github.com/1Password/connect-sdk-go/connect/config_helper.go
generated
vendored
@@ -1,209 +0,0 @@
|
|||||||
package connect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Password/connect-sdk-go/onepassword"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
vaultTag = "opvault"
|
|
||||||
itemTag = "opitem"
|
|
||||||
sectionTag = "opsection"
|
|
||||||
fieldTag = "opfield"
|
|
||||||
urlTag = "opurl"
|
|
||||||
|
|
||||||
envVaultVar = "OP_VAULT"
|
|
||||||
)
|
|
||||||
|
|
||||||
type parsedItem struct {
|
|
||||||
vaultUUID string
|
|
||||||
itemUUID string
|
|
||||||
itemTitle string
|
|
||||||
fields []*reflect.StructField
|
|
||||||
values []*reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkStruct(i interface{}) (reflect.Value, error) {
|
|
||||||
configP := reflect.ValueOf(i)
|
|
||||||
if configP.Kind() != reflect.Ptr {
|
|
||||||
return reflect.Value{}, fmt.Errorf("you must pass a pointer to Config struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := configP.Elem()
|
|
||||||
if config.Kind() != reflect.Struct {
|
|
||||||
return reflect.Value{}, fmt.Errorf("config values can only be loaded into a struct")
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func vaultUUIDForField(field *reflect.StructField, vaultUUID string, envVaultFound bool) (string, error) {
|
|
||||||
// Check to see if a specific vault has been specified on the field
|
|
||||||
// If the env vault id has not been found and item doesn't have a vault
|
|
||||||
// return an error
|
|
||||||
if vaultUUIDTag := field.Tag.Get(vaultTag); vaultUUIDTag == "" {
|
|
||||||
if !envVaultFound {
|
|
||||||
return "", fmt.Errorf("There is no vault for %q field", field.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return vaultUUIDTag, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return vaultUUID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setValuesForTag(client Client, parsedItem *parsedItem, byTitle bool) error {
|
|
||||||
var item *onepassword.Item
|
|
||||||
var err error
|
|
||||||
if byTitle {
|
|
||||||
item, err = client.GetItemByTitle(parsedItem.itemTitle, parsedItem.vaultUUID)
|
|
||||||
} else {
|
|
||||||
item, err = client.GetItem(parsedItem.itemUUID, parsedItem.vaultUUID)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, field := range parsedItem.fields {
|
|
||||||
value := parsedItem.values[i]
|
|
||||||
|
|
||||||
if field.Type == reflect.TypeOf(onepassword.ItemURL{}) {
|
|
||||||
url := &onepassword.ItemURL{
|
|
||||||
Primary: urlPrimaryForName(field.Tag.Get(urlTag), item.URLs),
|
|
||||||
Label: urlLabelForName(field.Tag.Get(urlTag), item.URLs),
|
|
||||||
URL: urlURLForName(field.Tag.Get(urlTag), item.URLs),
|
|
||||||
}
|
|
||||||
value.Set(reflect.ValueOf(*url))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
path := fmt.Sprintf("%s.%s", field.Tag.Get(sectionTag), field.Tag.Get(fieldTag))
|
|
||||||
if path == "." {
|
|
||||||
if field.Type == reflect.TypeOf(onepassword.Item{}) {
|
|
||||||
value.Set(reflect.ValueOf(*item))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("There is no %q specified for %q", fieldTag, field.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(path, ".") {
|
|
||||||
if field.Type == reflect.TypeOf(onepassword.ItemSection{}) {
|
|
||||||
section := &onepassword.ItemSection{
|
|
||||||
ID: sectionIDForName(field.Tag.Get(sectionTag), item.Sections),
|
|
||||||
Label: sectionLabelForName(field.Tag.Get(sectionTag), item.Sections),
|
|
||||||
}
|
|
||||||
value.Set(reflect.ValueOf(*section))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionID := sectionIDForName(field.Tag.Get(sectionTag), item.Sections)
|
|
||||||
|
|
||||||
for _, f := range item.Fields {
|
|
||||||
fieldSectionID := ""
|
|
||||||
if f.Section != nil {
|
|
||||||
fieldSectionID = f.Section.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
if fieldSectionID == sectionID && f.Label == field.Tag.Get(fieldTag) {
|
|
||||||
if err := setValue(value, f.Value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setValue(value *reflect.Value, toSet string) error {
|
|
||||||
switch value.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
value.SetString(toSet)
|
|
||||||
case reflect.Int:
|
|
||||||
v, err := strconv.Atoi(toSet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value.SetInt(int64(v))
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unsupported type %q. Only string, int64, and onepassword.Item are supported", value.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sectionIDForName(name string, sections []*onepassword.ItemSection) string {
|
|
||||||
if sections == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range sections {
|
|
||||||
if name == strings.ToLower(s.Label) {
|
|
||||||
return s.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func sectionLabelForName(name string, sections []*onepassword.ItemSection) string {
|
|
||||||
if sections == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range sections {
|
|
||||||
if name == strings.ToLower(s.Label) {
|
|
||||||
return s.Label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlPrimaryForName(name string, itemURLs []onepassword.ItemURL) bool {
|
|
||||||
if itemURLs == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, url := range itemURLs {
|
|
||||||
if url.Label == strings.ToLower(name) {
|
|
||||||
return url.Primary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlLabelForName(name string, itemURLs []onepassword.ItemURL) string {
|
|
||||||
if itemURLs == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, url := range itemURLs {
|
|
||||||
if url.Label == strings.ToLower(name) {
|
|
||||||
return url.Label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlURLForName(name string, itemURLs []onepassword.ItemURL) string {
|
|
||||||
if itemURLs == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, url := range itemURLs {
|
|
||||||
if url.Label == strings.ToLower(name) {
|
|
||||||
return url.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
}
|
|
||||||
104
vendor/github.com/1Password/connect-sdk-go/connect/version.go
generated
vendored
104
vendor/github.com/1Password/connect-sdk-go/connect/version.go
generated
vendored
@@ -1,104 +0,0 @@
|
|||||||
package connect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SDKVersion is the latest Semantic Version of the library
|
|
||||||
// Do not rename this variable without changing the regex in the Makefile
|
|
||||||
const SDKVersion = "1.5.3"
|
|
||||||
|
|
||||||
const VersionHeaderKey = "1Password-Connect-Version"
|
|
||||||
|
|
||||||
// expectMinimumConnectVersion returns an error if the provided minimum version for Connect is lower than the version
|
|
||||||
// reported in the response from Connect.
|
|
||||||
func expectMinimumConnectVersion(resp *http.Response, minimumVersion version) error {
|
|
||||||
serverVersion, err := getServerVersion(resp)
|
|
||||||
if err != nil {
|
|
||||||
// Return gracefully if server version cannot be determined reliably
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !serverVersion.IsGreaterOrEqualThan(minimumVersion) {
|
|
||||||
return fmt.Errorf("need at least version %s of Connect for this function, detected version %s. Please update your Connect server", minimumVersion, serverVersion)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServerVersion(resp *http.Response) (serverVersion, error) {
|
|
||||||
versionHeader := resp.Header.Get(VersionHeaderKey)
|
|
||||||
if versionHeader == "" {
|
|
||||||
// The last version without the version header was v1.2.0
|
|
||||||
return serverVersion{
|
|
||||||
version: version{1, 2, 0},
|
|
||||||
orEarlier: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return parseServerVersion(versionHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
type version struct {
|
|
||||||
major int
|
|
||||||
minor int
|
|
||||||
patch int
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverVersion describes the version reported by the server.
|
|
||||||
type serverVersion struct {
|
|
||||||
version
|
|
||||||
// orEarlier is true if the version is derived from the lack of a version header from the server.
|
|
||||||
orEarlier bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v version) String() string {
|
|
||||||
return fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v serverVersion) String() string {
|
|
||||||
if v.orEarlier {
|
|
||||||
return v.version.String() + " (or earlier)"
|
|
||||||
}
|
|
||||||
return v.version.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsGreaterOrEqualThan returns true if the lefthand-side version is equal to or or a higher version than the provided
|
|
||||||
// minimum according to the semantic versioning rules.
|
|
||||||
func (v version) IsGreaterOrEqualThan(min version) bool {
|
|
||||||
if v.major != min.major {
|
|
||||||
// Different major version
|
|
||||||
return v.major > min.major
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.minor != min.minor {
|
|
||||||
// Same major, but different minor version
|
|
||||||
return v.minor > min.minor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same major and minor version
|
|
||||||
return v.patch >= min.patch
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseServerVersion(v string) (serverVersion, error) {
|
|
||||||
spl := strings.Split(v, ".")
|
|
||||||
if len(spl) != 3 {
|
|
||||||
return serverVersion{}, errors.New("wrong length")
|
|
||||||
}
|
|
||||||
var res [3]int
|
|
||||||
for i := range res {
|
|
||||||
tmp, err := strconv.Atoi(spl[i])
|
|
||||||
if err != nil {
|
|
||||||
return serverVersion{}, err
|
|
||||||
}
|
|
||||||
res[i] = tmp
|
|
||||||
}
|
|
||||||
return serverVersion{
|
|
||||||
version: version{
|
|
||||||
major: res[0],
|
|
||||||
minor: res[1],
|
|
||||||
patch: res[2],
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
21
vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go
generated
vendored
21
vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
package onepassword
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Error is an error returned by the Connect API.
|
|
||||||
type Error struct {
|
|
||||||
StatusCode int `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf("status %d: %s", e.StatusCode, e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Is(target error) bool {
|
|
||||||
t, ok := target.(*Error)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return t.Message == e.Message && t.StatusCode == e.StatusCode
|
|
||||||
}
|
|
||||||
49
vendor/github.com/1Password/connect-sdk-go/onepassword/files.go
generated
vendored
49
vendor/github.com/1Password/connect-sdk-go/onepassword/files.go
generated
vendored
@@ -1,49 +0,0 @@
|
|||||||
package onepassword
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Section *ItemSection `json:"section,omitempty"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
ContentPath string `json:"content_path"`
|
|
||||||
content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) UnmarshalJSON(data []byte) error {
|
|
||||||
var jsonFile struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Section *ItemSection `json:"section,omitempty"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
ContentPath string `json:"content_path"`
|
|
||||||
Content []byte `json:"content,omitempty"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &jsonFile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.ID = jsonFile.ID
|
|
||||||
f.Name = jsonFile.Name
|
|
||||||
f.Section = jsonFile.Section
|
|
||||||
f.Size = jsonFile.Size
|
|
||||||
f.ContentPath = jsonFile.ContentPath
|
|
||||||
f.content = jsonFile.Content
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content returns the content of the file if they have been loaded and returns an error if they have not been loaded.
|
|
||||||
// Use `client.GetFileContent(file *File)` instead to make sure the content is fetched automatically if not present.
|
|
||||||
func (f *File) Content() ([]byte, error) {
|
|
||||||
if f.content == nil {
|
|
||||||
return nil, errors.New("file content not loaded")
|
|
||||||
}
|
|
||||||
return f.content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) SetContent(content []byte) {
|
|
||||||
f.content = content
|
|
||||||
}
|
|
||||||
193
vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
generated
vendored
193
vendor/github.com/1Password/connect-sdk-go/onepassword/items.go
generated
vendored
@@ -1,193 +0,0 @@
|
|||||||
package onepassword
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ItemCategory Represents the template of the Item
|
|
||||||
type ItemCategory string
|
|
||||||
|
|
||||||
type ItemFieldPurpose string
|
|
||||||
|
|
||||||
type ItemFieldType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
Login ItemCategory = "LOGIN"
|
|
||||||
Password ItemCategory = "PASSWORD"
|
|
||||||
ApiCredential ItemCategory = "API_CREDENTIAL"
|
|
||||||
Server ItemCategory = "SERVER"
|
|
||||||
Database ItemCategory = "DATABASE"
|
|
||||||
CreditCard ItemCategory = "CREDIT_CARD"
|
|
||||||
Membership ItemCategory = "MEMBERSHIP"
|
|
||||||
Passport ItemCategory = "PASSPORT"
|
|
||||||
SoftwareLicense ItemCategory = "SOFTWARE_LICENSE"
|
|
||||||
OutdoorLicense ItemCategory = "OUTDOOR_LICENSE"
|
|
||||||
SecureNote ItemCategory = "SECURE_NOTE"
|
|
||||||
WirelessRouter ItemCategory = "WIRELESS_ROUTER"
|
|
||||||
BankAccount ItemCategory = "BANK_ACCOUNT"
|
|
||||||
DriverLicense ItemCategory = "DRIVER_LICENSE"
|
|
||||||
Identity ItemCategory = "IDENTITY"
|
|
||||||
RewardProgram ItemCategory = "REWARD_PROGRAM"
|
|
||||||
Document ItemCategory = "DOCUMENT"
|
|
||||||
EmailAccount ItemCategory = "EMAIL_ACCOUNT"
|
|
||||||
SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER"
|
|
||||||
MedicalRecord ItemCategory = "MEDICAL_RECORD"
|
|
||||||
SSHKey ItemCategory = "SSH_KEY"
|
|
||||||
Custom ItemCategory = "CUSTOM"
|
|
||||||
|
|
||||||
FieldPurposeUsername ItemFieldPurpose = "USERNAME"
|
|
||||||
FieldPurposePassword ItemFieldPurpose = "PASSWORD"
|
|
||||||
FieldPurposeNotes ItemFieldPurpose = "NOTES"
|
|
||||||
|
|
||||||
FieldTypeAddress ItemFieldType = "ADDRESS"
|
|
||||||
FieldTypeConcealed ItemFieldType = "CONCEALED"
|
|
||||||
FieldTypeCreditCardNumber ItemFieldType = "CREDIT_CARD_NUMBER"
|
|
||||||
FieldTypeCreditCardType ItemFieldType = "CREDIT_CARD_TYPE"
|
|
||||||
FieldTypeDate ItemFieldType = "DATE"
|
|
||||||
FieldTypeEmail ItemFieldType = "EMAIL"
|
|
||||||
FieldTypeGender ItemFieldType = "GENDER"
|
|
||||||
FieldTypeMenu ItemFieldType = "MENU"
|
|
||||||
FieldTypeMonthYear ItemFieldType = "MONTH_YEAR"
|
|
||||||
FieldTypeOTP ItemFieldType = "OTP"
|
|
||||||
FieldTypePhone ItemFieldType = "PHONE"
|
|
||||||
FieldTypeReference ItemFieldType = "REFERENCE"
|
|
||||||
FieldTypeString ItemFieldType = "STRING"
|
|
||||||
FieldTypeURL ItemFieldType = "URL"
|
|
||||||
FieldTypeFile ItemFieldType = "FILE"
|
|
||||||
FieldTypeSSHKey ItemFieldType = "SSH_KEY"
|
|
||||||
FieldTypeUnknown ItemFieldType = "UNKNOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnmarshalJSON Unmarshall Item Category enum strings to Go string enums
|
|
||||||
func (ic *ItemCategory) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
json.Unmarshal(b, &s)
|
|
||||||
category := ItemCategory(s)
|
|
||||||
switch category {
|
|
||||||
case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense,
|
|
||||||
OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram,
|
|
||||||
Document, EmailAccount, SocialSecurityNumber, ApiCredential, MedicalRecord, SSHKey:
|
|
||||||
*ic = category
|
|
||||||
default:
|
|
||||||
*ic = Custom
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Item represents an item returned to the consumer
|
|
||||||
type Item struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
|
|
||||||
URLs []ItemURL `json:"urls,omitempty"`
|
|
||||||
Favorite bool `json:"favorite,omitempty"`
|
|
||||||
Tags []string `json:"tags,omitempty"`
|
|
||||||
Version int `json:"version,omitempty"`
|
|
||||||
|
|
||||||
Vault ItemVault `json:"vault"`
|
|
||||||
Category ItemCategory `json:"category,omitempty"` // TODO: switch this to `category`
|
|
||||||
|
|
||||||
Sections []*ItemSection `json:"sections,omitempty"`
|
|
||||||
Fields []*ItemField `json:"fields,omitempty"`
|
|
||||||
Files []*File `json:"files,omitempty"`
|
|
||||||
|
|
||||||
LastEditedBy string `json:"lastEditedBy,omitempty"`
|
|
||||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
|
||||||
|
|
||||||
// Deprecated: Connect does not return trashed items.
|
|
||||||
Trashed bool `json:"trashed,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ItemVault represents the Vault the Item is found in
|
|
||||||
type ItemVault struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ItemURL is a simplified item URL
|
|
||||||
type ItemURL struct {
|
|
||||||
Primary bool `json:"primary,omitempty"`
|
|
||||||
Label string `json:"label,omitempty"`
|
|
||||||
URL string `json:"href"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ItemSection Representation of a Section on an item
|
|
||||||
type ItemSection struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
Label string `json:"label,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeneratorRecipe Representation of a "recipe" used to generate a field
|
|
||||||
type GeneratorRecipe struct {
|
|
||||||
Length int `json:"length,omitempty"`
|
|
||||||
CharacterSets []string `json:"characterSets,omitempty"`
|
|
||||||
ExcludeCharacters string `json:"excludeCharacters,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ItemField Representation of a single field on an Item
|
|
||||||
type ItemField struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Section *ItemSection `json:"section,omitempty"`
|
|
||||||
Type ItemFieldType `json:"type"`
|
|
||||||
Purpose ItemFieldPurpose `json:"purpose,omitempty"`
|
|
||||||
Label string `json:"label,omitempty"`
|
|
||||||
Value string `json:"value,omitempty"`
|
|
||||||
Generate bool `json:"generate,omitempty"`
|
|
||||||
Recipe *GeneratorRecipe `json:"recipe,omitempty"`
|
|
||||||
Entropy float64 `json:"entropy,omitempty"`
|
|
||||||
TOTP string `json:"totp,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue Retrieve the value of a field on the item by its label. To specify a
|
|
||||||
// field from a specific section pass in <section label>.<field label>. If
|
|
||||||
// no field matching the selector is found return "".
|
|
||||||
func (i *Item) GetValue(field string) string {
|
|
||||||
if i == nil || len(i.Fields) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionFilter := false
|
|
||||||
sectionLabel := ""
|
|
||||||
fieldLabel := field
|
|
||||||
if strings.Contains(field, ".") {
|
|
||||||
parts := strings.Split(field, ".")
|
|
||||||
|
|
||||||
// Test to make sure the . isn't the last character
|
|
||||||
if len(parts) == 2 {
|
|
||||||
sectionFilter = true
|
|
||||||
sectionLabel = parts[0]
|
|
||||||
fieldLabel = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range i.Fields {
|
|
||||||
if sectionFilter {
|
|
||||||
if f.Section != nil {
|
|
||||||
if sectionLabel != i.SectionLabelForID(f.Section.ID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fieldLabel == f.Label {
|
|
||||||
return f.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Item) SectionLabelForID(id string) string {
|
|
||||||
if i != nil || len(i.Sections) > 0 {
|
|
||||||
for _, s := range i.Sections {
|
|
||||||
if s.ID == id {
|
|
||||||
return s.Label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
46
vendor/github.com/1Password/connect-sdk-go/onepassword/vaults.go
generated
vendored
46
vendor/github.com/1Password/connect-sdk-go/onepassword/vaults.go
generated
vendored
@@ -1,46 +0,0 @@
|
|||||||
package onepassword
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vault represents a 1password Vault
|
|
||||||
type Vault struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
|
|
||||||
AttrVersion int `json:"attributeVersion,omitempty"`
|
|
||||||
ContentVersion int `json:"contentVersion,omitempty"`
|
|
||||||
Items int `json:"items,omitempty"`
|
|
||||||
Type VaultType `json:"type,omitempty"`
|
|
||||||
|
|
||||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// VaultType Representation of what the Vault Type is
|
|
||||||
type VaultType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
PersonalVault VaultType = "PERSONAL"
|
|
||||||
EveryoneVault VaultType = "EVERYONE"
|
|
||||||
TransferVault VaultType = "TRANSFER"
|
|
||||||
UserCreatedVault VaultType = "USER_CREATED"
|
|
||||||
UnknownVault VaultType = "UNKNOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnmarshalJSON Unmarshall Vault Type enum strings to Go string enums
|
|
||||||
func (vt *VaultType) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
json.Unmarshal(b, &s)
|
|
||||||
vaultType := VaultType(s)
|
|
||||||
switch vaultType {
|
|
||||||
case PersonalVault, EveryoneVault, TransferVault, UserCreatedVault:
|
|
||||||
*vt = vaultType
|
|
||||||
default:
|
|
||||||
*vt = UnknownVault
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
@@ -1,20 +0,0 @@
|
|||||||
Copyright (C) 2013 Blake Mizerany
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
File diff suppressed because it is too large
Load Diff
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
@@ -1,316 +0,0 @@
|
|||||||
// Package quantile computes approximate quantiles over an unbounded data
|
|
||||||
// stream within low memory and CPU bounds.
|
|
||||||
//
|
|
||||||
// A small amount of accuracy is traded to achieve the above properties.
|
|
||||||
//
|
|
||||||
// Multiple streams can be merged before calling Query to generate a single set
|
|
||||||
// of results. This is meaningful when the streams represent the same type of
|
|
||||||
// data. See Merge and Samples.
|
|
||||||
//
|
|
||||||
// For more detailed information about the algorithm used, see:
|
|
||||||
//
|
|
||||||
// Effective Computation of Biased Quantiles over Data Streams
|
|
||||||
//
|
|
||||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
|
||||||
package quantile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sample holds an observed value and meta information for compression. JSON
|
|
||||||
// tags have been added for convenience.
|
|
||||||
type Sample struct {
|
|
||||||
Value float64 `json:",string"`
|
|
||||||
Width float64 `json:",string"`
|
|
||||||
Delta float64 `json:",string"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Samples represents a slice of samples. It implements sort.Interface.
|
|
||||||
type Samples []Sample
|
|
||||||
|
|
||||||
func (a Samples) Len() int { return len(a) }
|
|
||||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
|
||||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
type invariant func(s *stream, r float64) float64
|
|
||||||
|
|
||||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
|
||||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
|
||||||
// error guarantees can still be given even for the lower ranks of the data
|
|
||||||
// distribution.
|
|
||||||
//
|
|
||||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
|
||||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
|
||||||
//
|
|
||||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
|
||||||
// properties.
|
|
||||||
func NewLowBiased(epsilon float64) *Stream {
|
|
||||||
ƒ := func(s *stream, r float64) float64 {
|
|
||||||
return 2 * epsilon * r
|
|
||||||
}
|
|
||||||
return newStream(ƒ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
|
||||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
|
||||||
// error guarantees can still be given even for the higher ranks of the data
|
|
||||||
// distribution.
|
|
||||||
//
|
|
||||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
|
||||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
|
||||||
//
|
|
||||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
|
||||||
// properties.
|
|
||||||
func NewHighBiased(epsilon float64) *Stream {
|
|
||||||
ƒ := func(s *stream, r float64) float64 {
|
|
||||||
return 2 * epsilon * (s.n - r)
|
|
||||||
}
|
|
||||||
return newStream(ƒ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
|
||||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
|
||||||
// space and computation time. The targets map maps the desired quantiles to
|
|
||||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
|
||||||
// is guaranteed to be within (Quantile±Epsilon).
|
|
||||||
//
|
|
||||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
|
||||||
func NewTargeted(targetMap map[float64]float64) *Stream {
|
|
||||||
// Convert map to slice to avoid slow iterations on a map.
|
|
||||||
// ƒ is called on the hot path, so converting the map to a slice
|
|
||||||
// beforehand results in significant CPU savings.
|
|
||||||
targets := targetMapToSlice(targetMap)
|
|
||||||
|
|
||||||
ƒ := func(s *stream, r float64) float64 {
|
|
||||||
var m = math.MaxFloat64
|
|
||||||
var f float64
|
|
||||||
for _, t := range targets {
|
|
||||||
if t.quantile*s.n <= r {
|
|
||||||
f = (2 * t.epsilon * r) / t.quantile
|
|
||||||
} else {
|
|
||||||
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
|
|
||||||
}
|
|
||||||
if f < m {
|
|
||||||
m = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
return newStream(ƒ)
|
|
||||||
}
|
|
||||||
|
|
||||||
type target struct {
|
|
||||||
quantile float64
|
|
||||||
epsilon float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetMapToSlice(targetMap map[float64]float64) []target {
|
|
||||||
targets := make([]target, 0, len(targetMap))
|
|
||||||
|
|
||||||
for quantile, epsilon := range targetMap {
|
|
||||||
t := target{
|
|
||||||
quantile: quantile,
|
|
||||||
epsilon: epsilon,
|
|
||||||
}
|
|
||||||
targets = append(targets, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
|
||||||
// design. Take care when using across multiple goroutines.
|
|
||||||
type Stream struct {
|
|
||||||
*stream
|
|
||||||
b Samples
|
|
||||||
sorted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStream(ƒ invariant) *Stream {
|
|
||||||
x := &stream{ƒ: ƒ}
|
|
||||||
return &Stream{x, make(Samples, 0, 500), true}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert inserts v into the stream.
|
|
||||||
func (s *Stream) Insert(v float64) {
|
|
||||||
s.insert(Sample{Value: v, Width: 1})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) insert(sample Sample) {
|
|
||||||
s.b = append(s.b, sample)
|
|
||||||
s.sorted = false
|
|
||||||
if len(s.b) == cap(s.b) {
|
|
||||||
s.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query returns the computed qth percentiles value. If s was created with
|
|
||||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
|
||||||
// will return an unspecified result.
|
|
||||||
func (s *Stream) Query(q float64) float64 {
|
|
||||||
if !s.flushed() {
|
|
||||||
// Fast path when there hasn't been enough data for a flush;
|
|
||||||
// this also yields better accuracy for small sets of data.
|
|
||||||
l := len(s.b)
|
|
||||||
if l == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
i := int(math.Ceil(float64(l) * q))
|
|
||||||
if i > 0 {
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
s.maybeSort()
|
|
||||||
return s.b[i].Value
|
|
||||||
}
|
|
||||||
s.flush()
|
|
||||||
return s.stream.query(q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merges samples into the underlying streams samples. This is handy when
|
|
||||||
// merging multiple streams from separate threads, database shards, etc.
|
|
||||||
//
|
|
||||||
// ATTENTION: This method is broken and does not yield correct results. The
|
|
||||||
// underlying algorithm is not capable of merging streams correctly.
|
|
||||||
func (s *Stream) Merge(samples Samples) {
|
|
||||||
sort.Sort(samples)
|
|
||||||
s.stream.merge(samples)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
|
||||||
func (s *Stream) Reset() {
|
|
||||||
s.stream.reset()
|
|
||||||
s.b = s.b[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Samples returns stream samples held by s.
|
|
||||||
func (s *Stream) Samples() Samples {
|
|
||||||
if !s.flushed() {
|
|
||||||
return s.b
|
|
||||||
}
|
|
||||||
s.flush()
|
|
||||||
return s.stream.samples()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns the total number of samples observed in the stream
|
|
||||||
// since initialization.
|
|
||||||
func (s *Stream) Count() int {
|
|
||||||
return len(s.b) + s.stream.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) flush() {
|
|
||||||
s.maybeSort()
|
|
||||||
s.stream.merge(s.b)
|
|
||||||
s.b = s.b[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) maybeSort() {
|
|
||||||
if !s.sorted {
|
|
||||||
s.sorted = true
|
|
||||||
sort.Sort(s.b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) flushed() bool {
|
|
||||||
return len(s.stream.l) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type stream struct {
|
|
||||||
n float64
|
|
||||||
l []Sample
|
|
||||||
ƒ invariant
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) reset() {
|
|
||||||
s.l = s.l[:0]
|
|
||||||
s.n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) insert(v float64) {
|
|
||||||
s.merge(Samples{{v, 1, 0}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) merge(samples Samples) {
|
|
||||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
|
||||||
// whole summaries. The paper doesn't mention merging summaries at
|
|
||||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
|
||||||
// do merges properly.
|
|
||||||
var r float64
|
|
||||||
i := 0
|
|
||||||
for _, sample := range samples {
|
|
||||||
for ; i < len(s.l); i++ {
|
|
||||||
c := s.l[i]
|
|
||||||
if c.Value > sample.Value {
|
|
||||||
// Insert at position i.
|
|
||||||
s.l = append(s.l, Sample{})
|
|
||||||
copy(s.l[i+1:], s.l[i:])
|
|
||||||
s.l[i] = Sample{
|
|
||||||
sample.Value,
|
|
||||||
sample.Width,
|
|
||||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
|
||||||
// TODO(beorn7): How to calculate delta correctly?
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
goto inserted
|
|
||||||
}
|
|
||||||
r += c.Width
|
|
||||||
}
|
|
||||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
|
||||||
i++
|
|
||||||
inserted:
|
|
||||||
s.n += sample.Width
|
|
||||||
r += sample.Width
|
|
||||||
}
|
|
||||||
s.compress()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) count() int {
|
|
||||||
return int(s.n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) query(q float64) float64 {
|
|
||||||
t := math.Ceil(q * s.n)
|
|
||||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
|
||||||
p := s.l[0]
|
|
||||||
var r float64
|
|
||||||
for _, c := range s.l[1:] {
|
|
||||||
r += p.Width
|
|
||||||
if r+c.Width+c.Delta > t {
|
|
||||||
return p.Value
|
|
||||||
}
|
|
||||||
p = c
|
|
||||||
}
|
|
||||||
return p.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) compress() {
|
|
||||||
if len(s.l) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x := s.l[len(s.l)-1]
|
|
||||||
xi := len(s.l) - 1
|
|
||||||
r := s.n - 1 - x.Width
|
|
||||||
|
|
||||||
for i := len(s.l) - 2; i >= 0; i-- {
|
|
||||||
c := s.l[i]
|
|
||||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
|
||||||
x.Width += c.Width
|
|
||||||
s.l[xi] = x
|
|
||||||
// Remove element at i.
|
|
||||||
copy(s.l[i:], s.l[i+1:])
|
|
||||||
s.l = s.l[:len(s.l)-1]
|
|
||||||
xi -= 1
|
|
||||||
} else {
|
|
||||||
x = c
|
|
||||||
xi = i
|
|
||||||
}
|
|
||||||
r -= c.Width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) samples() Samples {
|
|
||||||
samples := make(Samples, len(s.l))
|
|
||||||
copy(samples, s.l)
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2016 Caleb Spare
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
72
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
72
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
@@ -1,72 +0,0 @@
|
|||||||
# xxhash
|
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
|
||||||
[](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
|
||||||
|
|
||||||
xxhash is a Go implementation of the 64-bit [xxHash] algorithm, XXH64. This is a
|
|
||||||
high-quality hashing algorithm that is much faster than anything in the Go
|
|
||||||
standard library.
|
|
||||||
|
|
||||||
This package provides a straightforward API:
|
|
||||||
|
|
||||||
```
|
|
||||||
func Sum64(b []byte) uint64
|
|
||||||
func Sum64String(s string) uint64
|
|
||||||
type Digest struct{ ... }
|
|
||||||
func New() *Digest
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Digest` type implements hash.Hash64. Its key methods are:
|
|
||||||
|
|
||||||
```
|
|
||||||
func (*Digest) Write([]byte) (int, error)
|
|
||||||
func (*Digest) WriteString(string) (int, error)
|
|
||||||
func (*Digest) Sum64() uint64
|
|
||||||
```
|
|
||||||
|
|
||||||
The package is written with optimized pure Go and also contains even faster
|
|
||||||
assembly implementations for amd64 and arm64. If desired, the `purego` build tag
|
|
||||||
opts into using the Go code even on those architectures.
|
|
||||||
|
|
||||||
[xxHash]: http://cyan4973.github.io/xxHash/
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
This package is in a module and the latest code is in version 2 of the module.
|
|
||||||
You need a version of Go with at least "minimal module compatibility" to use
|
|
||||||
github.com/cespare/xxhash/v2:
|
|
||||||
|
|
||||||
* 1.9.7+ for Go 1.9
|
|
||||||
* 1.10.3+ for Go 1.10
|
|
||||||
* Go 1.11 or later
|
|
||||||
|
|
||||||
I recommend using the latest release of Go.
|
|
||||||
|
|
||||||
## Benchmarks
|
|
||||||
|
|
||||||
Here are some quick benchmarks comparing the pure-Go and assembly
|
|
||||||
implementations of Sum64.
|
|
||||||
|
|
||||||
| input size | purego | asm |
|
|
||||||
| ---------- | --------- | --------- |
|
|
||||||
| 4 B | 1.3 GB/s | 1.2 GB/s |
|
|
||||||
| 16 B | 2.9 GB/s | 3.5 GB/s |
|
|
||||||
| 100 B | 6.9 GB/s | 8.1 GB/s |
|
|
||||||
| 4 KB | 11.7 GB/s | 16.7 GB/s |
|
|
||||||
| 10 MB | 12.0 GB/s | 17.3 GB/s |
|
|
||||||
|
|
||||||
These numbers were generated on Ubuntu 20.04 with an Intel Xeon Platinum 8252C
|
|
||||||
CPU using the following commands under Go 1.19.2:
|
|
||||||
|
|
||||||
```
|
|
||||||
benchstat <(go test -tags purego -benchtime 500ms -count 15 -bench 'Sum64$')
|
|
||||||
benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
|
|
||||||
```
|
|
||||||
|
|
||||||
## Projects using this package
|
|
||||||
|
|
||||||
- [InfluxDB](https://github.com/influxdata/influxdb)
|
|
||||||
- [Prometheus](https://github.com/prometheus/prometheus)
|
|
||||||
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
|
||||||
- [FreeCache](https://github.com/coocood/freecache)
|
|
||||||
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
|
||||||
10
vendor/github.com/cespare/xxhash/v2/testall.sh
generated
vendored
10
vendor/github.com/cespare/xxhash/v2/testall.sh
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -eu -o pipefail
|
|
||||||
|
|
||||||
# Small convenience script for running the tests with various combinations of
|
|
||||||
# arch/tags. This assumes we're running on amd64 and have qemu available.
|
|
||||||
|
|
||||||
go test ./...
|
|
||||||
go test -tags purego ./...
|
|
||||||
GOARCH=arm64 go test
|
|
||||||
GOARCH=arm64 go test -tags purego
|
|
||||||
228
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
228
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
@@ -1,228 +0,0 @@
|
|||||||
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
|
||||||
// at http://cyan4973.github.io/xxHash/.
|
|
||||||
package xxhash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"math/bits"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
prime1 uint64 = 11400714785074694791
|
|
||||||
prime2 uint64 = 14029467366897019727
|
|
||||||
prime3 uint64 = 1609587929392839161
|
|
||||||
prime4 uint64 = 9650029242287828579
|
|
||||||
prime5 uint64 = 2870177450012600261
|
|
||||||
)
|
|
||||||
|
|
||||||
// Store the primes in an array as well.
|
|
||||||
//
|
|
||||||
// The consts are used when possible in Go code to avoid MOVs but we need a
|
|
||||||
// contiguous array of the assembly code.
|
|
||||||
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
|
|
||||||
|
|
||||||
// Digest implements hash.Hash64.
|
|
||||||
type Digest struct {
|
|
||||||
v1 uint64
|
|
||||||
v2 uint64
|
|
||||||
v3 uint64
|
|
||||||
v4 uint64
|
|
||||||
total uint64
|
|
||||||
mem [32]byte
|
|
||||||
n int // how much of mem is used
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
|
||||||
func New() *Digest {
|
|
||||||
var d Digest
|
|
||||||
d.Reset()
|
|
||||||
return &d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset clears the Digest's state so that it can be reused.
|
|
||||||
func (d *Digest) Reset() {
|
|
||||||
d.v1 = primes[0] + prime2
|
|
||||||
d.v2 = prime2
|
|
||||||
d.v3 = 0
|
|
||||||
d.v4 = -primes[0]
|
|
||||||
d.total = 0
|
|
||||||
d.n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size always returns 8 bytes.
|
|
||||||
func (d *Digest) Size() int { return 8 }
|
|
||||||
|
|
||||||
// BlockSize always returns 32 bytes.
|
|
||||||
func (d *Digest) BlockSize() int { return 32 }
|
|
||||||
|
|
||||||
// Write adds more data to d. It always returns len(b), nil.
|
|
||||||
func (d *Digest) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b)
|
|
||||||
d.total += uint64(n)
|
|
||||||
|
|
||||||
memleft := d.mem[d.n&(len(d.mem)-1):]
|
|
||||||
|
|
||||||
if d.n+n < 32 {
|
|
||||||
// This new data doesn't even fill the current block.
|
|
||||||
copy(memleft, b)
|
|
||||||
d.n += n
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.n > 0 {
|
|
||||||
// Finish off the partial block.
|
|
||||||
c := copy(memleft, b)
|
|
||||||
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
|
||||||
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
|
||||||
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
|
||||||
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
|
||||||
b = b[c:]
|
|
||||||
d.n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) >= 32 {
|
|
||||||
// One or more full blocks left.
|
|
||||||
nw := writeBlocks(d, b)
|
|
||||||
b = b[nw:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store any remaining partial block.
|
|
||||||
copy(d.mem[:], b)
|
|
||||||
d.n = len(b)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum appends the current hash to b and returns the resulting slice.
|
|
||||||
func (d *Digest) Sum(b []byte) []byte {
|
|
||||||
s := d.Sum64()
|
|
||||||
return append(
|
|
||||||
b,
|
|
||||||
byte(s>>56),
|
|
||||||
byte(s>>48),
|
|
||||||
byte(s>>40),
|
|
||||||
byte(s>>32),
|
|
||||||
byte(s>>24),
|
|
||||||
byte(s>>16),
|
|
||||||
byte(s>>8),
|
|
||||||
byte(s),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum64 returns the current hash.
|
|
||||||
func (d *Digest) Sum64() uint64 {
|
|
||||||
var h uint64
|
|
||||||
|
|
||||||
if d.total >= 32 {
|
|
||||||
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
|
||||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
|
||||||
h = mergeRound(h, v1)
|
|
||||||
h = mergeRound(h, v2)
|
|
||||||
h = mergeRound(h, v3)
|
|
||||||
h = mergeRound(h, v4)
|
|
||||||
} else {
|
|
||||||
h = d.v3 + prime5
|
|
||||||
}
|
|
||||||
|
|
||||||
h += d.total
|
|
||||||
|
|
||||||
b := d.mem[:d.n&(len(d.mem)-1)]
|
|
||||||
for ; len(b) >= 8; b = b[8:] {
|
|
||||||
k1 := round(0, u64(b[:8]))
|
|
||||||
h ^= k1
|
|
||||||
h = rol27(h)*prime1 + prime4
|
|
||||||
}
|
|
||||||
if len(b) >= 4 {
|
|
||||||
h ^= uint64(u32(b[:4])) * prime1
|
|
||||||
h = rol23(h)*prime2 + prime3
|
|
||||||
b = b[4:]
|
|
||||||
}
|
|
||||||
for ; len(b) > 0; b = b[1:] {
|
|
||||||
h ^= uint64(b[0]) * prime5
|
|
||||||
h = rol11(h) * prime1
|
|
||||||
}
|
|
||||||
|
|
||||||
h ^= h >> 33
|
|
||||||
h *= prime2
|
|
||||||
h ^= h >> 29
|
|
||||||
h *= prime3
|
|
||||||
h ^= h >> 32
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
magic = "xxh\x06"
|
|
||||||
marshaledSize = len(magic) + 8*5 + 32
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
|
||||||
func (d *Digest) MarshalBinary() ([]byte, error) {
|
|
||||||
b := make([]byte, 0, marshaledSize)
|
|
||||||
b = append(b, magic...)
|
|
||||||
b = appendUint64(b, d.v1)
|
|
||||||
b = appendUint64(b, d.v2)
|
|
||||||
b = appendUint64(b, d.v3)
|
|
||||||
b = appendUint64(b, d.v4)
|
|
||||||
b = appendUint64(b, d.total)
|
|
||||||
b = append(b, d.mem[:d.n]...)
|
|
||||||
b = b[:len(b)+len(d.mem)-d.n]
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
|
||||||
func (d *Digest) UnmarshalBinary(b []byte) error {
|
|
||||||
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
|
|
||||||
return errors.New("xxhash: invalid hash state identifier")
|
|
||||||
}
|
|
||||||
if len(b) != marshaledSize {
|
|
||||||
return errors.New("xxhash: invalid hash state size")
|
|
||||||
}
|
|
||||||
b = b[len(magic):]
|
|
||||||
b, d.v1 = consumeUint64(b)
|
|
||||||
b, d.v2 = consumeUint64(b)
|
|
||||||
b, d.v3 = consumeUint64(b)
|
|
||||||
b, d.v4 = consumeUint64(b)
|
|
||||||
b, d.total = consumeUint64(b)
|
|
||||||
copy(d.mem[:], b)
|
|
||||||
d.n = int(d.total % uint64(len(d.mem)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendUint64(b []byte, x uint64) []byte {
|
|
||||||
var a [8]byte
|
|
||||||
binary.LittleEndian.PutUint64(a[:], x)
|
|
||||||
return append(b, a[:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func consumeUint64(b []byte) ([]byte, uint64) {
|
|
||||||
x := u64(b)
|
|
||||||
return b[8:], x
|
|
||||||
}
|
|
||||||
|
|
||||||
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
|
||||||
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
|
||||||
|
|
||||||
func round(acc, input uint64) uint64 {
|
|
||||||
acc += input * prime2
|
|
||||||
acc = rol31(acc)
|
|
||||||
acc *= prime1
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeRound(acc, val uint64) uint64 {
|
|
||||||
val = round(0, val)
|
|
||||||
acc ^= val
|
|
||||||
acc = acc*prime1 + prime4
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
|
||||||
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
|
||||||
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
|
||||||
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
|
||||||
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
|
||||||
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
|
||||||
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
|
||||||
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
|
||||||
209
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
209
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
@@ -1,209 +0,0 @@
|
|||||||
//go:build !appengine && gc && !purego
|
|
||||||
// +build !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !purego
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
// Registers:
|
|
||||||
#define h AX
|
|
||||||
#define d AX
|
|
||||||
#define p SI // pointer to advance through b
|
|
||||||
#define n DX
|
|
||||||
#define end BX // loop end
|
|
||||||
#define v1 R8
|
|
||||||
#define v2 R9
|
|
||||||
#define v3 R10
|
|
||||||
#define v4 R11
|
|
||||||
#define x R12
|
|
||||||
#define prime1 R13
|
|
||||||
#define prime2 R14
|
|
||||||
#define prime4 DI
|
|
||||||
|
|
||||||
#define round(acc, x) \
|
|
||||||
IMULQ prime2, x \
|
|
||||||
ADDQ x, acc \
|
|
||||||
ROLQ $31, acc \
|
|
||||||
IMULQ prime1, acc
|
|
||||||
|
|
||||||
// round0 performs the operation x = round(0, x).
|
|
||||||
#define round0(x) \
|
|
||||||
IMULQ prime2, x \
|
|
||||||
ROLQ $31, x \
|
|
||||||
IMULQ prime1, x
|
|
||||||
|
|
||||||
// mergeRound applies a merge round on the two registers acc and x.
|
|
||||||
// It assumes that prime1, prime2, and prime4 have been loaded.
|
|
||||||
#define mergeRound(acc, x) \
|
|
||||||
round0(x) \
|
|
||||||
XORQ x, acc \
|
|
||||||
IMULQ prime1, acc \
|
|
||||||
ADDQ prime4, acc
|
|
||||||
|
|
||||||
// blockLoop processes as many 32-byte blocks as possible,
|
|
||||||
// updating v1, v2, v3, and v4. It assumes that there is at least one block
|
|
||||||
// to process.
|
|
||||||
#define blockLoop() \
|
|
||||||
loop: \
|
|
||||||
MOVQ +0(p), x \
|
|
||||||
round(v1, x) \
|
|
||||||
MOVQ +8(p), x \
|
|
||||||
round(v2, x) \
|
|
||||||
MOVQ +16(p), x \
|
|
||||||
round(v3, x) \
|
|
||||||
MOVQ +24(p), x \
|
|
||||||
round(v4, x) \
|
|
||||||
ADDQ $32, p \
|
|
||||||
CMPQ p, end \
|
|
||||||
JLE loop
|
|
||||||
|
|
||||||
// func Sum64(b []byte) uint64
|
|
||||||
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
|
|
||||||
// Load fixed primes.
|
|
||||||
MOVQ ·primes+0(SB), prime1
|
|
||||||
MOVQ ·primes+8(SB), prime2
|
|
||||||
MOVQ ·primes+24(SB), prime4
|
|
||||||
|
|
||||||
// Load slice.
|
|
||||||
MOVQ b_base+0(FP), p
|
|
||||||
MOVQ b_len+8(FP), n
|
|
||||||
LEAQ (p)(n*1), end
|
|
||||||
|
|
||||||
// The first loop limit will be len(b)-32.
|
|
||||||
SUBQ $32, end
|
|
||||||
|
|
||||||
// Check whether we have at least one block.
|
|
||||||
CMPQ n, $32
|
|
||||||
JLT noBlocks
|
|
||||||
|
|
||||||
// Set up initial state (v1, v2, v3, v4).
|
|
||||||
MOVQ prime1, v1
|
|
||||||
ADDQ prime2, v1
|
|
||||||
MOVQ prime2, v2
|
|
||||||
XORQ v3, v3
|
|
||||||
XORQ v4, v4
|
|
||||||
SUBQ prime1, v4
|
|
||||||
|
|
||||||
blockLoop()
|
|
||||||
|
|
||||||
MOVQ v1, h
|
|
||||||
ROLQ $1, h
|
|
||||||
MOVQ v2, x
|
|
||||||
ROLQ $7, x
|
|
||||||
ADDQ x, h
|
|
||||||
MOVQ v3, x
|
|
||||||
ROLQ $12, x
|
|
||||||
ADDQ x, h
|
|
||||||
MOVQ v4, x
|
|
||||||
ROLQ $18, x
|
|
||||||
ADDQ x, h
|
|
||||||
|
|
||||||
mergeRound(h, v1)
|
|
||||||
mergeRound(h, v2)
|
|
||||||
mergeRound(h, v3)
|
|
||||||
mergeRound(h, v4)
|
|
||||||
|
|
||||||
JMP afterBlocks
|
|
||||||
|
|
||||||
noBlocks:
|
|
||||||
MOVQ ·primes+32(SB), h
|
|
||||||
|
|
||||||
afterBlocks:
|
|
||||||
ADDQ n, h
|
|
||||||
|
|
||||||
ADDQ $24, end
|
|
||||||
CMPQ p, end
|
|
||||||
JG try4
|
|
||||||
|
|
||||||
loop8:
|
|
||||||
MOVQ (p), x
|
|
||||||
ADDQ $8, p
|
|
||||||
round0(x)
|
|
||||||
XORQ x, h
|
|
||||||
ROLQ $27, h
|
|
||||||
IMULQ prime1, h
|
|
||||||
ADDQ prime4, h
|
|
||||||
|
|
||||||
CMPQ p, end
|
|
||||||
JLE loop8
|
|
||||||
|
|
||||||
try4:
|
|
||||||
ADDQ $4, end
|
|
||||||
CMPQ p, end
|
|
||||||
JG try1
|
|
||||||
|
|
||||||
MOVL (p), x
|
|
||||||
ADDQ $4, p
|
|
||||||
IMULQ prime1, x
|
|
||||||
XORQ x, h
|
|
||||||
|
|
||||||
ROLQ $23, h
|
|
||||||
IMULQ prime2, h
|
|
||||||
ADDQ ·primes+16(SB), h
|
|
||||||
|
|
||||||
try1:
|
|
||||||
ADDQ $4, end
|
|
||||||
CMPQ p, end
|
|
||||||
JGE finalize
|
|
||||||
|
|
||||||
loop1:
|
|
||||||
MOVBQZX (p), x
|
|
||||||
ADDQ $1, p
|
|
||||||
IMULQ ·primes+32(SB), x
|
|
||||||
XORQ x, h
|
|
||||||
ROLQ $11, h
|
|
||||||
IMULQ prime1, h
|
|
||||||
|
|
||||||
CMPQ p, end
|
|
||||||
JL loop1
|
|
||||||
|
|
||||||
finalize:
|
|
||||||
MOVQ h, x
|
|
||||||
SHRQ $33, x
|
|
||||||
XORQ x, h
|
|
||||||
IMULQ prime2, h
|
|
||||||
MOVQ h, x
|
|
||||||
SHRQ $29, x
|
|
||||||
XORQ x, h
|
|
||||||
IMULQ ·primes+16(SB), h
|
|
||||||
MOVQ h, x
|
|
||||||
SHRQ $32, x
|
|
||||||
XORQ x, h
|
|
||||||
|
|
||||||
MOVQ h, ret+24(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
// func writeBlocks(d *Digest, b []byte) int
|
|
||||||
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
|
||||||
// Load fixed primes needed for round.
|
|
||||||
MOVQ ·primes+0(SB), prime1
|
|
||||||
MOVQ ·primes+8(SB), prime2
|
|
||||||
|
|
||||||
// Load slice.
|
|
||||||
MOVQ b_base+8(FP), p
|
|
||||||
MOVQ b_len+16(FP), n
|
|
||||||
LEAQ (p)(n*1), end
|
|
||||||
SUBQ $32, end
|
|
||||||
|
|
||||||
// Load vN from d.
|
|
||||||
MOVQ s+0(FP), d
|
|
||||||
MOVQ 0(d), v1
|
|
||||||
MOVQ 8(d), v2
|
|
||||||
MOVQ 16(d), v3
|
|
||||||
MOVQ 24(d), v4
|
|
||||||
|
|
||||||
// We don't need to check the loop condition here; this function is
|
|
||||||
// always called with at least one block of data to process.
|
|
||||||
blockLoop()
|
|
||||||
|
|
||||||
// Copy vN back to d.
|
|
||||||
MOVQ v1, 0(d)
|
|
||||||
MOVQ v2, 8(d)
|
|
||||||
MOVQ v3, 16(d)
|
|
||||||
MOVQ v4, 24(d)
|
|
||||||
|
|
||||||
// The number of bytes written is p minus the old base pointer.
|
|
||||||
SUBQ b_base+8(FP), p
|
|
||||||
MOVQ p, ret+32(FP)
|
|
||||||
|
|
||||||
RET
|
|
||||||
183
vendor/github.com/cespare/xxhash/v2/xxhash_arm64.s
generated
vendored
183
vendor/github.com/cespare/xxhash/v2/xxhash_arm64.s
generated
vendored
@@ -1,183 +0,0 @@
|
|||||||
//go:build !appengine && gc && !purego
|
|
||||||
// +build !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !purego
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
// Registers:
|
|
||||||
#define digest R1
|
|
||||||
#define h R2 // return value
|
|
||||||
#define p R3 // input pointer
|
|
||||||
#define n R4 // input length
|
|
||||||
#define nblocks R5 // n / 32
|
|
||||||
#define prime1 R7
|
|
||||||
#define prime2 R8
|
|
||||||
#define prime3 R9
|
|
||||||
#define prime4 R10
|
|
||||||
#define prime5 R11
|
|
||||||
#define v1 R12
|
|
||||||
#define v2 R13
|
|
||||||
#define v3 R14
|
|
||||||
#define v4 R15
|
|
||||||
#define x1 R20
|
|
||||||
#define x2 R21
|
|
||||||
#define x3 R22
|
|
||||||
#define x4 R23
|
|
||||||
|
|
||||||
#define round(acc, x) \
|
|
||||||
MADD prime2, acc, x, acc \
|
|
||||||
ROR $64-31, acc \
|
|
||||||
MUL prime1, acc
|
|
||||||
|
|
||||||
// round0 performs the operation x = round(0, x).
|
|
||||||
#define round0(x) \
|
|
||||||
MUL prime2, x \
|
|
||||||
ROR $64-31, x \
|
|
||||||
MUL prime1, x
|
|
||||||
|
|
||||||
#define mergeRound(acc, x) \
|
|
||||||
round0(x) \
|
|
||||||
EOR x, acc \
|
|
||||||
MADD acc, prime4, prime1, acc
|
|
||||||
|
|
||||||
// blockLoop processes as many 32-byte blocks as possible,
|
|
||||||
// updating v1, v2, v3, and v4. It assumes that n >= 32.
|
|
||||||
#define blockLoop() \
|
|
||||||
LSR $5, n, nblocks \
|
|
||||||
PCALIGN $16 \
|
|
||||||
loop: \
|
|
||||||
LDP.P 16(p), (x1, x2) \
|
|
||||||
LDP.P 16(p), (x3, x4) \
|
|
||||||
round(v1, x1) \
|
|
||||||
round(v2, x2) \
|
|
||||||
round(v3, x3) \
|
|
||||||
round(v4, x4) \
|
|
||||||
SUB $1, nblocks \
|
|
||||||
CBNZ nblocks, loop
|
|
||||||
|
|
||||||
// func Sum64(b []byte) uint64
|
|
||||||
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
|
|
||||||
LDP b_base+0(FP), (p, n)
|
|
||||||
|
|
||||||
LDP ·primes+0(SB), (prime1, prime2)
|
|
||||||
LDP ·primes+16(SB), (prime3, prime4)
|
|
||||||
MOVD ·primes+32(SB), prime5
|
|
||||||
|
|
||||||
CMP $32, n
|
|
||||||
CSEL LT, prime5, ZR, h // if n < 32 { h = prime5 } else { h = 0 }
|
|
||||||
BLT afterLoop
|
|
||||||
|
|
||||||
ADD prime1, prime2, v1
|
|
||||||
MOVD prime2, v2
|
|
||||||
MOVD $0, v3
|
|
||||||
NEG prime1, v4
|
|
||||||
|
|
||||||
blockLoop()
|
|
||||||
|
|
||||||
ROR $64-1, v1, x1
|
|
||||||
ROR $64-7, v2, x2
|
|
||||||
ADD x1, x2
|
|
||||||
ROR $64-12, v3, x3
|
|
||||||
ROR $64-18, v4, x4
|
|
||||||
ADD x3, x4
|
|
||||||
ADD x2, x4, h
|
|
||||||
|
|
||||||
mergeRound(h, v1)
|
|
||||||
mergeRound(h, v2)
|
|
||||||
mergeRound(h, v3)
|
|
||||||
mergeRound(h, v4)
|
|
||||||
|
|
||||||
afterLoop:
|
|
||||||
ADD n, h
|
|
||||||
|
|
||||||
TBZ $4, n, try8
|
|
||||||
LDP.P 16(p), (x1, x2)
|
|
||||||
|
|
||||||
round0(x1)
|
|
||||||
|
|
||||||
// NOTE: here and below, sequencing the EOR after the ROR (using a
|
|
||||||
// rotated register) is worth a small but measurable speedup for small
|
|
||||||
// inputs.
|
|
||||||
ROR $64-27, h
|
|
||||||
EOR x1 @> 64-27, h, h
|
|
||||||
MADD h, prime4, prime1, h
|
|
||||||
|
|
||||||
round0(x2)
|
|
||||||
ROR $64-27, h
|
|
||||||
EOR x2 @> 64-27, h, h
|
|
||||||
MADD h, prime4, prime1, h
|
|
||||||
|
|
||||||
try8:
|
|
||||||
TBZ $3, n, try4
|
|
||||||
MOVD.P 8(p), x1
|
|
||||||
|
|
||||||
round0(x1)
|
|
||||||
ROR $64-27, h
|
|
||||||
EOR x1 @> 64-27, h, h
|
|
||||||
MADD h, prime4, prime1, h
|
|
||||||
|
|
||||||
try4:
|
|
||||||
TBZ $2, n, try2
|
|
||||||
MOVWU.P 4(p), x2
|
|
||||||
|
|
||||||
MUL prime1, x2
|
|
||||||
ROR $64-23, h
|
|
||||||
EOR x2 @> 64-23, h, h
|
|
||||||
MADD h, prime3, prime2, h
|
|
||||||
|
|
||||||
try2:
|
|
||||||
TBZ $1, n, try1
|
|
||||||
MOVHU.P 2(p), x3
|
|
||||||
AND $255, x3, x1
|
|
||||||
LSR $8, x3, x2
|
|
||||||
|
|
||||||
MUL prime5, x1
|
|
||||||
ROR $64-11, h
|
|
||||||
EOR x1 @> 64-11, h, h
|
|
||||||
MUL prime1, h
|
|
||||||
|
|
||||||
MUL prime5, x2
|
|
||||||
ROR $64-11, h
|
|
||||||
EOR x2 @> 64-11, h, h
|
|
||||||
MUL prime1, h
|
|
||||||
|
|
||||||
try1:
|
|
||||||
TBZ $0, n, finalize
|
|
||||||
MOVBU (p), x4
|
|
||||||
|
|
||||||
MUL prime5, x4
|
|
||||||
ROR $64-11, h
|
|
||||||
EOR x4 @> 64-11, h, h
|
|
||||||
MUL prime1, h
|
|
||||||
|
|
||||||
finalize:
|
|
||||||
EOR h >> 33, h
|
|
||||||
MUL prime2, h
|
|
||||||
EOR h >> 29, h
|
|
||||||
MUL prime3, h
|
|
||||||
EOR h >> 32, h
|
|
||||||
|
|
||||||
MOVD h, ret+24(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
// func writeBlocks(d *Digest, b []byte) int
|
|
||||||
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
|
|
||||||
LDP ·primes+0(SB), (prime1, prime2)
|
|
||||||
|
|
||||||
// Load state. Assume v[1-4] are stored contiguously.
|
|
||||||
MOVD d+0(FP), digest
|
|
||||||
LDP 0(digest), (v1, v2)
|
|
||||||
LDP 16(digest), (v3, v4)
|
|
||||||
|
|
||||||
LDP b_base+8(FP), (p, n)
|
|
||||||
|
|
||||||
blockLoop()
|
|
||||||
|
|
||||||
// Store updated state.
|
|
||||||
STP (v1, v2), 0(digest)
|
|
||||||
STP (v3, v4), 16(digest)
|
|
||||||
|
|
||||||
BIC $31, n
|
|
||||||
MOVD n, ret+32(FP)
|
|
||||||
RET
|
|
||||||
15
vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
generated
vendored
15
vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
generated
vendored
@@ -1,15 +0,0 @@
|
|||||||
//go:build (amd64 || arm64) && !appengine && gc && !purego
|
|
||||||
// +build amd64 arm64
|
|
||||||
// +build !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !purego
|
|
||||||
|
|
||||||
package xxhash
|
|
||||||
|
|
||||||
// Sum64 computes the 64-bit xxHash digest of b.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func Sum64(b []byte) uint64
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
func writeBlocks(d *Digest, b []byte) int
|
|
||||||
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
@@ -1,76 +0,0 @@
|
|||||||
//go:build (!amd64 && !arm64) || appengine || !gc || purego
|
|
||||||
// +build !amd64,!arm64 appengine !gc purego
|
|
||||||
|
|
||||||
package xxhash
|
|
||||||
|
|
||||||
// Sum64 computes the 64-bit xxHash digest of b.
|
|
||||||
func Sum64(b []byte) uint64 {
|
|
||||||
// A simpler version would be
|
|
||||||
// d := New()
|
|
||||||
// d.Write(b)
|
|
||||||
// return d.Sum64()
|
|
||||||
// but this is faster, particularly for small inputs.
|
|
||||||
|
|
||||||
n := len(b)
|
|
||||||
var h uint64
|
|
||||||
|
|
||||||
if n >= 32 {
|
|
||||||
v1 := primes[0] + prime2
|
|
||||||
v2 := prime2
|
|
||||||
v3 := uint64(0)
|
|
||||||
v4 := -primes[0]
|
|
||||||
for len(b) >= 32 {
|
|
||||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
|
||||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
|
||||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
|
||||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
|
||||||
b = b[32:len(b):len(b)]
|
|
||||||
}
|
|
||||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
|
||||||
h = mergeRound(h, v1)
|
|
||||||
h = mergeRound(h, v2)
|
|
||||||
h = mergeRound(h, v3)
|
|
||||||
h = mergeRound(h, v4)
|
|
||||||
} else {
|
|
||||||
h = prime5
|
|
||||||
}
|
|
||||||
|
|
||||||
h += uint64(n)
|
|
||||||
|
|
||||||
for ; len(b) >= 8; b = b[8:] {
|
|
||||||
k1 := round(0, u64(b[:8]))
|
|
||||||
h ^= k1
|
|
||||||
h = rol27(h)*prime1 + prime4
|
|
||||||
}
|
|
||||||
if len(b) >= 4 {
|
|
||||||
h ^= uint64(u32(b[:4])) * prime1
|
|
||||||
h = rol23(h)*prime2 + prime3
|
|
||||||
b = b[4:]
|
|
||||||
}
|
|
||||||
for ; len(b) > 0; b = b[1:] {
|
|
||||||
h ^= uint64(b[0]) * prime5
|
|
||||||
h = rol11(h) * prime1
|
|
||||||
}
|
|
||||||
|
|
||||||
h ^= h >> 33
|
|
||||||
h *= prime2
|
|
||||||
h ^= h >> 29
|
|
||||||
h *= prime3
|
|
||||||
h ^= h >> 32
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeBlocks(d *Digest, b []byte) int {
|
|
||||||
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
|
||||||
n := len(b)
|
|
||||||
for len(b) >= 32 {
|
|
||||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
|
||||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
|
||||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
|
||||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
|
||||||
b = b[32:len(b):len(b)]
|
|
||||||
}
|
|
||||||
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
|
|
||||||
return n - len(b)
|
|
||||||
}
|
|
||||||
16
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
16
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
@@ -1,16 +0,0 @@
|
|||||||
//go:build appengine
|
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
// This file contains the safe implementations of otherwise unsafe-using code.
|
|
||||||
|
|
||||||
package xxhash
|
|
||||||
|
|
||||||
// Sum64String computes the 64-bit xxHash digest of s.
|
|
||||||
func Sum64String(s string) uint64 {
|
|
||||||
return Sum64([]byte(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteString adds more data to d. It always returns len(s), nil.
|
|
||||||
func (d *Digest) WriteString(s string) (n int, err error) {
|
|
||||||
return d.Write([]byte(s))
|
|
||||||
}
|
|
||||||
58
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
58
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
@@ -1,58 +0,0 @@
|
|||||||
//go:build !appengine
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
// This file encapsulates usage of unsafe.
|
|
||||||
// xxhash_safe.go contains the safe implementations.
|
|
||||||
|
|
||||||
package xxhash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// In the future it's possible that compiler optimizations will make these
|
|
||||||
// XxxString functions unnecessary by realizing that calls such as
|
|
||||||
// Sum64([]byte(s)) don't need to copy s. See https://go.dev/issue/2205.
|
|
||||||
// If that happens, even if we keep these functions they can be replaced with
|
|
||||||
// the trivial safe code.
|
|
||||||
|
|
||||||
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
|
|
||||||
//
|
|
||||||
// var b []byte
|
|
||||||
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
|
||||||
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
|
||||||
// bh.Len = len(s)
|
|
||||||
// bh.Cap = len(s)
|
|
||||||
//
|
|
||||||
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
|
|
||||||
// weight to this sequence of expressions that any function that uses it will
|
|
||||||
// not be inlined. Instead, the functions below use a different unsafe
|
|
||||||
// conversion designed to minimize the inliner weight and allow both to be
|
|
||||||
// inlined. There is also a test (TestInlining) which verifies that these are
|
|
||||||
// inlined.
|
|
||||||
//
|
|
||||||
// See https://github.com/golang/go/issues/42739 for discussion.
|
|
||||||
|
|
||||||
// Sum64String computes the 64-bit xxHash digest of s.
|
|
||||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
|
||||||
func Sum64String(s string) uint64 {
|
|
||||||
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
|
||||||
return Sum64(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteString adds more data to d. It always returns len(s), nil.
|
|
||||||
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
|
||||||
func (d *Digest) WriteString(s string) (n int, err error) {
|
|
||||||
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
|
|
||||||
// d.Write always returns len(s), nil.
|
|
||||||
// Ignoring the return output and returning these fixed values buys a
|
|
||||||
// savings of 6 in the inliner's cost model.
|
|
||||||
return len(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
|
|
||||||
// of the first two words is the same as the layout of a string.
|
|
||||||
type sliceHeader struct {
|
|
||||||
s string
|
|
||||||
cap int
|
|
||||||
}
|
|
||||||
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
@@ -1,15 +0,0 @@
|
|||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
@@ -1,145 +0,0 @@
|
|||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
|
||||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
|
||||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
|
||||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = false
|
|
||||||
|
|
||||||
// ptrSize is the size of a pointer on the current arch.
|
|
||||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
type flag uintptr
|
|
||||||
|
|
||||||
var (
|
|
||||||
// flagRO indicates whether the value field of a reflect.Value
|
|
||||||
// is read-only.
|
|
||||||
flagRO flag
|
|
||||||
|
|
||||||
// flagAddr indicates whether the address of the reflect.Value's
|
|
||||||
// value may be taken.
|
|
||||||
flagAddr flag
|
|
||||||
)
|
|
||||||
|
|
||||||
// flagKindMask holds the bits that make up the kind
|
|
||||||
// part of the flags field. In all the supported versions,
|
|
||||||
// it is in the lower 5 bits.
|
|
||||||
const flagKindMask = flag(0x1f)
|
|
||||||
|
|
||||||
// Different versions of Go have used different
|
|
||||||
// bit layouts for the flags type. This table
|
|
||||||
// records the known combinations.
|
|
||||||
var okFlags = []struct {
|
|
||||||
ro, addr flag
|
|
||||||
}{{
|
|
||||||
// From Go 1.4 to 1.5
|
|
||||||
ro: 1 << 5,
|
|
||||||
addr: 1 << 7,
|
|
||||||
}, {
|
|
||||||
// Up to Go tip.
|
|
||||||
ro: 1<<5 | 1<<6,
|
|
||||||
addr: 1 << 8,
|
|
||||||
}}
|
|
||||||
|
|
||||||
var flagValOffset = func() uintptr {
|
|
||||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
||||||
if !ok {
|
|
||||||
panic("reflect.Value has no flag field")
|
|
||||||
}
|
|
||||||
return field.Offset
|
|
||||||
}()
|
|
||||||
|
|
||||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
|
||||||
func flagField(v *reflect.Value) *flag {
|
|
||||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
|
||||||
// the typical safety restrictions preventing access to unaddressable and
|
|
||||||
// unexported data. It works by digging the raw pointer to the underlying
|
|
||||||
// value out of the protected value and generating a new unprotected (unsafe)
|
|
||||||
// reflect.Value to it.
|
|
||||||
//
|
|
||||||
// This allows us to check for implementations of the Stringer and error
|
|
||||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
|
||||||
// inaccessible values such as unexported struct fields.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
flagFieldPtr := flagField(&v)
|
|
||||||
*flagFieldPtr &^= flagRO
|
|
||||||
*flagFieldPtr |= flagAddr
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity checks against future reflect package changes
|
|
||||||
// to the type or semantics of the Value.flag field.
|
|
||||||
func init() {
|
|
||||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
||||||
if !ok {
|
|
||||||
panic("reflect.Value has no flag field")
|
|
||||||
}
|
|
||||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
|
||||||
panic("reflect.Value flag field has changed kind")
|
|
||||||
}
|
|
||||||
type t0 int
|
|
||||||
var t struct {
|
|
||||||
A t0
|
|
||||||
// t0 will have flagEmbedRO set.
|
|
||||||
t0
|
|
||||||
// a will have flagStickyRO set
|
|
||||||
a t0
|
|
||||||
}
|
|
||||||
vA := reflect.ValueOf(t).FieldByName("A")
|
|
||||||
va := reflect.ValueOf(t).FieldByName("a")
|
|
||||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
|
||||||
|
|
||||||
// Infer flagRO from the difference between the flags
|
|
||||||
// for the (otherwise identical) fields in t.
|
|
||||||
flagPublic := *flagField(&vA)
|
|
||||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
|
||||||
flagRO = flagPublic ^ flagWithRO
|
|
||||||
|
|
||||||
// Infer flagAddr from the difference between a value
|
|
||||||
// taken from a pointer and not.
|
|
||||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
|
||||||
flagNoPtr := *flagField(&vA)
|
|
||||||
flagPtr := *flagField(&vPtrA)
|
|
||||||
flagAddr = flagNoPtr ^ flagPtr
|
|
||||||
|
|
||||||
// Check that the inferred flags tally with one of the known versions.
|
|
||||||
for _, f := range okFlags {
|
|
||||||
if flagRO == f.ro && flagAddr == f.addr {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("reflect.Value read-only flag has changed semantics")
|
|
||||||
}
|
|
||||||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
|
||||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build js appengine safe disableunsafe !go1.4
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
|
||||||
// that bypasses the typical safety restrictions preventing access to
|
|
||||||
// unaddressable and unexported data. However, doing this relies on access to
|
|
||||||
// the unsafe package. This is a stub version which simply returns the passed
|
|
||||||
// reflect.Value when the unsafe package is not available.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
@@ -1,341 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
|
||||||
// the technique used in the fmt package.
|
|
||||||
var (
|
|
||||||
panicBytes = []byte("(PANIC=")
|
|
||||||
plusBytes = []byte("+")
|
|
||||||
iBytes = []byte("i")
|
|
||||||
trueBytes = []byte("true")
|
|
||||||
falseBytes = []byte("false")
|
|
||||||
interfaceBytes = []byte("(interface {})")
|
|
||||||
commaNewlineBytes = []byte(",\n")
|
|
||||||
newlineBytes = []byte("\n")
|
|
||||||
openBraceBytes = []byte("{")
|
|
||||||
openBraceNewlineBytes = []byte("{\n")
|
|
||||||
closeBraceBytes = []byte("}")
|
|
||||||
asteriskBytes = []byte("*")
|
|
||||||
colonBytes = []byte(":")
|
|
||||||
colonSpaceBytes = []byte(": ")
|
|
||||||
openParenBytes = []byte("(")
|
|
||||||
closeParenBytes = []byte(")")
|
|
||||||
spaceBytes = []byte(" ")
|
|
||||||
pointerChainBytes = []byte("->")
|
|
||||||
nilAngleBytes = []byte("<nil>")
|
|
||||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
|
||||||
maxShortBytes = []byte("<max>")
|
|
||||||
circularBytes = []byte("<already shown>")
|
|
||||||
circularShortBytes = []byte("<shown>")
|
|
||||||
invalidAngleBytes = []byte("<invalid>")
|
|
||||||
openBracketBytes = []byte("[")
|
|
||||||
closeBracketBytes = []byte("]")
|
|
||||||
percentBytes = []byte("%")
|
|
||||||
precisionBytes = []byte(".")
|
|
||||||
openAngleBytes = []byte("<")
|
|
||||||
closeAngleBytes = []byte(">")
|
|
||||||
openMapBytes = []byte("map[")
|
|
||||||
closeMapBytes = []byte("]")
|
|
||||||
lenEqualsBytes = []byte("len=")
|
|
||||||
capEqualsBytes = []byte("cap=")
|
|
||||||
)
|
|
||||||
|
|
||||||
// hexDigits is used to map a decimal value to a hex digit.
|
|
||||||
var hexDigits = "0123456789abcdef"
|
|
||||||
|
|
||||||
// catchPanic handles any panics that might occur during the handleMethods
|
|
||||||
// calls.
|
|
||||||
func catchPanic(w io.Writer, v reflect.Value) {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
w.Write(panicBytes)
|
|
||||||
fmt.Fprintf(w, "%v", err)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMethods attempts to call the Error and String methods on the underlying
|
|
||||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
|
||||||
//
|
|
||||||
// It handles panics in any called methods by catching and displaying the error
|
|
||||||
// as the formatted value.
|
|
||||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
|
||||||
// We need an interface to check if the type implements the error or
|
|
||||||
// Stringer interface. However, the reflect package won't give us an
|
|
||||||
// interface on certain things like unexported struct fields in order
|
|
||||||
// to enforce visibility rules. We use unsafe, when it's available,
|
|
||||||
// to bypass these restrictions since this package does not mutate the
|
|
||||||
// values.
|
|
||||||
if !v.CanInterface() {
|
|
||||||
if UnsafeDisabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose whether or not to do error and Stringer interface lookups against
|
|
||||||
// the base type or a pointer to the base type depending on settings.
|
|
||||||
// Technically calling one of these methods with a pointer receiver can
|
|
||||||
// mutate the value, however, types which choose to satisify an error or
|
|
||||||
// Stringer interface with a pointer receiver should not be mutating their
|
|
||||||
// state inside these interface methods.
|
|
||||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
if v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it an error or Stringer?
|
|
||||||
switch iface := v.Interface().(type) {
|
|
||||||
case error:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
return true
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printBool outputs a boolean value as true or false to Writer w.
|
|
||||||
func printBool(w io.Writer, val bool) {
|
|
||||||
if val {
|
|
||||||
w.Write(trueBytes)
|
|
||||||
} else {
|
|
||||||
w.Write(falseBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printInt outputs a signed integer value to Writer w.
|
|
||||||
func printInt(w io.Writer, val int64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printUint outputs an unsigned integer value to Writer w.
|
|
||||||
func printUint(w io.Writer, val uint64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printFloat outputs a floating point value using the specified precision,
|
|
||||||
// which is expected to be 32 or 64bit, to Writer w.
|
|
||||||
func printFloat(w io.Writer, val float64, precision int) {
|
|
||||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printComplex outputs a complex value using the specified float precision
|
|
||||||
// for the real and imaginary parts to Writer w.
|
|
||||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
|
||||||
r := real(c)
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
|
||||||
i := imag(c)
|
|
||||||
if i >= 0 {
|
|
||||||
w.Write(plusBytes)
|
|
||||||
}
|
|
||||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
|
||||||
w.Write(iBytes)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
|
||||||
// prefix to Writer w.
|
|
||||||
func printHexPtr(w io.Writer, p uintptr) {
|
|
||||||
// Null pointer.
|
|
||||||
num := uint64(p)
|
|
||||||
if num == 0 {
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
|
||||||
buf := make([]byte, 18)
|
|
||||||
|
|
||||||
// It's simpler to construct the hex string right to left.
|
|
||||||
base := uint64(16)
|
|
||||||
i := len(buf) - 1
|
|
||||||
for num >= base {
|
|
||||||
buf[i] = hexDigits[num%base]
|
|
||||||
num /= base
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
buf[i] = hexDigits[num]
|
|
||||||
|
|
||||||
// Add '0x' prefix.
|
|
||||||
i--
|
|
||||||
buf[i] = 'x'
|
|
||||||
i--
|
|
||||||
buf[i] = '0'
|
|
||||||
|
|
||||||
// Strip unused leading bytes.
|
|
||||||
buf = buf[i:]
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
|
||||||
// elements to be sorted.
|
|
||||||
type valuesSorter struct {
|
|
||||||
values []reflect.Value
|
|
||||||
strings []string // either nil or same len and values
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
|
||||||
// surrogate keys on which the data should be sorted. It uses flags in
|
|
||||||
// ConfigState to decide if and how to populate those surrogate keys.
|
|
||||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
|
||||||
vs := &valuesSorter{values: values, cs: cs}
|
|
||||||
if canSortSimply(vs.values[0].Kind()) {
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
if !cs.DisableMethods {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
if !handleMethods(cs, &b, vs.values[i]) {
|
|
||||||
vs.strings = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
vs.strings[i] = b.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vs.strings == nil && cs.SpewKeys {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
|
|
||||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
|
||||||
// directly, or whether it should be considered for sorting by surrogate keys
|
|
||||||
// (if the ConfigState allows it).
|
|
||||||
func canSortSimply(kind reflect.Kind) bool {
|
|
||||||
// This switch parallels valueSortLess, except for the default case.
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
return true
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return true
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return true
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return true
|
|
||||||
case reflect.String:
|
|
||||||
return true
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return true
|
|
||||||
case reflect.Array:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of values in the slice. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Len() int {
|
|
||||||
return len(s.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps the values at the passed indices. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Swap(i, j int) {
|
|
||||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
|
||||||
if s.strings != nil {
|
|
||||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueSortLess returns whether the first value should sort before the second
|
|
||||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
|
||||||
// implementation.
|
|
||||||
func valueSortLess(a, b reflect.Value) bool {
|
|
||||||
switch a.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return !a.Bool() && b.Bool()
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return a.Int() < b.Int()
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return a.Float() < b.Float()
|
|
||||||
case reflect.String:
|
|
||||||
return a.String() < b.String()
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Array:
|
|
||||||
// Compare the contents of both arrays.
|
|
||||||
l := a.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
av := a.Index(i)
|
|
||||||
bv := b.Index(i)
|
|
||||||
if av.Interface() == bv.Interface() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return valueSortLess(av, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.String() < b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less returns whether the value at index i should sort before the
|
|
||||||
// value at index j. It is part of the sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Less(i, j int) bool {
|
|
||||||
if s.strings == nil {
|
|
||||||
return valueSortLess(s.values[i], s.values[j])
|
|
||||||
}
|
|
||||||
return s.strings[i] < s.strings[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortValues is a sort function that handles both native types and any type that
|
|
||||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
|
||||||
// their Value.String() value to ensure display stability.
|
|
||||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sort.Sort(newValuesSorter(values, cs))
|
|
||||||
}
|
|
||||||
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
@@ -1,306 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigState houses the configuration options used by spew to format and
|
|
||||||
// display values. There is a global instance, Config, that is used to control
|
|
||||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
|
||||||
// provides methods equivalent to the top-level functions.
|
|
||||||
//
|
|
||||||
// The zero value for ConfigState provides no indentation. You would typically
|
|
||||||
// want to set it to a space or a tab.
|
|
||||||
//
|
|
||||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
|
||||||
// with default settings. See the documentation of NewDefaultConfig for default
|
|
||||||
// values.
|
|
||||||
type ConfigState struct {
|
|
||||||
// Indent specifies the string to use for each indentation level. The
|
|
||||||
// global config instance that all top-level functions use set this to a
|
|
||||||
// single space by default. If you would like more indentation, you might
|
|
||||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
|
||||||
Indent string
|
|
||||||
|
|
||||||
// MaxDepth controls the maximum number of levels to descend into nested
|
|
||||||
// data structures. The default, 0, means there is no limit.
|
|
||||||
//
|
|
||||||
// NOTE: Circular data structures are properly detected, so it is not
|
|
||||||
// necessary to set this value unless you specifically want to limit deeply
|
|
||||||
// nested data structures.
|
|
||||||
MaxDepth int
|
|
||||||
|
|
||||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
|
||||||
// invoked for types that implement them.
|
|
||||||
DisableMethods bool
|
|
||||||
|
|
||||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
|
||||||
// error and Stringer interfaces on types which only accept a pointer
|
|
||||||
// receiver when the current type is not a pointer.
|
|
||||||
//
|
|
||||||
// NOTE: This might be an unsafe action since calling one of these methods
|
|
||||||
// with a pointer receiver could technically mutate the value, however,
|
|
||||||
// in practice, types which choose to satisify an error or Stringer
|
|
||||||
// interface with a pointer receiver should not be mutating their state
|
|
||||||
// inside these interface methods. As a result, this option relies on
|
|
||||||
// access to the unsafe package, so it will not have any effect when
|
|
||||||
// running in environments without access to the unsafe package such as
|
|
||||||
// Google App Engine or with the "safe" build tag specified.
|
|
||||||
DisablePointerMethods bool
|
|
||||||
|
|
||||||
// DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
// pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
DisablePointerAddresses bool
|
|
||||||
|
|
||||||
// DisableCapacities specifies whether to disable the printing of capacities
|
|
||||||
// for arrays, slices, maps and channels. This is useful when diffing
|
|
||||||
// data structures in tests.
|
|
||||||
DisableCapacities bool
|
|
||||||
|
|
||||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
|
||||||
// a custom error or Stringer interface is invoked. The default, false,
|
|
||||||
// means it will print the results of invoking the custom error or Stringer
|
|
||||||
// interface and return immediately instead of continuing to recurse into
|
|
||||||
// the internals of the data type.
|
|
||||||
//
|
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
|
||||||
ContinueOnMethod bool
|
|
||||||
|
|
||||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
|
||||||
// this to have a more deterministic, diffable output. Note that only
|
|
||||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
|
||||||
// that support the error or Stringer interfaces (if methods are
|
|
||||||
// enabled) are supported, with other types sorted according to the
|
|
||||||
// reflect.Value.String() output which guarantees display stability.
|
|
||||||
SortKeys bool
|
|
||||||
|
|
||||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
|
||||||
// be spewed to strings and sorted by those strings. This is only
|
|
||||||
// considered if SortKeys is true.
|
|
||||||
SpewKeys bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
|
||||||
// The configuration can be changed by modifying the contents of spew.Config.
|
|
||||||
var Config = ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the formatted string as a value that satisfies error. See NewFormatter
|
|
||||||
// for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
c.Printf, c.Println, or c.Printf.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(c, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(c, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by modifying the public members
|
|
||||||
of c. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) Dump(a ...interface{}) {
|
|
||||||
fdump(c, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(c, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a spew Formatter interface using
|
|
||||||
// the ConfigState associated with s.
|
|
||||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = newFormatter(c, arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
|
||||||
//
|
|
||||||
// Indent: " "
|
|
||||||
// MaxDepth: 0
|
|
||||||
// DisableMethods: false
|
|
||||||
// DisablePointerMethods: false
|
|
||||||
// ContinueOnMethod: false
|
|
||||||
// SortKeys: false
|
|
||||||
func NewDefaultConfig() *ConfigState {
|
|
||||||
return &ConfigState{Indent: " "}
|
|
||||||
}
|
|
||||||
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
@@ -1,211 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
|
||||||
debugging.
|
|
||||||
|
|
||||||
A quick overview of the additional features spew provides over the built-in
|
|
||||||
printing facilities for Go data types are as follows:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output (only when using
|
|
||||||
Dump style)
|
|
||||||
|
|
||||||
There are two different approaches spew allows for dumping Go data structures:
|
|
||||||
|
|
||||||
* Dump style which prints with newlines, customizable indentation,
|
|
||||||
and additional debug information such as types and all pointer addresses
|
|
||||||
used to indirect to the final value
|
|
||||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
|
||||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
|
||||||
similar to the default %v while providing the additional functionality
|
|
||||||
outlined above and passing unsupported format verbs such as %x and %q
|
|
||||||
along to fmt
|
|
||||||
|
|
||||||
Quick Start
|
|
||||||
|
|
||||||
This section demonstrates how to quickly get started with spew. See the
|
|
||||||
sections below for further details on formatting and configuration options.
|
|
||||||
|
|
||||||
To dump a variable with full newlines, indentation, type, and pointer
|
|
||||||
information use Dump, Fdump, or Sdump:
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
|
||||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
|
||||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
|
||||||
%#+v (adds types and pointer addresses):
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
Configuration Options
|
|
||||||
|
|
||||||
Configuration of spew is handled by fields in the ConfigState type. For
|
|
||||||
convenience, all of the top-level functions use a global state available
|
|
||||||
via the spew.Config global.
|
|
||||||
|
|
||||||
It is also possible to create a ConfigState instance that provides methods
|
|
||||||
equivalent to the top-level functions. This allows concurrent configuration
|
|
||||||
options. See the ConfigState documentation for more details.
|
|
||||||
|
|
||||||
The following configuration options are available:
|
|
||||||
* Indent
|
|
||||||
String to use for each indentation level for Dump functions.
|
|
||||||
It is a single space by default. A popular alternative is "\t".
|
|
||||||
|
|
||||||
* MaxDepth
|
|
||||||
Maximum number of levels to descend into nested data structures.
|
|
||||||
There is no limit by default.
|
|
||||||
|
|
||||||
* DisableMethods
|
|
||||||
Disables invocation of error and Stringer interface methods.
|
|
||||||
Method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerMethods
|
|
||||||
Disables invocation of error and Stringer interface methods on types
|
|
||||||
which only accept pointer receivers from non-pointer variables.
|
|
||||||
Pointer method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerAddresses
|
|
||||||
DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
|
|
||||||
* DisableCapacities
|
|
||||||
DisableCapacities specifies whether to disable the printing of
|
|
||||||
capacities for arrays, slices, maps and channels. This is useful when
|
|
||||||
diffing data structures in tests.
|
|
||||||
|
|
||||||
* ContinueOnMethod
|
|
||||||
Enables recursion into types after invoking error and Stringer interface
|
|
||||||
methods. Recursion after method invocation is disabled by default.
|
|
||||||
|
|
||||||
* SortKeys
|
|
||||||
Specifies map keys should be sorted before being printed. Use
|
|
||||||
this to have a more deterministic, diffable output. Note that
|
|
||||||
only native types (bool, int, uint, floats, uintptr and string)
|
|
||||||
and types which implement error or Stringer interfaces are
|
|
||||||
supported with other types sorted according to the
|
|
||||||
reflect.Value.String() output which guarantees display
|
|
||||||
stability. Natural map order is used by default.
|
|
||||||
|
|
||||||
* SpewKeys
|
|
||||||
Specifies that, as a last resort attempt, map keys should be
|
|
||||||
spewed to strings and sorted by those strings. This is only
|
|
||||||
considered if SortKeys is true.
|
|
||||||
|
|
||||||
Dump Usage
|
|
||||||
|
|
||||||
Simply call spew.Dump with a list of variables you want to dump:
|
|
||||||
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
|
||||||
io.Writer. For example, to dump to standard error:
|
|
||||||
|
|
||||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
|
||||||
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Sample Dump Output
|
|
||||||
|
|
||||||
See the Dump example for details on the setup of the types and variables being
|
|
||||||
shown here.
|
|
||||||
|
|
||||||
(main.Foo) {
|
|
||||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
|
||||||
flag: (main.Flag) flagTwo,
|
|
||||||
data: (uintptr) <nil>
|
|
||||||
}),
|
|
||||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
(string) (len=3) "one": (bool) true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
|
||||||
command as shown.
|
|
||||||
([]uint8) (len=32 cap=32) {
|
|
||||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
|
||||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
|
||||||
00000020 31 32 |12|
|
|
||||||
}
|
|
||||||
|
|
||||||
Custom Formatter
|
|
||||||
|
|
||||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
|
||||||
so that it integrates cleanly with standard fmt package printing functions. The
|
|
||||||
formatter is useful for inline printing of smaller data types similar to the
|
|
||||||
standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Custom Formatter Usage
|
|
||||||
|
|
||||||
The simplest way to make use of the spew custom formatter is to call one of the
|
|
||||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
|
||||||
functions have syntax you are most likely already familiar with:
|
|
||||||
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Println(myVar, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
See the Index for the full list convenience functions.
|
|
||||||
|
|
||||||
Sample Formatter Output
|
|
||||||
|
|
||||||
Double pointer to a uint8:
|
|
||||||
%v: <**>5
|
|
||||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
|
||||||
%#v: (**uint8)5
|
|
||||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
|
||||||
|
|
||||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
|
||||||
%v: <*>{1 <*><shown>}
|
|
||||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
|
||||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
|
||||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
|
||||||
|
|
||||||
See the Printf example for details on the setup of variables being shown
|
|
||||||
here.
|
|
||||||
|
|
||||||
Errors
|
|
||||||
|
|
||||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
|
||||||
detects them and handles them internally by printing the panic information
|
|
||||||
inline with the output. Since spew is intended to provide deep pretty printing
|
|
||||||
capabilities on structures, it intentionally does not return any errors.
|
|
||||||
*/
|
|
||||||
package spew
|
|
||||||
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
@@ -1,509 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
|
||||||
// convert cgo types to uint8 slices for hexdumping.
|
|
||||||
uint8Type = reflect.TypeOf(uint8(0))
|
|
||||||
|
|
||||||
// cCharRE is a regular expression that matches a cgo char.
|
|
||||||
// It is used to detect character arrays to hexdump them.
|
|
||||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
|
||||||
|
|
||||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
|
||||||
// char. It is used to detect unsigned character arrays to hexdump
|
|
||||||
// them.
|
|
||||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
|
||||||
|
|
||||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
|
||||||
// It is used to detect uint8_t arrays to hexdump them.
|
|
||||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// dumpState contains information about the state of a dump operation.
|
|
||||||
type dumpState struct {
|
|
||||||
w io.Writer
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
ignoreNextIndent bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// indent performs indentation according to the depth level and cs.Indent
|
|
||||||
// option.
|
|
||||||
func (d *dumpState) indent() {
|
|
||||||
if d.ignoreNextIndent {
|
|
||||||
d.ignoreNextIndent = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range d.pointers {
|
|
||||||
if depth >= d.depth {
|
|
||||||
delete(d.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by dereferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d.pointers[addr] = d.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type information.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
d.w.Write([]byte(ve.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
|
|
||||||
// Display pointer information.
|
|
||||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
d.w.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(d.w, addr)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
switch {
|
|
||||||
case nilFound:
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound:
|
|
||||||
d.w.Write(circularBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.ignoreNextType = true
|
|
||||||
d.dump(ve)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
|
||||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
|
||||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
|
||||||
// Determine whether this type should be hex dumped or not. Also,
|
|
||||||
// for types which should be hexdumped, try to use the underlying data
|
|
||||||
// first, then fall back to trying to convert them to a uint8 slice.
|
|
||||||
var buf []uint8
|
|
||||||
doConvert := false
|
|
||||||
doHexDump := false
|
|
||||||
numEntries := v.Len()
|
|
||||||
if numEntries > 0 {
|
|
||||||
vt := v.Index(0).Type()
|
|
||||||
vts := vt.String()
|
|
||||||
switch {
|
|
||||||
// C types that need to be converted.
|
|
||||||
case cCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUnsignedCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUint8tCharRE.MatchString(vts):
|
|
||||||
doConvert = true
|
|
||||||
|
|
||||||
// Try to use existing uint8 slices and fall back to converting
|
|
||||||
// and copying if that fails.
|
|
||||||
case vt.Kind() == reflect.Uint8:
|
|
||||||
// We need an addressable interface to convert the type
|
|
||||||
// to a byte slice. However, the reflect package won't
|
|
||||||
// give us an interface on certain things like
|
|
||||||
// unexported struct fields in order to enforce
|
|
||||||
// visibility rules. We use unsafe, when available, to
|
|
||||||
// bypass these restrictions since this package does not
|
|
||||||
// mutate the values.
|
|
||||||
vs := v
|
|
||||||
if !vs.CanInterface() || !vs.CanAddr() {
|
|
||||||
vs = unsafeReflectValue(vs)
|
|
||||||
}
|
|
||||||
if !UnsafeDisabled {
|
|
||||||
vs = vs.Slice(0, numEntries)
|
|
||||||
|
|
||||||
// Use the existing uint8 slice if it can be
|
|
||||||
// type asserted.
|
|
||||||
iface := vs.Interface()
|
|
||||||
if slice, ok := iface.([]uint8); ok {
|
|
||||||
buf = slice
|
|
||||||
doHexDump = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The underlying data needs to be converted if it can't
|
|
||||||
// be type asserted to a uint8 slice.
|
|
||||||
doConvert = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy and convert the underlying type if needed.
|
|
||||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
|
||||||
// Convert and copy each element into a uint8 byte
|
|
||||||
// slice.
|
|
||||||
buf = make([]uint8, numEntries)
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
vv := v.Index(i)
|
|
||||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
|
||||||
}
|
|
||||||
doHexDump = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hexdump the entire slice as needed.
|
|
||||||
if doHexDump {
|
|
||||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
|
||||||
str := indent + hex.Dump(buf)
|
|
||||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
|
||||||
str = strings.TrimRight(str, d.cs.Indent)
|
|
||||||
d.w.Write([]byte(str))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively call dump for each item.
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
d.dump(d.unpackValue(v.Index(i)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
|
||||||
// value to figure out what kind of object we are dealing with and formats it
|
|
||||||
// appropriately. It is a recursive function, however circular data structures
|
|
||||||
// are detected and handled properly.
|
|
||||||
func (d *dumpState) dump(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
d.w.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
d.indent()
|
|
||||||
d.dumpPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !d.ignoreNextType {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write([]byte(v.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.ignoreNextType = false
|
|
||||||
|
|
||||||
// Display length and capacity if the built-in len and cap functions
|
|
||||||
// work with the value's kind and the len/cap itself is non-zero.
|
|
||||||
valueLen, valueCap := 0, 0
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
|
||||||
valueLen, valueCap = v.Len(), v.Cap()
|
|
||||||
case reflect.Map, reflect.String:
|
|
||||||
valueLen = v.Len()
|
|
||||||
}
|
|
||||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(lenEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueLen), 10)
|
|
||||||
}
|
|
||||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.w.Write(capEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueCap), 10)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
|
||||||
// is enabled
|
|
||||||
if !d.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(d.w, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(d.w, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(d.w, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(d.w, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(d.w, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(d.w, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(d.w, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.dumpSlice(v)
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if d.cs.SortKeys {
|
|
||||||
sortValues(keys, d.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
d.dump(d.unpackValue(key))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
numFields := v.NumField()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
d.indent()
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
d.w.Write([]byte(vtf.Name))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.Field(i)))
|
|
||||||
if i < (numFields - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(d.w, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(d.w, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it in case any new
|
|
||||||
// types are added.
|
|
||||||
default:
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fdump is a helper function to consolidate the logic from the various public
|
|
||||||
// methods which take varying writers and config states.
|
|
||||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
|
||||||
for _, arg := range a {
|
|
||||||
if arg == nil {
|
|
||||||
w.Write(interfaceBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
w.Write(newlineBytes)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d := dumpState{w: w, cs: cs}
|
|
||||||
d.pointers = make(map[uintptr]int)
|
|
||||||
d.dump(reflect.ValueOf(arg))
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(&Config, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(&Config, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by an exported package global,
|
|
||||||
spew.Config. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func Dump(a ...interface{}) {
|
|
||||||
fdump(&Config, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
@@ -1,419 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
|
||||||
const supportedFlags = "0-+# "
|
|
||||||
|
|
||||||
// formatState implements the fmt.Formatter interface and contains information
|
|
||||||
// about the state of a formatting operation. The NewFormatter function can
|
|
||||||
// be used to get a new Formatter which can be used directly as arguments
|
|
||||||
// in standard fmt package printing calls.
|
|
||||||
type formatState struct {
|
|
||||||
value interface{}
|
|
||||||
fs fmt.State
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefaultFormat recreates the original format string without precision
|
|
||||||
// and width information to pass in to fmt.Sprintf in the case of an
|
|
||||||
// unrecognized type. Unless new types are added to the language, this
|
|
||||||
// function won't ever be called.
|
|
||||||
func (f *formatState) buildDefaultFormat() (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune('v')
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructOrigFormat recreates the original format string including precision
|
|
||||||
// and width information to pass along to the standard fmt package. This allows
|
|
||||||
// automatic deferral of all format strings this package doesn't support.
|
|
||||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if width, ok := f.fs.Width(); ok {
|
|
||||||
buf.WriteString(strconv.Itoa(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
if precision, ok := f.fs.Precision(); ok {
|
|
||||||
buf.Write(precisionBytes)
|
|
||||||
buf.WriteString(strconv.Itoa(precision))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune(verb)
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
|
||||||
// ensures that types for values which have been unpacked from an interface
|
|
||||||
// are displayed when the show types flag is also set.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
f.ignoreNextType = false
|
|
||||||
if !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (f *formatState) formatPtr(v reflect.Value) {
|
|
||||||
// Display nil if top level pointer is nil.
|
|
||||||
showTypes := f.fs.Flag('#')
|
|
||||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range f.pointers {
|
|
||||||
if depth >= f.depth {
|
|
||||||
delete(f.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to possibly show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by derferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.pointers[addr] = f.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type or indirection level depending on flags.
|
|
||||||
if showTypes && !f.ignoreNextType {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
f.fs.Write([]byte(ve.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
} else {
|
|
||||||
if nilFound || cycleFound {
|
|
||||||
indirects += strings.Count(ve.Type().String(), "*")
|
|
||||||
}
|
|
||||||
f.fs.Write(openAngleBytes)
|
|
||||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
|
||||||
f.fs.Write(closeAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display pointer information depending on flags.
|
|
||||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(f.fs, addr)
|
|
||||||
}
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
switch {
|
|
||||||
case nilFound:
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound:
|
|
||||||
f.fs.Write(circularShortBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(ve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// format is the main workhorse for providing the Formatter interface. It
|
|
||||||
// uses the passed reflect value to figure out what kind of object we are
|
|
||||||
// dealing with and formats it appropriately. It is a recursive function,
|
|
||||||
// however circular data structures are detected and handled properly.
|
|
||||||
func (f *formatState) format(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
f.fs.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
f.formatPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write([]byte(v.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = false
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods
|
|
||||||
// flag is enabled.
|
|
||||||
if !f.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(f.fs, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(f.fs, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(f.fs, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(f.fs, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(f.fs, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(f.fs, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(f.fs, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
f.fs.Write(openBracketBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.Index(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBracketBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
f.fs.Write([]byte(v.String()))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f.fs.Write(openMapBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if f.cs.SortKeys {
|
|
||||||
sortValues(keys, f.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(key))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.MapIndex(key)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeMapBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
numFields := v.NumField()
|
|
||||||
f.fs.Write(openBraceBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
|
||||||
f.fs.Write([]byte(vtf.Name))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
}
|
|
||||||
f.format(f.unpackValue(v.Field(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(f.fs, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it if any get added.
|
|
||||||
default:
|
|
||||||
format := f.buildDefaultFormat()
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(f.fs, format, v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(f.fs, format, v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
|
||||||
// details.
|
|
||||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
|
||||||
f.fs = fs
|
|
||||||
|
|
||||||
// Use standard formatting for verbs that are not v.
|
|
||||||
if verb != 'v' {
|
|
||||||
format := f.constructOrigFormat(verb)
|
|
||||||
fmt.Fprintf(fs, format, f.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.value == nil {
|
|
||||||
if fs.Flag('#') {
|
|
||||||
fs.Write(interfaceBytes)
|
|
||||||
}
|
|
||||||
fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.format(reflect.ValueOf(f.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFormatter is a helper function to consolidate the logic from the various
|
|
||||||
// public methods which take varying config states.
|
|
||||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
|
||||||
fs := &formatState{value: v, cs: cs}
|
|
||||||
fs.pointers = make(map[uintptr]int)
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
Printf, Println, or Fprintf.
|
|
||||||
*/
|
|
||||||
func NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(&Config, v)
|
|
||||||
}
|
|
||||||
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
@@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the formatted string as a value that satisfies error. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a default spew Formatter interface.
|
|
||||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = NewFormatter(arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
71
vendor/github.com/emicklei/go-restful/v3/.gitignore
generated
vendored
71
vendor/github.com/emicklei/go-restful/v3/.gitignore
generated
vendored
@@ -1,71 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
restful.html
|
|
||||||
|
|
||||||
*.out
|
|
||||||
|
|
||||||
tmp.prof
|
|
||||||
|
|
||||||
go-restful.test
|
|
||||||
|
|
||||||
examples/restful-basic-authentication
|
|
||||||
|
|
||||||
examples/restful-encoding-filter
|
|
||||||
|
|
||||||
examples/restful-filters
|
|
||||||
|
|
||||||
examples/restful-hello-world
|
|
||||||
|
|
||||||
examples/restful-resource-functions
|
|
||||||
|
|
||||||
examples/restful-serve-static
|
|
||||||
|
|
||||||
examples/restful-user-service
|
|
||||||
|
|
||||||
*.DS_Store
|
|
||||||
examples/restful-user-resource
|
|
||||||
|
|
||||||
examples/restful-multi-containers
|
|
||||||
|
|
||||||
examples/restful-form-handling
|
|
||||||
|
|
||||||
examples/restful-CORS-filter
|
|
||||||
|
|
||||||
examples/restful-options-filter
|
|
||||||
|
|
||||||
examples/restful-curly-router
|
|
||||||
|
|
||||||
examples/restful-cpuprofiler-service
|
|
||||||
|
|
||||||
examples/restful-pre-post-filters
|
|
||||||
|
|
||||||
curly.prof
|
|
||||||
|
|
||||||
examples/restful-NCSA-logging
|
|
||||||
|
|
||||||
examples/restful-html-template
|
|
||||||
|
|
||||||
s.html
|
|
||||||
restful-path-tail
|
|
||||||
.idea
|
|
||||||
1
vendor/github.com/emicklei/go-restful/v3/.goconvey
generated
vendored
1
vendor/github.com/emicklei/go-restful/v3/.goconvey
generated
vendored
@@ -1 +0,0 @@
|
|||||||
ignore
|
|
||||||
13
vendor/github.com/emicklei/go-restful/v3/.travis.yml
generated
vendored
13
vendor/github.com/emicklei/go-restful/v3/.travis.yml
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.x
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go test -v
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
||||||
408
vendor/github.com/emicklei/go-restful/v3/CHANGES.md
generated
vendored
408
vendor/github.com/emicklei/go-restful/v3/CHANGES.md
generated
vendored
@@ -1,408 +0,0 @@
|
|||||||
# Change history of go-restful
|
|
||||||
|
|
||||||
|
|
||||||
## [v3.12.0] - 2024-03-11
|
|
||||||
- add Flush method #529 (#538)
|
|
||||||
- fix: Improper handling of empty POST requests (#543)
|
|
||||||
|
|
||||||
## [v3.11.3] - 2024-01-09
|
|
||||||
- better not have 2 tags on one commit
|
|
||||||
|
|
||||||
## [v3.11.1, v3.11.2] - 2024-01-09
|
|
||||||
|
|
||||||
- fix by restoring custom JSON handler functions (Mike Beaumont #540)
|
|
||||||
|
|
||||||
## [v3.11.0] - 2023-08-19
|
|
||||||
|
|
||||||
- restored behavior as <= v3.9.0 with option to change path strategy using TrimRightSlashEnabled.
|
|
||||||
|
|
||||||
## [v3.10.2] - 2023-03-09 - DO NOT USE
|
|
||||||
|
|
||||||
- introduced MergePathStrategy to be able to revert behaviour of path concatenation to 3.9.0
|
|
||||||
see comment in Readme how to customize this behaviour.
|
|
||||||
|
|
||||||
## [v3.10.1] - 2022-11-19 - DO NOT USE
|
|
||||||
|
|
||||||
- fix broken 3.10.0 by using path package for joining paths
|
|
||||||
|
|
||||||
## [v3.10.0] - 2022-10-11 - BROKEN
|
|
||||||
|
|
||||||
- changed tokenizer to match std route match behavior; do not trimright the path (#511)
|
|
||||||
- Add MIME_ZIP (#512)
|
|
||||||
- Add MIME_ZIP and HEADER_ContentDisposition (#513)
|
|
||||||
- Changed how to get query parameter issue #510
|
|
||||||
|
|
||||||
## [v3.9.0] - 2022-07-21
|
|
||||||
|
|
||||||
- add support for http.Handler implementations to work as FilterFunction, issue #504 (thanks to https://github.com/ggicci)
|
|
||||||
|
|
||||||
## [v3.8.0] - 2022-06-06
|
|
||||||
|
|
||||||
- use exact matching of allowed domain entries, issue #489 (#493)
|
|
||||||
- this changes fixes [security] Authorization Bypass Through User-Controlled Key
|
|
||||||
by changing the behaviour of the AllowedDomains setting in the CORS filter.
|
|
||||||
To support the previous behaviour, the CORS filter type now has a AllowedDomainFunc
|
|
||||||
callback mechanism which is called when a simple domain match fails.
|
|
||||||
- add test and fix for POST without body and Content-type, issue #492 (#496)
|
|
||||||
- [Minor] Bad practice to have a mix of Receiver types. (#491)
|
|
||||||
|
|
||||||
## [v3.7.2] - 2021-11-24
|
|
||||||
|
|
||||||
- restored FilterChain (#482 by SVilgelm)
|
|
||||||
|
|
||||||
|
|
||||||
## [v3.7.1] - 2021-10-04
|
|
||||||
|
|
||||||
- fix problem with contentEncodingEnabled setting (#479)
|
|
||||||
|
|
||||||
## [v3.7.0] - 2021-09-24
|
|
||||||
|
|
||||||
- feat(parameter): adds additional openapi mappings (#478)
|
|
||||||
|
|
||||||
## [v3.6.0] - 2021-09-18
|
|
||||||
|
|
||||||
- add support for vendor extensions (#477 thx erraggy)
|
|
||||||
|
|
||||||
## [v3.5.2] - 2021-07-14
|
|
||||||
|
|
||||||
- fix removing absent route from webservice (#472)
|
|
||||||
|
|
||||||
## [v3.5.1] - 2021-04-12
|
|
||||||
|
|
||||||
- fix handling no match access selected path
|
|
||||||
- remove obsolete field
|
|
||||||
|
|
||||||
## [v3.5.0] - 2021-04-10
|
|
||||||
|
|
||||||
- add check for wildcard (#463) in CORS
|
|
||||||
- add access to Route from Request, issue #459 (#462)
|
|
||||||
|
|
||||||
## [v3.4.0] - 2020-11-10
|
|
||||||
|
|
||||||
- Added OPTIONS to WebService
|
|
||||||
|
|
||||||
## [v3.3.2] - 2020-01-23
|
|
||||||
|
|
||||||
- Fixed duplicate compression in dispatch. #449
|
|
||||||
|
|
||||||
|
|
||||||
## [v3.3.1] - 2020-08-31
|
|
||||||
|
|
||||||
- Added check on writer to prevent compression of response twice. #447
|
|
||||||
|
|
||||||
## [v3.3.0] - 2020-08-19
|
|
||||||
|
|
||||||
- Enable content encoding on Handle and ServeHTTP (#446)
|
|
||||||
- List available representations in 406 body (#437)
|
|
||||||
- Convert to string using rune() (#443)
|
|
||||||
|
|
||||||
## [v3.2.0] - 2020-06-21
|
|
||||||
|
|
||||||
- 405 Method Not Allowed must have Allow header (#436) (thx Bracken <abdawson@gmail.com>)
|
|
||||||
- add field allowedMethodsWithoutContentType (#424)
|
|
||||||
|
|
||||||
## [v3.1.0]
|
|
||||||
|
|
||||||
- support describing response headers (#426)
|
|
||||||
- fix openapi examples (#425)
|
|
||||||
|
|
||||||
v3.0.0
|
|
||||||
|
|
||||||
- fix: use request/response resulting from filter chain
|
|
||||||
- add Go module
|
|
||||||
Module consumer should use github.com/emicklei/go-restful/v3 as import path
|
|
||||||
|
|
||||||
v2.10.0
|
|
||||||
|
|
||||||
- support for Custom Verbs (thanks Vinci Xu <277040271@qq.com>)
|
|
||||||
- fixed static example (thanks Arthur <yang_yapo@126.com>)
|
|
||||||
- simplify code (thanks Christian Muehlhaeuser <muesli@gmail.com>)
|
|
||||||
- added JWT HMAC with SHA-512 authentication code example (thanks Amim Knabben <amim.knabben@gmail.com>)
|
|
||||||
|
|
||||||
v2.9.6
|
|
||||||
|
|
||||||
- small optimization in filter code
|
|
||||||
|
|
||||||
v2.11.1
|
|
||||||
|
|
||||||
- fix WriteError return value (#415)
|
|
||||||
|
|
||||||
v2.11.0
|
|
||||||
|
|
||||||
- allow prefix and suffix in path variable expression (#414)
|
|
||||||
|
|
||||||
v2.9.6
|
|
||||||
|
|
||||||
- support google custome verb (#413)
|
|
||||||
|
|
||||||
v2.9.5
|
|
||||||
|
|
||||||
- fix panic in Response.WriteError if err == nil
|
|
||||||
|
|
||||||
v2.9.4
|
|
||||||
|
|
||||||
- fix issue #400 , parsing mime type quality
|
|
||||||
- Route Builder added option for contentEncodingEnabled (#398)
|
|
||||||
|
|
||||||
v2.9.3
|
|
||||||
|
|
||||||
- Avoid return of 415 Unsupported Media Type when request body is empty (#396)
|
|
||||||
|
|
||||||
v2.9.2
|
|
||||||
|
|
||||||
- Reduce allocations in per-request methods to improve performance (#395)
|
|
||||||
|
|
||||||
v2.9.1
|
|
||||||
|
|
||||||
- Fix issue with default responses and invalid status code 0. (#393)
|
|
||||||
|
|
||||||
v2.9.0
|
|
||||||
|
|
||||||
- add per Route content encoding setting (overrides container setting)
|
|
||||||
|
|
||||||
v2.8.0
|
|
||||||
|
|
||||||
- add Request.QueryParameters()
|
|
||||||
- add json-iterator (via build tag)
|
|
||||||
- disable vgo module (until log is moved)
|
|
||||||
|
|
||||||
v2.7.1
|
|
||||||
|
|
||||||
- add vgo module
|
|
||||||
|
|
||||||
v2.6.1
|
|
||||||
|
|
||||||
- add JSONNewDecoderFunc to allow custom JSON Decoder usage (go 1.10+)
|
|
||||||
|
|
||||||
v2.6.0
|
|
||||||
|
|
||||||
- Make JSR 311 routing and path param processing consistent
|
|
||||||
- Adding description to RouteBuilder.Reads()
|
|
||||||
- Update example for Swagger12 and OpenAPI
|
|
||||||
|
|
||||||
2017-09-13
|
|
||||||
|
|
||||||
- added route condition functions using `.If(func)` in route building.
|
|
||||||
|
|
||||||
2017-02-16
|
|
||||||
|
|
||||||
- solved issue #304, make operation names unique
|
|
||||||
|
|
||||||
2017-01-30
|
|
||||||
|
|
||||||
[IMPORTANT] For swagger users, change your import statement to:
|
|
||||||
swagger "github.com/emicklei/go-restful-swagger12"
|
|
||||||
|
|
||||||
- moved swagger 1.2 code to go-restful-swagger12
|
|
||||||
- created TAG 2.0.0
|
|
||||||
|
|
||||||
2017-01-27
|
|
||||||
|
|
||||||
- remove defer request body close
|
|
||||||
- expose Dispatch for testing filters and Routefunctions
|
|
||||||
- swagger response model cannot be array
|
|
||||||
- created TAG 1.0.0
|
|
||||||
|
|
||||||
2016-12-22
|
|
||||||
|
|
||||||
- (API change) Remove code related to caching request content. Removes SetCacheReadEntity(doCache bool)
|
|
||||||
|
|
||||||
2016-11-26
|
|
||||||
|
|
||||||
- Default change! now use CurlyRouter (was RouterJSR311)
|
|
||||||
- Default change! no more caching of request content
|
|
||||||
- Default change! do not recover from panics
|
|
||||||
|
|
||||||
2016-09-22
|
|
||||||
|
|
||||||
- fix the DefaultRequestContentType feature
|
|
||||||
|
|
||||||
2016-02-14
|
|
||||||
|
|
||||||
- take the qualify factor of the Accept header mediatype into account when deciding the contentype of the response
|
|
||||||
- add constructors for custom entity accessors for xml and json
|
|
||||||
|
|
||||||
2015-09-27
|
|
||||||
|
|
||||||
- rename new WriteStatusAnd... to WriteHeaderAnd... for consistency
|
|
||||||
|
|
||||||
2015-09-25
|
|
||||||
|
|
||||||
- fixed problem with changing Header after WriteHeader (issue 235)
|
|
||||||
|
|
||||||
2015-09-14
|
|
||||||
|
|
||||||
- changed behavior of WriteHeader (immediate write) and WriteEntity (no status write)
|
|
||||||
- added support for custom EntityReaderWriters.
|
|
||||||
|
|
||||||
2015-08-06
|
|
||||||
|
|
||||||
- add support for reading entities from compressed request content
|
|
||||||
- use sync.Pool for compressors of http response and request body
|
|
||||||
- add Description to Parameter for documentation in Swagger UI
|
|
||||||
|
|
||||||
2015-03-20
|
|
||||||
|
|
||||||
- add configurable logging
|
|
||||||
|
|
||||||
2015-03-18
|
|
||||||
|
|
||||||
- if not specified, the Operation is derived from the Route function
|
|
||||||
|
|
||||||
2015-03-17
|
|
||||||
|
|
||||||
- expose Parameter creation functions
|
|
||||||
- make trace logger an interface
|
|
||||||
- fix OPTIONSFilter
|
|
||||||
- customize rendering of ServiceError
|
|
||||||
- JSR311 router now handles wildcards
|
|
||||||
- add Notes to Route
|
|
||||||
|
|
||||||
2014-11-27
|
|
||||||
|
|
||||||
- (api add) PrettyPrint per response. (as proposed in #167)
|
|
||||||
|
|
||||||
2014-11-12
|
|
||||||
|
|
||||||
- (api add) ApiVersion(.) for documentation in Swagger UI
|
|
||||||
|
|
||||||
2014-11-10
|
|
||||||
|
|
||||||
- (api change) struct fields tagged with "description" show up in Swagger UI
|
|
||||||
|
|
||||||
2014-10-31
|
|
||||||
|
|
||||||
- (api change) ReturnsError -> Returns
|
|
||||||
- (api add) RouteBuilder.Do(aBuilder) for DRY use of RouteBuilder
|
|
||||||
- fix swagger nested structs
|
|
||||||
- sort Swagger response messages by code
|
|
||||||
|
|
||||||
2014-10-23
|
|
||||||
|
|
||||||
- (api add) ReturnsError allows you to document Http codes in swagger
|
|
||||||
- fixed problem with greedy CurlyRouter
|
|
||||||
- (api add) Access-Control-Max-Age in CORS
|
|
||||||
- add tracing functionality (injectable) for debugging purposes
|
|
||||||
- support JSON parse 64bit int
|
|
||||||
- fix empty parameters for swagger
|
|
||||||
- WebServicesUrl is now optional for swagger
|
|
||||||
- fixed duplicate AccessControlAllowOrigin in CORS
|
|
||||||
- (api change) expose ServeMux in container
|
|
||||||
- (api add) added AllowedDomains in CORS
|
|
||||||
- (api add) ParameterNamed for detailed documentation
|
|
||||||
|
|
||||||
2014-04-16
|
|
||||||
|
|
||||||
- (api add) expose constructor of Request for testing.
|
|
||||||
|
|
||||||
2014-06-27
|
|
||||||
|
|
||||||
- (api add) ParameterNamed gives access to a Parameter definition and its data (for further specification).
|
|
||||||
- (api add) SetCacheReadEntity allow scontrol over whether or not the request body is being cached (default true for compatibility reasons).
|
|
||||||
|
|
||||||
2014-07-03
|
|
||||||
|
|
||||||
- (api add) CORS can be configured with a list of allowed domains
|
|
||||||
|
|
||||||
2014-03-12
|
|
||||||
|
|
||||||
- (api add) Route path parameters can use wildcard or regular expressions. (requires CurlyRouter)
|
|
||||||
|
|
||||||
2014-02-26
|
|
||||||
|
|
||||||
- (api add) Request now provides information about the matched Route, see method SelectedRoutePath
|
|
||||||
|
|
||||||
2014-02-17
|
|
||||||
|
|
||||||
- (api change) renamed parameter constants (go-lint checks)
|
|
||||||
|
|
||||||
2014-01-10
|
|
||||||
|
|
||||||
- (api add) support for CloseNotify, see http://golang.org/pkg/net/http/#CloseNotifier
|
|
||||||
|
|
||||||
2014-01-07
|
|
||||||
|
|
||||||
- (api change) Write* methods in Response now return the error or nil.
|
|
||||||
- added example of serving HTML from a Go template.
|
|
||||||
- fixed comparing Allowed headers in CORS (is now case-insensitive)
|
|
||||||
|
|
||||||
2013-11-13
|
|
||||||
|
|
||||||
- (api add) Response knows how many bytes are written to the response body.
|
|
||||||
|
|
||||||
2013-10-29
|
|
||||||
|
|
||||||
- (api add) RecoverHandler(handler RecoverHandleFunction) to change how panic recovery is handled. Default behavior is to log and return a stacktrace. This may be a security issue as it exposes sourcecode information.
|
|
||||||
|
|
||||||
2013-10-04
|
|
||||||
|
|
||||||
- (api add) Response knows what HTTP status has been written
|
|
||||||
- (api add) Request can have attributes (map of string->interface, also called request-scoped variables
|
|
||||||
|
|
||||||
2013-09-12
|
|
||||||
|
|
||||||
- (api change) Router interface simplified
|
|
||||||
- Implemented CurlyRouter, a Router that does not use|allow regular expressions in paths
|
|
||||||
|
|
||||||
2013-08-05
|
|
||||||
- add OPTIONS support
|
|
||||||
- add CORS support
|
|
||||||
|
|
||||||
2013-08-27
|
|
||||||
|
|
||||||
- fixed some reported issues (see github)
|
|
||||||
- (api change) deprecated use of WriteError; use WriteErrorString instead
|
|
||||||
|
|
||||||
2014-04-15
|
|
||||||
|
|
||||||
- (fix) v1.0.1 tag: fix Issue 111: WriteErrorString
|
|
||||||
|
|
||||||
2013-08-08
|
|
||||||
|
|
||||||
- (api add) Added implementation Container: a WebServices collection with its own http.ServeMux allowing multiple endpoints per program. Existing uses of go-restful will register their services to the DefaultContainer.
|
|
||||||
- (api add) the swagger package has be extended to have a UI per container.
|
|
||||||
- if panic is detected then a small stack trace is printed (thanks to runner-mei)
|
|
||||||
- (api add) WriteErrorString to Response
|
|
||||||
|
|
||||||
Important API changes:
|
|
||||||
|
|
||||||
- (api remove) package variable DoNotRecover no longer works ; use restful.DefaultContainer.DoNotRecover(true) instead.
|
|
||||||
- (api remove) package variable EnableContentEncoding no longer works ; use restful.DefaultContainer.EnableContentEncoding(true) instead.
|
|
||||||
|
|
||||||
|
|
||||||
2013-07-06
|
|
||||||
|
|
||||||
- (api add) Added support for response encoding (gzip and deflate(zlib)). This feature is disabled on default (for backwards compatibility). Use restful.EnableContentEncoding = true in your initialization to enable this feature.
|
|
||||||
|
|
||||||
2013-06-19
|
|
||||||
|
|
||||||
- (improve) DoNotRecover option, moved request body closer, improved ReadEntity
|
|
||||||
|
|
||||||
2013-06-03
|
|
||||||
|
|
||||||
- (api change) removed Dispatcher interface, hide PathExpression
|
|
||||||
- changed receiver names of type functions to be more idiomatic Go
|
|
||||||
|
|
||||||
2013-06-02
|
|
||||||
|
|
||||||
- (optimize) Cache the RegExp compilation of Paths.
|
|
||||||
|
|
||||||
2013-05-22
|
|
||||||
|
|
||||||
- (api add) Added support for request/response filter functions
|
|
||||||
|
|
||||||
2013-05-18
|
|
||||||
|
|
||||||
|
|
||||||
- (api add) Added feature to change the default Http Request Dispatch function (travis cline)
|
|
||||||
- (api change) Moved Swagger Webservice to swagger package (see example restful-user)
|
|
||||||
|
|
||||||
[2012-11-14 .. 2013-05-18>
|
|
||||||
|
|
||||||
- See https://github.com/emicklei/go-restful/commits
|
|
||||||
|
|
||||||
2012-11-14
|
|
||||||
|
|
||||||
- Initial commit
|
|
||||||
|
|
||||||
|
|
||||||
22
vendor/github.com/emicklei/go-restful/v3/LICENSE
generated
vendored
22
vendor/github.com/emicklei/go-restful/v3/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2012,2013 Ernest Micklei
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
8
vendor/github.com/emicklei/go-restful/v3/Makefile
generated
vendored
8
vendor/github.com/emicklei/go-restful/v3/Makefile
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
all: test
|
|
||||||
|
|
||||||
test:
|
|
||||||
go vet .
|
|
||||||
go test -cover -v .
|
|
||||||
|
|
||||||
ex:
|
|
||||||
find ./examples -type f -name "*.go" | xargs -I {} go build -o /tmp/ignore {}
|
|
||||||
110
vendor/github.com/emicklei/go-restful/v3/README.md
generated
vendored
110
vendor/github.com/emicklei/go-restful/v3/README.md
generated
vendored
@@ -1,110 +0,0 @@
|
|||||||
go-restful
|
|
||||||
==========
|
|
||||||
package for building REST-style Web Services using Google Go
|
|
||||||
|
|
||||||
[](https://goreportcard.com/report/github.com/emicklei/go-restful)
|
|
||||||
[](https://pkg.go.dev/github.com/emicklei/go-restful)
|
|
||||||
[](https://codecov.io/gh/emicklei/go-restful)
|
|
||||||
|
|
||||||
- [Code examples use v3](https://github.com/emicklei/go-restful/tree/v3/examples)
|
|
||||||
|
|
||||||
REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping:
|
|
||||||
|
|
||||||
- GET = Retrieve a representation of a resource
|
|
||||||
- POST = Create if you are sending content to the server to create a subordinate of the specified resource collection, using some server-side algorithm.
|
|
||||||
- PUT = Create if you are sending the full content of the specified resource (URI).
|
|
||||||
- PUT = Update if you are updating the full content of the specified resource.
|
|
||||||
- DELETE = Delete if you are requesting the server to delete the resource
|
|
||||||
- PATCH = Update partial content of a resource
|
|
||||||
- OPTIONS = Get information about the communication options for the request URI
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
#### Without Go Modules
|
|
||||||
|
|
||||||
All versions up to `v2.*.*` (on the master) are not supporting Go modules.
|
|
||||||
|
|
||||||
```
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Using Go Modules
|
|
||||||
|
|
||||||
As of version `v3.0.0` (on the v3 branch), this package supports Go modules.
|
|
||||||
|
|
||||||
```
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful/v3"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```Go
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser).
|
|
||||||
Doc("get a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Writes(User{}))
|
|
||||||
...
|
|
||||||
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[Full API of a UserResource](https://github.com/emicklei/go-restful/blob/v3/examples/user-resource/restful-user-resource.go)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Routes for request → function mapping with path parameter (e.g. {id} but also prefix_{var} and {var}_suffix) support
|
|
||||||
- Configurable router:
|
|
||||||
- (default) Fast routing algorithm that allows static elements, [google custom method](https://cloud.google.com/apis/design/custom_methods), regular expressions and dynamic parameters in the URL path (e.g. /resource/name:customVerb, /meetings/{id} or /static/{subpath:*})
|
|
||||||
- Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but does **not** accept) regular expressions
|
|
||||||
- Request API for reading structs from JSON/XML and accessing parameters (path,query,header)
|
|
||||||
- Response API for writing structs to JSON/XML and setting headers
|
|
||||||
- Customizable encoding using EntityReaderWriter registration
|
|
||||||
- Filters for intercepting the request → response flow on Service or Route level
|
|
||||||
- Request-scoped variables using attributes
|
|
||||||
- Containers for WebServices on different HTTP endpoints
|
|
||||||
- Content encoding (gzip,deflate) of request and response payloads
|
|
||||||
- Automatic responses on OPTIONS (using a filter)
|
|
||||||
- Automatic CORS request handling (using a filter)
|
|
||||||
- API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi))
|
|
||||||
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
|
|
||||||
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
|
|
||||||
- Configurable (trace) logging
|
|
||||||
- Customizable gzip/deflate readers and writers using CompressorProvider registration
|
|
||||||
- Inject your own http.Handler using the `HttpMiddlewareHandlerToFilter` function
|
|
||||||
|
|
||||||
## How to customize
|
|
||||||
There are several hooks to customize the behavior of the go-restful package.
|
|
||||||
|
|
||||||
- Router algorithm
|
|
||||||
- Panic recovery
|
|
||||||
- JSON decoder
|
|
||||||
- Trace logging
|
|
||||||
- Compression
|
|
||||||
- Encoders for other serializers
|
|
||||||
- Use the package variable `TrimRightSlashEnabled` (default true) to control the behavior of matching routes that end with a slash `/`
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Example programs](./examples)
|
|
||||||
- [Example posted on blog](http://ernestmicklei.com/2012/11/go-restful-first-working-example/)
|
|
||||||
- [Design explained on blog](http://ernestmicklei.com/2012/11/go-restful-api-design/)
|
|
||||||
- [sourcegraph](https://sourcegraph.com/github.com/emicklei/go-restful)
|
|
||||||
- [showcase: Zazkia - tcp proxy for testing resiliency](https://github.com/emicklei/zazkia)
|
|
||||||
- [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora)
|
|
||||||
|
|
||||||
Type ```git shortlog -s``` for a full list of contributors.
|
|
||||||
|
|
||||||
© 2012 - 2023, http://ernestmicklei.com. MIT License. Contributions are welcome.
|
|
||||||
13
vendor/github.com/emicklei/go-restful/v3/SECURITY.md
generated
vendored
13
vendor/github.com/emicklei/go-restful/v3/SECURITY.md
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
| Version | Supported |
|
|
||||||
| ------- | ------------------ |
|
|
||||||
| v3.7.x | :white_check_mark: |
|
|
||||||
| < v3.0.1 | :x: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Create an Issue and put the label `[security]` in the title of the issue.
|
|
||||||
Valid reported security issues are expected to be solved within a week.
|
|
||||||
1
vendor/github.com/emicklei/go-restful/v3/Srcfile
generated
vendored
1
vendor/github.com/emicklei/go-restful/v3/Srcfile
generated
vendored
@@ -1 +0,0 @@
|
|||||||
{"SkipDirs": ["examples"]}
|
|
||||||
10
vendor/github.com/emicklei/go-restful/v3/bench_test.sh
generated
vendored
10
vendor/github.com/emicklei/go-restful/v3/bench_test.sh
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
#go test -run=none -file bench_test.go -test.bench . -cpuprofile=bench_test.out
|
|
||||||
|
|
||||||
go test -c
|
|
||||||
./go-restful.test -test.run=none -test.cpuprofile=tmp.prof -test.bench=BenchmarkMany
|
|
||||||
./go-restful.test -test.run=none -test.cpuprofile=curly.prof -test.bench=BenchmarkManyCurly
|
|
||||||
|
|
||||||
#go tool pprof go-restful.test tmp.prof
|
|
||||||
go tool pprof go-restful.test curly.prof
|
|
||||||
|
|
||||||
|
|
||||||
137
vendor/github.com/emicklei/go-restful/v3/compress.go
generated
vendored
137
vendor/github.com/emicklei/go-restful/v3/compress.go
generated
vendored
@@ -1,137 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OBSOLETE : use restful.DefaultContainer.EnableContentEncoding(true) to change this setting.
|
|
||||||
var EnableContentEncoding = false
|
|
||||||
|
|
||||||
// CompressingResponseWriter is a http.ResponseWriter that can perform content encoding (gzip and zlib)
|
|
||||||
type CompressingResponseWriter struct {
|
|
||||||
writer http.ResponseWriter
|
|
||||||
compressor io.WriteCloser
|
|
||||||
encoding string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header is part of http.ResponseWriter interface
|
|
||||||
func (c *CompressingResponseWriter) Header() http.Header {
|
|
||||||
return c.writer.Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader is part of http.ResponseWriter interface
|
|
||||||
func (c *CompressingResponseWriter) WriteHeader(status int) {
|
|
||||||
c.writer.WriteHeader(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is part of http.ResponseWriter interface
|
|
||||||
// It is passed through the compressor
|
|
||||||
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
|
|
||||||
if c.isCompressorClosed() {
|
|
||||||
return -1, errors.New("Compressing error: tried to write data using closed compressor")
|
|
||||||
}
|
|
||||||
return c.compressor.Write(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseNotify is part of http.CloseNotifier interface
|
|
||||||
func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
|
|
||||||
return c.writer.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush is part of http.Flusher interface. Noop if the underlying writer doesn't support it.
|
|
||||||
func (c *CompressingResponseWriter) Flush() {
|
|
||||||
flusher, ok := c.writer.(http.Flusher)
|
|
||||||
if !ok {
|
|
||||||
// writer doesn't support http.Flusher interface
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the underlying compressor
|
|
||||||
func (c *CompressingResponseWriter) Close() error {
|
|
||||||
if c.isCompressorClosed() {
|
|
||||||
return errors.New("Compressing error: tried to close already closed compressor")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.compressor.Close()
|
|
||||||
if ENCODING_GZIP == c.encoding {
|
|
||||||
currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer))
|
|
||||||
}
|
|
||||||
if ENCODING_DEFLATE == c.encoding {
|
|
||||||
currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer))
|
|
||||||
}
|
|
||||||
// gc hint needed?
|
|
||||||
c.compressor = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompressingResponseWriter) isCompressorClosed() bool {
|
|
||||||
return nil == c.compressor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hijack implements the Hijacker interface
|
|
||||||
// This is especially useful when combining Container.EnabledContentEncoding
|
|
||||||
// in combination with websockets (for instance gorilla/websocket)
|
|
||||||
func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
hijacker, ok := c.writer.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface")
|
|
||||||
}
|
|
||||||
return hijacker.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
|
|
||||||
// It also inspects the httpWriter whether its content-encoding is already set (non-empty).
|
|
||||||
func wantsCompressedResponse(httpRequest *http.Request, httpWriter http.ResponseWriter) (bool, string) {
|
|
||||||
if contentEncoding := httpWriter.Header().Get(HEADER_ContentEncoding); contentEncoding != "" {
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
header := httpRequest.Header.Get(HEADER_AcceptEncoding)
|
|
||||||
gi := strings.Index(header, ENCODING_GZIP)
|
|
||||||
zi := strings.Index(header, ENCODING_DEFLATE)
|
|
||||||
// use in order of appearance
|
|
||||||
if gi == -1 {
|
|
||||||
return zi != -1, ENCODING_DEFLATE
|
|
||||||
} else if zi == -1 {
|
|
||||||
return gi != -1, ENCODING_GZIP
|
|
||||||
} else {
|
|
||||||
if gi < zi {
|
|
||||||
return true, ENCODING_GZIP
|
|
||||||
}
|
|
||||||
return true, ENCODING_DEFLATE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCompressingResponseWriter create a CompressingResponseWriter for a known encoding = {gzip,deflate}
|
|
||||||
func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding string) (*CompressingResponseWriter, error) {
|
|
||||||
httpWriter.Header().Set(HEADER_ContentEncoding, encoding)
|
|
||||||
c := new(CompressingResponseWriter)
|
|
||||||
c.writer = httpWriter
|
|
||||||
var err error
|
|
||||||
if ENCODING_GZIP == encoding {
|
|
||||||
w := currentCompressorProvider.AcquireGzipWriter()
|
|
||||||
w.Reset(httpWriter)
|
|
||||||
c.compressor = w
|
|
||||||
c.encoding = ENCODING_GZIP
|
|
||||||
} else if ENCODING_DEFLATE == encoding {
|
|
||||||
w := currentCompressorProvider.AcquireZlibWriter()
|
|
||||||
w.Reset(httpWriter)
|
|
||||||
c.compressor = w
|
|
||||||
c.encoding = ENCODING_DEFLATE
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Unknown encoding:" + encoding)
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
103
vendor/github.com/emicklei/go-restful/v3/compressor_cache.go
generated
vendored
103
vendor/github.com/emicklei/go-restful/v3/compressor_cache.go
generated
vendored
@@ -1,103 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BoundedCachedCompressors is a CompressorProvider that uses a cache with a fixed amount
|
|
||||||
// of writers and readers (resources).
|
|
||||||
// If a new resource is acquired and all are in use, it will return a new unmanaged resource.
|
|
||||||
type BoundedCachedCompressors struct {
|
|
||||||
gzipWriters chan *gzip.Writer
|
|
||||||
gzipReaders chan *gzip.Reader
|
|
||||||
zlibWriters chan *zlib.Writer
|
|
||||||
writersCapacity int
|
|
||||||
readersCapacity int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoundedCachedCompressors returns a new, with filled cache, BoundedCachedCompressors.
|
|
||||||
func NewBoundedCachedCompressors(writersCapacity, readersCapacity int) *BoundedCachedCompressors {
|
|
||||||
b := &BoundedCachedCompressors{
|
|
||||||
gzipWriters: make(chan *gzip.Writer, writersCapacity),
|
|
||||||
gzipReaders: make(chan *gzip.Reader, readersCapacity),
|
|
||||||
zlibWriters: make(chan *zlib.Writer, writersCapacity),
|
|
||||||
writersCapacity: writersCapacity,
|
|
||||||
readersCapacity: readersCapacity,
|
|
||||||
}
|
|
||||||
for ix := 0; ix < writersCapacity; ix++ {
|
|
||||||
b.gzipWriters <- newGzipWriter()
|
|
||||||
b.zlibWriters <- newZlibWriter()
|
|
||||||
}
|
|
||||||
for ix := 0; ix < readersCapacity; ix++ {
|
|
||||||
b.gzipReaders <- newGzipReader()
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireGzipWriter returns an resettable *gzip.Writer. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireGzipWriter() *gzip.Writer {
|
|
||||||
var writer *gzip.Writer
|
|
||||||
select {
|
|
||||||
case writer, _ = <-b.gzipWriters:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
writer = newGzipWriter()
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzipWriter accepts a writer (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseGzipWriter(w *gzip.Writer) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.gzipWriters) < b.writersCapacity {
|
|
||||||
b.gzipWriters <- w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireGzipReader returns a *gzip.Reader. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireGzipReader() *gzip.Reader {
|
|
||||||
var reader *gzip.Reader
|
|
||||||
select {
|
|
||||||
case reader, _ = <-b.gzipReaders:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
reader = newGzipReader()
|
|
||||||
}
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzipReader accepts a reader (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseGzipReader(r *gzip.Reader) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.gzipReaders) < b.readersCapacity {
|
|
||||||
b.gzipReaders <- r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireZlibWriter returns an resettable *zlib.Writer. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireZlibWriter() *zlib.Writer {
|
|
||||||
var writer *zlib.Writer
|
|
||||||
select {
|
|
||||||
case writer, _ = <-b.zlibWriters:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
writer = newZlibWriter()
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseZlibWriter accepts a writer (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseZlibWriter(w *zlib.Writer) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.zlibWriters) < b.writersCapacity {
|
|
||||||
b.zlibWriters <- w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
91
vendor/github.com/emicklei/go-restful/v3/compressor_pools.go
generated
vendored
91
vendor/github.com/emicklei/go-restful/v3/compressor_pools.go
generated
vendored
@@ -1,91 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
|
|
||||||
type SyncPoolCompessors struct {
|
|
||||||
GzipWriterPool *sync.Pool
|
|
||||||
GzipReaderPool *sync.Pool
|
|
||||||
ZlibWriterPool *sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSyncPoolCompessors returns a new ("empty") SyncPoolCompessors.
|
|
||||||
func NewSyncPoolCompessors() *SyncPoolCompessors {
|
|
||||||
return &SyncPoolCompessors{
|
|
||||||
GzipWriterPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newGzipWriter() },
|
|
||||||
},
|
|
||||||
GzipReaderPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newGzipReader() },
|
|
||||||
},
|
|
||||||
ZlibWriterPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newZlibWriter() },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireGzipWriter() *gzip.Writer {
|
|
||||||
return s.GzipWriterPool.Get().(*gzip.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseGzipWriter(w *gzip.Writer) {
|
|
||||||
s.GzipWriterPool.Put(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireGzipReader() *gzip.Reader {
|
|
||||||
return s.GzipReaderPool.Get().(*gzip.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseGzipReader(r *gzip.Reader) {
|
|
||||||
s.GzipReaderPool.Put(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireZlibWriter() *zlib.Writer {
|
|
||||||
return s.ZlibWriterPool.Get().(*zlib.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseZlibWriter(w *zlib.Writer) {
|
|
||||||
s.ZlibWriterPool.Put(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGzipWriter() *gzip.Writer {
|
|
||||||
// create with an empty bytes writer; it will be replaced before using the gzipWriter
|
|
||||||
writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGzipReader() *gzip.Reader {
|
|
||||||
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
|
|
||||||
// we can safely use currentCompressProvider because it is set on package initialization.
|
|
||||||
w := currentCompressorProvider.AcquireGzipWriter()
|
|
||||||
defer currentCompressorProvider.ReleaseGzipWriter(w)
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
w.Reset(b)
|
|
||||||
w.Flush()
|
|
||||||
w.Close()
|
|
||||||
reader, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func newZlibWriter() *zlib.Writer {
|
|
||||||
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
54
vendor/github.com/emicklei/go-restful/v3/compressors.go
generated
vendored
54
vendor/github.com/emicklei/go-restful/v3/compressors.go
generated
vendored
@@ -1,54 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompressorProvider describes a component that can provider compressors for the std methods.
|
|
||||||
type CompressorProvider interface {
|
|
||||||
// Returns a *gzip.Writer which needs to be released later.
|
|
||||||
// Before using it, call Reset().
|
|
||||||
AcquireGzipWriter() *gzip.Writer
|
|
||||||
|
|
||||||
// Releases an acquired *gzip.Writer.
|
|
||||||
ReleaseGzipWriter(w *gzip.Writer)
|
|
||||||
|
|
||||||
// Returns a *gzip.Reader which needs to be released later.
|
|
||||||
AcquireGzipReader() *gzip.Reader
|
|
||||||
|
|
||||||
// Releases an acquired *gzip.Reader.
|
|
||||||
ReleaseGzipReader(w *gzip.Reader)
|
|
||||||
|
|
||||||
// Returns a *zlib.Writer which needs to be released later.
|
|
||||||
// Before using it, call Reset().
|
|
||||||
AcquireZlibWriter() *zlib.Writer
|
|
||||||
|
|
||||||
// Releases an acquired *zlib.Writer.
|
|
||||||
ReleaseZlibWriter(w *zlib.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultCompressorProvider is the actual provider of compressors (zlib or gzip).
|
|
||||||
var currentCompressorProvider CompressorProvider
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
currentCompressorProvider = NewSyncPoolCompessors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentCompressorProvider returns the current CompressorProvider.
|
|
||||||
// It is initialized using a SyncPoolCompessors.
|
|
||||||
func CurrentCompressorProvider() CompressorProvider {
|
|
||||||
return currentCompressorProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCompressorProvider sets the actual provider of compressors (zlib or gzip).
|
|
||||||
func SetCompressorProvider(p CompressorProvider) {
|
|
||||||
if p == nil {
|
|
||||||
panic("cannot set compressor provider to nil")
|
|
||||||
}
|
|
||||||
currentCompressorProvider = p
|
|
||||||
}
|
|
||||||
32
vendor/github.com/emicklei/go-restful/v3/constants.go
generated
vendored
32
vendor/github.com/emicklei/go-restful/v3/constants.go
generated
vendored
@@ -1,32 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
const (
|
|
||||||
MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_ZIP = "application/zip" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_OCTET = "application/octet-stream" // If Content-Type is not present in request, use the default
|
|
||||||
|
|
||||||
HEADER_Allow = "Allow"
|
|
||||||
HEADER_Accept = "Accept"
|
|
||||||
HEADER_Origin = "Origin"
|
|
||||||
HEADER_ContentType = "Content-Type"
|
|
||||||
HEADER_ContentDisposition = "Content-Disposition"
|
|
||||||
HEADER_LastModified = "Last-Modified"
|
|
||||||
HEADER_AcceptEncoding = "Accept-Encoding"
|
|
||||||
HEADER_ContentEncoding = "Content-Encoding"
|
|
||||||
HEADER_AccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
|
||||||
HEADER_AccessControlRequestMethod = "Access-Control-Request-Method"
|
|
||||||
HEADER_AccessControlRequestHeaders = "Access-Control-Request-Headers"
|
|
||||||
HEADER_AccessControlAllowMethods = "Access-Control-Allow-Methods"
|
|
||||||
HEADER_AccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
|
||||||
HEADER_AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
|
||||||
HEADER_AccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
|
||||||
HEADER_AccessControlMaxAge = "Access-Control-Max-Age"
|
|
||||||
|
|
||||||
ENCODING_GZIP = "gzip"
|
|
||||||
ENCODING_DEFLATE = "deflate"
|
|
||||||
)
|
|
||||||
450
vendor/github.com/emicklei/go-restful/v3/container.go
generated
vendored
450
vendor/github.com/emicklei/go-restful/v3/container.go
generated
vendored
@@ -1,450 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/v3/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
|
|
||||||
// The requests are further dispatched to routes of WebServices using a RouteSelector
|
|
||||||
type Container struct {
|
|
||||||
webServicesLock sync.RWMutex
|
|
||||||
webServices []*WebService
|
|
||||||
ServeMux *http.ServeMux
|
|
||||||
isRegisteredOnRoot bool
|
|
||||||
containerFilters []FilterFunction
|
|
||||||
doNotRecover bool // default is true
|
|
||||||
recoverHandleFunc RecoverHandleFunction
|
|
||||||
serviceErrorHandleFunc ServiceErrorHandleFunction
|
|
||||||
router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
|
|
||||||
contentEncodingEnabled bool // default is false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
|
|
||||||
func NewContainer() *Container {
|
|
||||||
return &Container{
|
|
||||||
webServices: []*WebService{},
|
|
||||||
ServeMux: http.NewServeMux(),
|
|
||||||
isRegisteredOnRoot: false,
|
|
||||||
containerFilters: []FilterFunction{},
|
|
||||||
doNotRecover: true,
|
|
||||||
recoverHandleFunc: logStackOnRecover,
|
|
||||||
serviceErrorHandleFunc: writeServiceError,
|
|
||||||
router: CurlyRouter{},
|
|
||||||
contentEncodingEnabled: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverHandleFunction declares functions that can be used to handle a panic situation.
|
|
||||||
// The first argument is what recover() returns. The second must be used to communicate an error response.
|
|
||||||
type RecoverHandleFunction func(interface{}, http.ResponseWriter)
|
|
||||||
|
|
||||||
// RecoverHandler changes the default function (logStackOnRecover) to be called
|
|
||||||
// when a panic is detected. DoNotRecover must be have its default value (=false).
|
|
||||||
func (c *Container) RecoverHandler(handler RecoverHandleFunction) {
|
|
||||||
c.recoverHandleFunc = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceErrorHandleFunction declares functions that can be used to handle a service error situation.
|
|
||||||
// The first argument is the service error, the second is the request that resulted in the error and
|
|
||||||
// the third must be used to communicate an error response.
|
|
||||||
type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
|
|
||||||
|
|
||||||
// ServiceErrorHandler changes the default function (writeServiceError) to be called
|
|
||||||
// when a ServiceError is detected.
|
|
||||||
func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
|
|
||||||
c.serviceErrorHandleFunc = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoNotRecover controls whether panics will be caught to return HTTP 500.
|
|
||||||
// If set to true, Route functions are responsible for handling any error situation.
|
|
||||||
// Default value is true.
|
|
||||||
func (c *Container) DoNotRecover(doNot bool) {
|
|
||||||
c.doNotRecover = doNot
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router changes the default Router (currently CurlyRouter)
|
|
||||||
func (c *Container) Router(aRouter RouteSelector) {
|
|
||||||
c.router = aRouter
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
|
|
||||||
func (c *Container) EnableContentEncoding(enabled bool) {
|
|
||||||
c.contentEncodingEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
|
|
||||||
func (c *Container) Add(service *WebService) *Container {
|
|
||||||
c.webServicesLock.Lock()
|
|
||||||
defer c.webServicesLock.Unlock()
|
|
||||||
|
|
||||||
// if rootPath was not set then lazy initialize it
|
|
||||||
if len(service.rootPath) == 0 {
|
|
||||||
service.Path("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cannot have duplicate root paths
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.RootPath() == service.RootPath() {
|
|
||||||
log.Printf("WebService with duplicate root path detected:['%v']", each)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not registered on root then add specific mapping
|
|
||||||
if !c.isRegisteredOnRoot {
|
|
||||||
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
|
|
||||||
}
|
|
||||||
c.webServices = append(c.webServices, service)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// addHandler may set a new HandleFunc for the serveMux
|
|
||||||
// this function must run inside the critical region protected by the webServicesLock.
|
|
||||||
// returns true if the function was registered on root ("/")
|
|
||||||
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
|
|
||||||
pattern := fixedPrefixPath(service.RootPath())
|
|
||||||
// check if root path registration is needed
|
|
||||||
if "/" == pattern || "" == pattern {
|
|
||||||
serveMux.HandleFunc("/", c.dispatch)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// detect if registration already exists
|
|
||||||
alreadyMapped := false
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.RootPath() == service.RootPath() {
|
|
||||||
alreadyMapped = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !alreadyMapped {
|
|
||||||
serveMux.HandleFunc(pattern, c.dispatch)
|
|
||||||
if !strings.HasSuffix(pattern, "/") {
|
|
||||||
serveMux.HandleFunc(pattern+"/", c.dispatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Remove(ws *WebService) error {
|
|
||||||
if c.ServeMux == http.DefaultServeMux {
|
|
||||||
errMsg := fmt.Sprintf("cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws)
|
|
||||||
log.Print(errMsg)
|
|
||||||
return errors.New(errMsg)
|
|
||||||
}
|
|
||||||
c.webServicesLock.Lock()
|
|
||||||
defer c.webServicesLock.Unlock()
|
|
||||||
// build a new ServeMux and re-register all WebServices
|
|
||||||
newServeMux := http.NewServeMux()
|
|
||||||
newServices := []*WebService{}
|
|
||||||
newIsRegisteredOnRoot := false
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.rootPath != ws.rootPath {
|
|
||||||
// If not registered on root then add specific mapping
|
|
||||||
if !newIsRegisteredOnRoot {
|
|
||||||
newIsRegisteredOnRoot = c.addHandler(each, newServeMux)
|
|
||||||
}
|
|
||||||
newServices = append(newServices, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logStackOnRecover is the default RecoverHandleFunction and is called
|
|
||||||
// when DoNotRecover is false and the recoverHandleFunc is not set for the container.
|
|
||||||
// Default implementation logs the stacktrace and writes the stacktrace on the response.
|
|
||||||
// This may be a security issue as it exposes sourcecode information.
|
|
||||||
func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
|
|
||||||
for i := 2; ; i += 1 {
|
|
||||||
_, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
|
||||||
}
|
|
||||||
log.Print(buffer.String())
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
httpWriter.Write(buffer.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeServiceError is the default ServiceErrorHandleFunction and is called
|
|
||||||
// when a ServiceError is returned during route selection. Default implementation
|
|
||||||
// calls resp.WriteErrorString(err.Code, err.Message)
|
|
||||||
func writeServiceError(err ServiceError, req *Request, resp *Response) {
|
|
||||||
for header, values := range err.Header {
|
|
||||||
for _, value := range values {
|
|
||||||
resp.Header().Add(header, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.WriteErrorString(err.Code, err.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the incoming Http Request to a matching WebService.
|
|
||||||
func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
if httpWriter == nil {
|
|
||||||
panic("httpWriter cannot be nil")
|
|
||||||
}
|
|
||||||
if httpRequest == nil {
|
|
||||||
panic("httpRequest cannot be nil")
|
|
||||||
}
|
|
||||||
c.dispatch(httpWriter, httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the incoming Http Request to a matching WebService.
|
|
||||||
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
// so we can assign a compressing one later
|
|
||||||
writer := httpWriter
|
|
||||||
|
|
||||||
// CompressingResponseWriter should be closed after all operations are done
|
|
||||||
defer func() {
|
|
||||||
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
|
|
||||||
compressWriter.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Instal panic recovery unless told otherwise
|
|
||||||
if !c.doNotRecover { // catch all for 500 response
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
c.recoverHandleFunc(r, writer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find best match Route ; err is non nil if no match was found
|
|
||||||
var webService *WebService
|
|
||||||
var route *Route
|
|
||||||
var err error
|
|
||||||
func() {
|
|
||||||
c.webServicesLock.RLock()
|
|
||||||
defer c.webServicesLock.RUnlock()
|
|
||||||
webService, route, err = c.router.SelectRoute(
|
|
||||||
c.webServices,
|
|
||||||
httpRequest)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
// a non-200 response (may be compressed) has already been written
|
|
||||||
// run container filters anyway ; they should not touch the response...
|
|
||||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
switch err.(type) {
|
|
||||||
case ServiceError:
|
|
||||||
ser := err.(ServiceError)
|
|
||||||
c.serviceErrorHandleFunc(ser, req, resp)
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unless httpWriter is already an CompressingResponseWriter see if we need to install one
|
|
||||||
if _, isCompressing := httpWriter.(*CompressingResponseWriter); !isCompressing {
|
|
||||||
// Detect if compression is needed
|
|
||||||
// assume without compression, test for override
|
|
||||||
contentEncodingEnabled := c.contentEncodingEnabled
|
|
||||||
if route != nil && route.contentEncodingEnabled != nil {
|
|
||||||
contentEncodingEnabled = *route.contentEncodingEnabled
|
|
||||||
}
|
|
||||||
if contentEncodingEnabled {
|
|
||||||
doCompress, encoding := wantsCompressedResponse(httpRequest, httpWriter)
|
|
||||||
if doCompress {
|
|
||||||
var err error
|
|
||||||
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("unable to install compressor: ", err)
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pathProcessor, routerProcessesPath := c.router.(PathProcessor)
|
|
||||||
if !routerProcessesPath {
|
|
||||||
pathProcessor = defaultPathProcessor{}
|
|
||||||
}
|
|
||||||
pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
|
|
||||||
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
|
|
||||||
// pass through filters (if any)
|
|
||||||
if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size > 0 {
|
|
||||||
// compose filter chain
|
|
||||||
allFilters := make([]FilterFunction, 0, size)
|
|
||||||
allFilters = append(allFilters, c.containerFilters...)
|
|
||||||
allFilters = append(allFilters, webService.filters...)
|
|
||||||
allFilters = append(allFilters, route.Filters...)
|
|
||||||
chain := FilterChain{
|
|
||||||
Filters: allFilters,
|
|
||||||
Target: route.Function,
|
|
||||||
ParameterDocs: route.ParameterDocs,
|
|
||||||
Operation: route.Operation,
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(wrappedRequest, wrappedResponse)
|
|
||||||
} else {
|
|
||||||
// no filters, handle request by route
|
|
||||||
route.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
|
|
||||||
func fixedPrefixPath(pathspec string) string {
|
|
||||||
varBegin := strings.Index(pathspec, "{")
|
|
||||||
if -1 == varBegin {
|
|
||||||
return pathspec
|
|
||||||
}
|
|
||||||
return pathspec[:varBegin]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
|
|
||||||
func (c *Container) ServeHTTP(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
// Skip, if content encoding is disabled
|
|
||||||
if !c.contentEncodingEnabled {
|
|
||||||
c.ServeMux.ServeHTTP(httpWriter, httpRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// content encoding is enabled
|
|
||||||
|
|
||||||
// Skip, if httpWriter is already an CompressingResponseWriter
|
|
||||||
if _, ok := httpWriter.(*CompressingResponseWriter); ok {
|
|
||||||
c.ServeMux.ServeHTTP(httpWriter, httpRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writer := httpWriter
|
|
||||||
// CompressingResponseWriter should be closed after all operations are done
|
|
||||||
defer func() {
|
|
||||||
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
|
|
||||||
compressWriter.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
doCompress, encoding := wantsCompressedResponse(httpRequest, httpWriter)
|
|
||||||
if doCompress {
|
|
||||||
var err error
|
|
||||||
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("unable to install compressor: ", err)
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ServeMux.ServeHTTP(writer, httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
|
|
||||||
func (c *Container) Handle(pattern string, handler http.Handler) {
|
|
||||||
c.ServeMux.Handle(pattern, http.HandlerFunc(func(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
// Skip, if httpWriter is already an CompressingResponseWriter
|
|
||||||
if _, ok := httpWriter.(*CompressingResponseWriter); ok {
|
|
||||||
handler.ServeHTTP(httpWriter, httpRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writer := httpWriter
|
|
||||||
|
|
||||||
// CompressingResponseWriter should be closed after all operations are done
|
|
||||||
defer func() {
|
|
||||||
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
|
|
||||||
compressWriter.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if c.contentEncodingEnabled {
|
|
||||||
doCompress, encoding := wantsCompressedResponse(httpRequest, httpWriter)
|
|
||||||
if doCompress {
|
|
||||||
var err error
|
|
||||||
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("unable to install compressor: ", err)
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.ServeHTTP(writer, httpRequest)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleWithFilter registers the handler for the given pattern.
|
|
||||||
// Container's filter chain is applied for handler.
|
|
||||||
// If a handler already exists for pattern, HandleWithFilter panics.
|
|
||||||
func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
|
|
||||||
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
if len(c.containerFilters) == 0 {
|
|
||||||
handler.ServeHTTP(httpResponse, httpRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
handler.ServeHTTP(resp, req.Request)
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Handle(pattern, http.HandlerFunc(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a container FilterFunction. These are called before dispatching
|
|
||||||
// a http.Request to a WebService from the container
|
|
||||||
func (c *Container) Filter(filter FilterFunction) {
|
|
||||||
c.containerFilters = append(c.containerFilters, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisteredWebServices returns the collections of added WebServices
|
|
||||||
func (c *Container) RegisteredWebServices() []*WebService {
|
|
||||||
c.webServicesLock.RLock()
|
|
||||||
defer c.webServicesLock.RUnlock()
|
|
||||||
result := make([]*WebService, len(c.webServices))
|
|
||||||
for ix := range c.webServices {
|
|
||||||
result[ix] = c.webServices[ix]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeAllowedMethods returns a list of HTTP methods that are valid for a Request
|
|
||||||
func (c *Container) computeAllowedMethods(req *Request) []string {
|
|
||||||
// Go through all RegisteredWebServices() and all its Routes to collect the options
|
|
||||||
methods := []string{}
|
|
||||||
requestPath := req.Request.URL.Path
|
|
||||||
for _, ws := range c.RegisteredWebServices() {
|
|
||||||
matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
|
||||||
if matches != nil {
|
|
||||||
finalMatch := matches[len(matches)-1]
|
|
||||||
for _, rt := range ws.Routes() {
|
|
||||||
matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
|
|
||||||
if matches != nil {
|
|
||||||
lastMatch := matches[len(matches)-1]
|
|
||||||
if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
|
||||||
methods = append(methods, rt.Method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// methods = append(methods, "OPTIONS") not sure about this
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBasicRequestResponse creates a pair of Request,Response from its http versions.
|
|
||||||
// It is basic because no parameter or (produces) content-type information is given.
|
|
||||||
func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
|
|
||||||
resp := NewResponse(httpWriter)
|
|
||||||
resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
return NewRequest(httpRequest), resp
|
|
||||||
}
|
|
||||||
193
vendor/github.com/emicklei/go-restful/v3/cors_filter.go
generated
vendored
193
vendor/github.com/emicklei/go-restful/v3/cors_filter.go
generated
vendored
@@ -1,193 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CrossOriginResourceSharing is used to create a Container Filter that implements CORS.
|
|
||||||
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
|
|
||||||
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
|
|
||||||
//
|
|
||||||
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
|
||||||
// http://enable-cors.org/server.html
|
|
||||||
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
|
|
||||||
type CrossOriginResourceSharing struct {
|
|
||||||
ExposeHeaders []string // list of Header names
|
|
||||||
|
|
||||||
// AllowedHeaders is alist of Header names. Checking is case-insensitive.
|
|
||||||
// The list may contain the special wildcard string ".*" ; all is allowed
|
|
||||||
AllowedHeaders []string
|
|
||||||
|
|
||||||
// AllowedDomains is a list of allowed values for Http Origin.
|
|
||||||
// The list may contain the special wildcard string ".*" ; all is allowed
|
|
||||||
// If empty all are allowed.
|
|
||||||
AllowedDomains []string
|
|
||||||
|
|
||||||
// AllowedDomainFunc is optional and is a function that will do the check
|
|
||||||
// when the origin is not part of the AllowedDomains and it does not contain the wildcard ".*".
|
|
||||||
AllowedDomainFunc func(origin string) bool
|
|
||||||
|
|
||||||
// AllowedMethods is either empty or has a list of http methods names. Checking is case-insensitive.
|
|
||||||
AllowedMethods []string
|
|
||||||
MaxAge int // number of seconds before requiring new Options request
|
|
||||||
CookiesAllowed bool
|
|
||||||
Container *Container
|
|
||||||
|
|
||||||
allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html
|
|
||||||
// and http://www.html5rocks.com/static/images/cors_server_flowchart.png
|
|
||||||
func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
if len(origin) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Print("no Http header Origin set")
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !c.isOriginAllowed(origin) { // check whether this origin is allowed
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns)
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if req.Request.Method != "OPTIONS" {
|
|
||||||
c.doActualRequest(req, resp)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod); acrm != "" {
|
|
||||||
c.doPreflightRequest(req, resp)
|
|
||||||
} else {
|
|
||||||
c.doActualRequest(req, resp)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response) {
|
|
||||||
c.setOptionsHeaders(req, resp)
|
|
||||||
// continue processing the response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
|
|
||||||
if len(c.AllowedMethods) == 0 {
|
|
||||||
if c.Container == nil {
|
|
||||||
c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
|
|
||||||
} else {
|
|
||||||
c.AllowedMethods = c.Container.computeAllowedMethods(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)
|
|
||||||
if !c.isValidAccessControlRequestMethod(acrm, c.AllowedMethods) {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
|
||||||
HEADER_AccessControlRequestMethod,
|
|
||||||
acrm,
|
|
||||||
c.AllowedMethods)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
acrhs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
|
||||||
if len(acrhs) > 0 {
|
|
||||||
for _, each := range strings.Split(acrhs, ",") {
|
|
||||||
if !c.isValidAccessControlRequestHeader(strings.Trim(each, " ")) {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
|
||||||
HEADER_AccessControlRequestHeaders,
|
|
||||||
acrhs,
|
|
||||||
c.AllowedHeaders)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, acrhs)
|
|
||||||
c.setOptionsHeaders(req, resp)
|
|
||||||
|
|
||||||
// return http 200 response, no body
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) setOptionsHeaders(req *Request, resp *Response) {
|
|
||||||
c.checkAndSetExposeHeaders(resp)
|
|
||||||
c.setAllowOriginHeader(req, resp)
|
|
||||||
c.checkAndSetAllowCredentials(resp)
|
|
||||||
if c.MaxAge > 0 {
|
|
||||||
resp.AddHeader(HEADER_AccessControlMaxAge, strconv.Itoa(c.MaxAge))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool {
|
|
||||||
if len(origin) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
lowerOrigin := strings.ToLower(origin)
|
|
||||||
if len(c.AllowedDomains) == 0 {
|
|
||||||
if c.AllowedDomainFunc != nil {
|
|
||||||
return c.AllowedDomainFunc(lowerOrigin)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// exact match on each allowed domain
|
|
||||||
for _, domain := range c.AllowedDomains {
|
|
||||||
if domain == ".*" || strings.ToLower(domain) == lowerOrigin {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.AllowedDomainFunc != nil {
|
|
||||||
return c.AllowedDomainFunc(origin)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) {
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
if c.isOriginAllowed(origin) {
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) {
|
|
||||||
if len(c.ExposeHeaders) > 0 {
|
|
||||||
resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) {
|
|
||||||
if c.CookiesAllowed {
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowCredentials, "true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool {
|
|
||||||
for _, each := range allowedMethods {
|
|
||||||
if each == method {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool {
|
|
||||||
for _, each := range c.AllowedHeaders {
|
|
||||||
if strings.ToLower(each) == strings.ToLower(header) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if each == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
2
vendor/github.com/emicklei/go-restful/v3/coverage.sh
generated
vendored
2
vendor/github.com/emicklei/go-restful/v3/coverage.sh
generated
vendored
@@ -1,2 +0,0 @@
|
|||||||
go test -coverprofile=coverage.out
|
|
||||||
go tool cover -html=coverage.out
|
|
||||||
173
vendor/github.com/emicklei/go-restful/v3/curly.go
generated
vendored
173
vendor/github.com/emicklei/go-restful/v3/curly.go
generated
vendored
@@ -1,173 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
|
|
||||||
type CurlyRouter struct{}
|
|
||||||
|
|
||||||
// SelectRoute is part of the Router interface and returns the best match
|
|
||||||
// for the WebService and its Route for the given Request.
|
|
||||||
func (c CurlyRouter) SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
|
|
||||||
|
|
||||||
requestTokens := tokenizePath(httpRequest.URL.Path)
|
|
||||||
|
|
||||||
detectedService := c.detectWebService(requestTokens, webServices)
|
|
||||||
if detectedService == nil {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
|
|
||||||
}
|
|
||||||
return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
|
|
||||||
if len(candidateRoutes) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
|
|
||||||
}
|
|
||||||
return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
|
|
||||||
if selectedRoute == nil {
|
|
||||||
return detectedService, nil, err
|
|
||||||
}
|
|
||||||
return detectedService, selectedRoute, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
|
|
||||||
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
|
|
||||||
candidates := make(sortableCurlyRoutes, 0, 8)
|
|
||||||
for _, each := range ws.routes {
|
|
||||||
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
|
|
||||||
if matches {
|
|
||||||
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(candidates)
|
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
|
|
||||||
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string, routeHasCustomVerb bool) (matches bool, paramCount int, staticCount int) {
|
|
||||||
if len(routeTokens) < len(requestTokens) {
|
|
||||||
// proceed in matching only if last routeToken is wildcard
|
|
||||||
count := len(routeTokens)
|
|
||||||
if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
// proceed
|
|
||||||
}
|
|
||||||
for i, routeToken := range routeTokens {
|
|
||||||
if i == len(requestTokens) {
|
|
||||||
// reached end of request path
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
requestToken := requestTokens[i]
|
|
||||||
if routeHasCustomVerb && hasCustomVerb(routeToken){
|
|
||||||
if !isMatchCustomVerb(routeToken, requestToken) {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
staticCount++
|
|
||||||
requestToken = removeCustomVerb(requestToken)
|
|
||||||
routeToken = removeCustomVerb(routeToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(routeToken, "{") {
|
|
||||||
paramCount++
|
|
||||||
if colon := strings.Index(routeToken, ":"); colon != -1 {
|
|
||||||
// match by regex
|
|
||||||
matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
|
|
||||||
if !matchesToken {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
if matchesRemainder {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // no { prefix
|
|
||||||
if requestToken != routeToken {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
staticCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, paramCount, staticCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
|
|
||||||
// format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
|
|
||||||
func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
|
|
||||||
regPart := routeToken[colon+1 : len(routeToken)-1]
|
|
||||||
if regPart == "*" {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
|
|
||||||
}
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
matched, err := regexp.MatchString(regPart, requestToken)
|
|
||||||
return (matched && err == nil), false
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsr311Router = RouterJSR311{}
|
|
||||||
|
|
||||||
// detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
|
|
||||||
// headers of the Request. See also RouterJSR311 in jsr311.go
|
|
||||||
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
|
|
||||||
// tracing is done inside detectRoute
|
|
||||||
return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectWebService returns the best matching webService given the list of path tokens.
|
|
||||||
// see also computeWebserviceScore
|
|
||||||
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
|
|
||||||
var best *WebService
|
|
||||||
score := -1
|
|
||||||
for _, each := range webServices {
|
|
||||||
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
|
|
||||||
if matches && (eachScore > score) {
|
|
||||||
best = each
|
|
||||||
score = eachScore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeWebserviceScore returns whether tokens match and
|
|
||||||
// the weighted score of the longest matching consecutive tokens from the beginning.
|
|
||||||
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
|
|
||||||
if len(tokens) > len(requestTokens) {
|
|
||||||
return false, 0
|
|
||||||
}
|
|
||||||
score := 0
|
|
||||||
for i := 0; i < len(tokens); i++ {
|
|
||||||
each := requestTokens[i]
|
|
||||||
other := tokens[i]
|
|
||||||
if len(each) == 0 && len(other) == 0 {
|
|
||||||
score++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(other) > 0 && strings.HasPrefix(other, "{") {
|
|
||||||
// no empty match
|
|
||||||
if len(each) == 0 {
|
|
||||||
return false, score
|
|
||||||
}
|
|
||||||
score += 1
|
|
||||||
} else {
|
|
||||||
// not a parameter
|
|
||||||
if each != other {
|
|
||||||
return false, score
|
|
||||||
}
|
|
||||||
score += (len(tokens) - i) * 10 //fuzzy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, score
|
|
||||||
}
|
|
||||||
54
vendor/github.com/emicklei/go-restful/v3/curly_route.go
generated
vendored
54
vendor/github.com/emicklei/go-restful/v3/curly_route.go
generated
vendored
@@ -1,54 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// curlyRoute exits for sorting Routes by the CurlyRouter based on number of parameters and number of static path elements.
|
|
||||||
type curlyRoute struct {
|
|
||||||
route Route
|
|
||||||
paramCount int
|
|
||||||
staticCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortableCurlyRoutes orders by most parameters and path elements first.
|
|
||||||
type sortableCurlyRoutes []curlyRoute
|
|
||||||
|
|
||||||
func (s *sortableCurlyRoutes) add(route curlyRoute) {
|
|
||||||
*s = append(*s, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortableCurlyRoutes) routes() (routes []Route) {
|
|
||||||
routes = make([]Route, 0, len(s))
|
|
||||||
for _, each := range s {
|
|
||||||
routes = append(routes, each.route) // TODO change return type
|
|
||||||
}
|
|
||||||
return routes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortableCurlyRoutes) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
func (s sortableCurlyRoutes) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
func (s sortableCurlyRoutes) Less(i, j int) bool {
|
|
||||||
a := s[j]
|
|
||||||
b := s[i]
|
|
||||||
|
|
||||||
// primary key
|
|
||||||
if a.staticCount < b.staticCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if a.staticCount > b.staticCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if a.paramCount < b.paramCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if a.paramCount > b.paramCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return a.route.Path < b.route.Path
|
|
||||||
}
|
|
||||||
29
vendor/github.com/emicklei/go-restful/v3/custom_verb.go
generated
vendored
29
vendor/github.com/emicklei/go-restful/v3/custom_verb.go
generated
vendored
@@ -1,29 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
customVerbReg = regexp.MustCompile(":([A-Za-z]+)$")
|
|
||||||
)
|
|
||||||
|
|
||||||
func hasCustomVerb(routeToken string) bool {
|
|
||||||
return customVerbReg.MatchString(routeToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMatchCustomVerb(routeToken string, pathToken string) bool {
|
|
||||||
rs := customVerbReg.FindStringSubmatch(routeToken)
|
|
||||||
if len(rs) < 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
customVerb := rs[1]
|
|
||||||
specificVerbReg := regexp.MustCompile(fmt.Sprintf(":%s$", customVerb))
|
|
||||||
return specificVerbReg.MatchString(pathToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeCustomVerb(str string) string {
|
|
||||||
return customVerbReg.ReplaceAllString(str, "")
|
|
||||||
}
|
|
||||||
185
vendor/github.com/emicklei/go-restful/v3/doc.go
generated
vendored
185
vendor/github.com/emicklei/go-restful/v3/doc.go
generated
vendored
@@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
Package restful , a lean package for creating REST-style WebServices without magic.
|
|
||||||
|
|
||||||
WebServices and Routes
|
|
||||||
|
|
||||||
A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls.
|
|
||||||
Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes.
|
|
||||||
WebServices must be added to a container (see below) in order to handler Http requests from a server.
|
|
||||||
|
|
||||||
A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept).
|
|
||||||
This package has the logic to find the best matching Route and if found, call its Function.
|
|
||||||
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_JSON, restful.MIME_XML).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser)) // u is a UserResource
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The (*Request, *Response) arguments provide functions for reading information from the request and writing information back to the response.
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/v3/examples/user-resource/restful-user-resource.go with a full implementation.
|
|
||||||
|
|
||||||
Regular expression matching Routes
|
|
||||||
|
|
||||||
A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path.
|
|
||||||
For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters.
|
|
||||||
Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax)
|
|
||||||
This feature requires the use of a CurlyRouter.
|
|
||||||
|
|
||||||
Containers
|
|
||||||
|
|
||||||
A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests.
|
|
||||||
Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container.
|
|
||||||
The Default container of go-restful uses the http.DefaultServeMux.
|
|
||||||
You can create your own Container and create a new http.Server for that particular container.
|
|
||||||
|
|
||||||
container := restful.NewContainer()
|
|
||||||
server := &http.Server{Addr: ":8081", Handler: container}
|
|
||||||
|
|
||||||
Filters
|
|
||||||
|
|
||||||
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
|
|
||||||
You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc.
|
|
||||||
In the restful package there are three hooks into the request,response flow where filters can be added.
|
|
||||||
Each filter must define a FilterFunction:
|
|
||||||
|
|
||||||
func (req *restful.Request, resp *restful.Response, chain *restful.FilterChain)
|
|
||||||
|
|
||||||
Use the following statement to pass the request,response pair to the next filter or RouteFunction
|
|
||||||
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
|
|
||||||
Container Filters
|
|
||||||
|
|
||||||
These are processed before any registered WebService.
|
|
||||||
|
|
||||||
// install a (global) filter for the default container (processed before any webservice)
|
|
||||||
restful.Filter(globalLogging)
|
|
||||||
|
|
||||||
WebService Filters
|
|
||||||
|
|
||||||
These are processed before any Route of a WebService.
|
|
||||||
|
|
||||||
// install a webservice filter (processed before any route)
|
|
||||||
ws.Filter(webserviceLogging).Filter(measureTime)
|
|
||||||
|
|
||||||
|
|
||||||
Route Filters
|
|
||||||
|
|
||||||
These are processed before calling the function associated with the Route.
|
|
||||||
|
|
||||||
// install 2 chained route filters (processed before calling findUser)
|
|
||||||
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/v3/examples/filters/restful-filters.go with full implementations.
|
|
||||||
|
|
||||||
Response Encoding
|
|
||||||
|
|
||||||
Two encodings are supported: gzip and deflate. To enable this for all responses:
|
|
||||||
|
|
||||||
restful.DefaultContainer.EnableContentEncoding(true)
|
|
||||||
|
|
||||||
If a Http request includes the Accept-Encoding header then the response content will be compressed using the specified encoding.
|
|
||||||
Alternatively, you can create a Filter that performs the encoding and install it per WebService or Route.
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/v3/examples/encoding/restful-encoding-filter.go
|
|
||||||
|
|
||||||
OPTIONS support
|
|
||||||
|
|
||||||
By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request.
|
|
||||||
|
|
||||||
Filter(OPTIONSFilter())
|
|
||||||
|
|
||||||
CORS
|
|
||||||
|
|
||||||
By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests.
|
|
||||||
|
|
||||||
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
|
|
||||||
Error Handling
|
|
||||||
|
|
||||||
Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why.
|
|
||||||
For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation.
|
|
||||||
|
|
||||||
400: Bad Request
|
|
||||||
|
|
||||||
If path or query parameters are not valid (content or type) then use http.StatusBadRequest.
|
|
||||||
|
|
||||||
404: Not Found
|
|
||||||
|
|
||||||
Despite a valid URI, the resource requested may not be available
|
|
||||||
|
|
||||||
500: Internal Server Error
|
|
||||||
|
|
||||||
If the application logic could not process the request (or write the response) then use http.StatusInternalServerError.
|
|
||||||
|
|
||||||
405: Method Not Allowed
|
|
||||||
|
|
||||||
The request has a valid URL but the method (GET,PUT,POST,...) is not allowed.
|
|
||||||
|
|
||||||
406: Not Acceptable
|
|
||||||
|
|
||||||
The request does not have or has an unknown Accept Header set for this operation.
|
|
||||||
|
|
||||||
415: Unsupported Media Type
|
|
||||||
|
|
||||||
The request does not have or has an unknown Content-Type Header set for this operation.
|
|
||||||
|
|
||||||
ServiceError
|
|
||||||
|
|
||||||
In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response.
|
|
||||||
|
|
||||||
Performance options
|
|
||||||
|
|
||||||
This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
|
|
||||||
|
|
||||||
restful.DefaultContainer.DoNotRecover(false)
|
|
||||||
|
|
||||||
DoNotRecover controls whether panics will be caught to return HTTP 500.
|
|
||||||
If set to false, the container will recover from panics.
|
|
||||||
Default value is true
|
|
||||||
|
|
||||||
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
|
|
||||||
|
|
||||||
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
|
|
||||||
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
|
|
||||||
|
|
||||||
Trouble shooting
|
|
||||||
|
|
||||||
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
|
|
||||||
Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
|
|
||||||
|
|
||||||
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
|
|
||||||
|
|
||||||
Logging
|
|
||||||
|
|
||||||
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
|
|
||||||
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
|
|
||||||
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
|
|
||||||
preferred package is simple.
|
|
||||||
|
|
||||||
Resources
|
|
||||||
|
|
||||||
[project]: https://github.com/emicklei/go-restful
|
|
||||||
|
|
||||||
[examples]: https://github.com/emicklei/go-restful/blob/master/examples
|
|
||||||
|
|
||||||
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
|
|
||||||
|
|
||||||
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
|
|
||||||
|
|
||||||
(c) 2012-2015, http://ernestmicklei.com. MIT License
|
|
||||||
*/
|
|
||||||
package restful
|
|
||||||
169
vendor/github.com/emicklei/go-restful/v3/entity_accessors.go
generated
vendored
169
vendor/github.com/emicklei/go-restful/v3/entity_accessors.go
generated
vendored
@@ -1,169 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
NewEncoder = json.NewEncoder
|
|
||||||
)
|
|
||||||
|
|
||||||
// EntityReaderWriter can read and write values using an encoding such as JSON,XML.
|
|
||||||
type EntityReaderWriter interface {
|
|
||||||
// Read a serialized version of the value from the request.
|
|
||||||
// The Request may have a decompressing reader. Depends on Content-Encoding.
|
|
||||||
Read(req *Request, v interface{}) error
|
|
||||||
|
|
||||||
// Write a serialized version of the value on the response.
|
|
||||||
// The Response may have a compressing writer. Depends on Accept-Encoding.
|
|
||||||
// status should be a valid Http Status code
|
|
||||||
Write(resp *Response, status int, v interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityAccessRegistry is a singleton
|
|
||||||
var entityAccessRegistry = &entityReaderWriters{
|
|
||||||
protection: new(sync.RWMutex),
|
|
||||||
accessors: map[string]EntityReaderWriter{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityReaderWriters associates MIME to an EntityReaderWriter
|
|
||||||
type entityReaderWriters struct {
|
|
||||||
protection *sync.RWMutex
|
|
||||||
accessors map[string]EntityReaderWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON))
|
|
||||||
RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
|
|
||||||
func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
|
|
||||||
entityAccessRegistry.protection.Lock()
|
|
||||||
defer entityAccessRegistry.protection.Unlock()
|
|
||||||
entityAccessRegistry.accessors[mime] = erw
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content.
|
|
||||||
// This package is already initialized with such an accessor using the MIME_JSON contentType.
|
|
||||||
func NewEntityAccessorJSON(contentType string) EntityReaderWriter {
|
|
||||||
return entityJSONAccess{ContentType: contentType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content.
|
|
||||||
// This package is already initialized with such an accessor using the MIME_XML contentType.
|
|
||||||
func NewEntityAccessorXML(contentType string) EntityReaderWriter {
|
|
||||||
return entityXMLAccess{ContentType: contentType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// accessorAt returns the registered ReaderWriter for this MIME type.
|
|
||||||
func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) {
|
|
||||||
r.protection.RLock()
|
|
||||||
defer r.protection.RUnlock()
|
|
||||||
er, ok := r.accessors[mime]
|
|
||||||
if !ok {
|
|
||||||
// retry with reverse lookup
|
|
||||||
// more expensive but we are in an exceptional situation anyway
|
|
||||||
for k, v := range r.accessors {
|
|
||||||
if strings.Contains(mime, k) {
|
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return er, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityXMLAccess is a EntityReaderWriter for XML encoding
|
|
||||||
type entityXMLAccess struct {
|
|
||||||
// This is used for setting the Content-Type header when writing
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from XML
|
|
||||||
func (e entityXMLAccess) Read(req *Request, v interface{}) error {
|
|
||||||
return xml.NewDecoder(req.Request.Body).Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
return writeXML(resp, status, e.ContentType, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeXML marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func writeXML(resp *Response, status int, contentType string, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if resp.prettyPrint {
|
|
||||||
// pretty output must be created and written explicitly
|
|
||||||
output, err := xml.MarshalIndent(v, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
_, err = resp.Write([]byte(xml.Header))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = resp.Write(output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// not-so-pretty
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return xml.NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityJSONAccess is a EntityReaderWriter for JSON encoding
|
|
||||||
type entityJSONAccess struct {
|
|
||||||
// This is used for setting the Content-Type header when writing
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from JSON
|
|
||||||
func (e entityJSONAccess) Read(req *Request, v interface{}) error {
|
|
||||||
decoder := NewDecoder(req.Request.Body)
|
|
||||||
decoder.UseNumber()
|
|
||||||
return decoder.Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
return writeJSON(resp, status, e.ContentType, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func writeJSON(resp *Response, status int, contentType string, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if resp.prettyPrint {
|
|
||||||
// pretty output must be created and written explicitly
|
|
||||||
output, err := MarshalIndent(v, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
_, err = resp.Write(output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// not-so-pretty
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
||||||
21
vendor/github.com/emicklei/go-restful/v3/extensions.go
generated
vendored
21
vendor/github.com/emicklei/go-restful/v3/extensions.go
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2021 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// ExtensionProperties provides storage of vendor extensions for entities
|
|
||||||
type ExtensionProperties struct {
|
|
||||||
// Extensions vendor extensions used to describe extra functionality
|
|
||||||
// (https://swagger.io/docs/specification/2-0/swagger-extensions/)
|
|
||||||
Extensions map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExtension adds or updates a key=value pair to the extension map.
|
|
||||||
func (ep *ExtensionProperties) AddExtension(key string, value interface{}) {
|
|
||||||
if ep.Extensions == nil {
|
|
||||||
ep.Extensions = map[string]interface{}{key: value}
|
|
||||||
} else {
|
|
||||||
ep.Extensions[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
37
vendor/github.com/emicklei/go-restful/v3/filter.go
generated
vendored
37
vendor/github.com/emicklei/go-restful/v3/filter.go
generated
vendored
@@ -1,37 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction.
|
|
||||||
type FilterChain struct {
|
|
||||||
Filters []FilterFunction // ordered list of FilterFunction
|
|
||||||
Index int // index into filters that is currently in progress
|
|
||||||
Target RouteFunction // function to call after passing all filters
|
|
||||||
ParameterDocs []*Parameter // the parameter docs for the route
|
|
||||||
Operation string // the name of the operation
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessFilter passes the request,response pair through the next of Filters.
|
|
||||||
// Each filter can decide to proceed to the next Filter or handle the Response itself.
|
|
||||||
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
|
|
||||||
if f.Index < len(f.Filters) {
|
|
||||||
f.Index++
|
|
||||||
f.Filters[f.Index-1](request, response, f)
|
|
||||||
} else {
|
|
||||||
f.Target(request, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
|
|
||||||
type FilterFunction func(*Request, *Response, *FilterChain)
|
|
||||||
|
|
||||||
// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching
|
|
||||||
// See examples/restful-no-cache-filter.go for usage
|
|
||||||
func NoBrowserCacheFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1.
|
|
||||||
resp.Header().Set("Pragma", "no-cache") // HTTP 1.0.
|
|
||||||
resp.Header().Set("Expires", "0") // Proxies.
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
21
vendor/github.com/emicklei/go-restful/v3/filter_adapter.go
generated
vendored
21
vendor/github.com/emicklei/go-restful/v3/filter_adapter.go
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HttpMiddlewareHandler is a function that takes a http.Handler and returns a http.Handler
|
|
||||||
type HttpMiddlewareHandler func(http.Handler) http.Handler
|
|
||||||
|
|
||||||
// HttpMiddlewareHandlerToFilter converts a HttpMiddlewareHandler to a FilterFunction.
|
|
||||||
func HttpMiddlewareHandlerToFilter(middleware HttpMiddlewareHandler) FilterFunction {
|
|
||||||
return func(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
||||||
req.Request = r
|
|
||||||
resp.ResponseWriter = rw
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
})
|
|
||||||
|
|
||||||
middleware(next).ServeHTTP(resp.ResponseWriter, req.Request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
326
vendor/github.com/emicklei/go-restful/v3/jsr311.go
generated
vendored
326
vendor/github.com/emicklei/go-restful/v3/jsr311.go
generated
vendored
@@ -1,326 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
|
|
||||||
// as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
|
|
||||||
// RouterJSR311 implements the Router interface.
|
|
||||||
// Concept of locators is not implemented.
|
|
||||||
type RouterJSR311 struct{}
|
|
||||||
|
|
||||||
// SelectRoute is part of the Router interface and returns the best match
|
|
||||||
// for the WebService and its Route for the given Request.
|
|
||||||
func (r RouterJSR311) SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
|
|
||||||
|
|
||||||
// Identify the root resource class (WebService)
|
|
||||||
dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, NewError(http.StatusNotFound, "")
|
|
||||||
}
|
|
||||||
// Obtain the set of candidate methods (Routes)
|
|
||||||
routes := r.selectRoutes(dispatcher, finalMatch)
|
|
||||||
if len(routes) == 0 {
|
|
||||||
return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify the method (Route) that will handle the request
|
|
||||||
route, ok := r.detectRoute(routes, httpRequest)
|
|
||||||
return dispatcher, route, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractParameters is used to obtain the path parameters from the route using the same matching
|
|
||||||
// engine as the JSR 311 router.
|
|
||||||
func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
|
|
||||||
webServiceExpr := webService.pathExpr
|
|
||||||
webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
|
|
||||||
pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
|
|
||||||
routeExpr := route.pathExpr
|
|
||||||
routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
|
|
||||||
routeParams := r.extractParams(routeExpr, routeMatches)
|
|
||||||
for key, value := range routeParams {
|
|
||||||
pathParameters[key] = value
|
|
||||||
}
|
|
||||||
return pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
|
|
||||||
params := map[string]string{}
|
|
||||||
for i := 1; i < len(matches); i++ {
|
|
||||||
if len(pathExpr.VarNames) >= i {
|
|
||||||
params[pathExpr.VarNames[i-1]] = matches[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
|
||||||
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
|
|
||||||
candidates := make([]*Route, 0, 8)
|
|
||||||
for i, each := range routes {
|
|
||||||
ok := true
|
|
||||||
for _, fn := range each.If {
|
|
||||||
if !fn(httpRequest) {
|
|
||||||
ok = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
candidates = append(candidates, &routes[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusNotFound, "404: Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// http method
|
|
||||||
previous := candidates
|
|
||||||
candidates = candidates[:0]
|
|
||||||
for _, each := range previous {
|
|
||||||
if httpRequest.Method == each.Method {
|
|
||||||
candidates = append(candidates, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
|
|
||||||
}
|
|
||||||
allowed := []string{}
|
|
||||||
allowedLoop:
|
|
||||||
for _, candidate := range previous {
|
|
||||||
for _, method := range allowed {
|
|
||||||
if method == candidate.Method {
|
|
||||||
continue allowedLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allowed = append(allowed, candidate.Method)
|
|
||||||
}
|
|
||||||
header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
|
|
||||||
return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
|
|
||||||
}
|
|
||||||
|
|
||||||
// content-type
|
|
||||||
contentType := httpRequest.Header.Get(HEADER_ContentType)
|
|
||||||
previous = candidates
|
|
||||||
candidates = candidates[:0]
|
|
||||||
for _, each := range previous {
|
|
||||||
if each.matchesContentType(contentType) {
|
|
||||||
candidates = append(candidates, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
|
|
||||||
}
|
|
||||||
if httpRequest.ContentLength > 0 {
|
|
||||||
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept
|
|
||||||
previous = candidates
|
|
||||||
candidates = candidates[:0]
|
|
||||||
accept := httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
if len(accept) == 0 {
|
|
||||||
accept = "*/*"
|
|
||||||
}
|
|
||||||
for _, each := range previous {
|
|
||||||
if each.matchesAccept(accept) {
|
|
||||||
candidates = append(candidates, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
|
|
||||||
}
|
|
||||||
available := []string{}
|
|
||||||
for _, candidate := range previous {
|
|
||||||
available = append(available, candidate.Produces...)
|
|
||||||
}
|
|
||||||
// if POST,PUT,PATCH without body
|
|
||||||
method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length")
|
|
||||||
if (method == http.MethodPost ||
|
|
||||||
method == http.MethodPut ||
|
|
||||||
method == http.MethodPatch) && (length == "" || length == "0") {
|
|
||||||
return nil, NewError(
|
|
||||||
http.StatusUnsupportedMediaType,
|
|
||||||
fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil, NewError(
|
|
||||||
http.StatusNotAcceptable,
|
|
||||||
fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
|
|
||||||
return candidates[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
|
||||||
// n/m > n/* > */*
|
|
||||||
func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
|
|
||||||
// TODO
|
|
||||||
return &routes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
|
|
||||||
func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
|
|
||||||
filtered := &sortableRouteCandidates{}
|
|
||||||
for _, each := range dispatcher.Routes() {
|
|
||||||
pathExpr := each.pathExpr
|
|
||||||
matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
|
|
||||||
if matches != nil {
|
|
||||||
lastMatch := matches[len(matches)-1]
|
|
||||||
if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
|
||||||
filtered.candidates = append(filtered.candidates,
|
|
||||||
routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filtered.candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
|
|
||||||
}
|
|
||||||
return []Route{}
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(filtered))
|
|
||||||
|
|
||||||
// select other routes from candidates whoes expression matches rmatch
|
|
||||||
matchingRoutes := []Route{filtered.candidates[0].route}
|
|
||||||
for c := 1; c < len(filtered.candidates); c++ {
|
|
||||||
each := filtered.candidates[c]
|
|
||||||
if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
|
|
||||||
matchingRoutes = append(matchingRoutes, each.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchingRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
|
|
||||||
func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
|
|
||||||
filtered := &sortableDispatcherCandidates{}
|
|
||||||
for _, each := range dispatchers {
|
|
||||||
matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
|
||||||
if matches != nil {
|
|
||||||
filtered.candidates = append(filtered.candidates,
|
|
||||||
dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filtered.candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
|
|
||||||
}
|
|
||||||
return nil, "", errors.New("not found")
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(filtered))
|
|
||||||
return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types and functions to support the sorting of Routes
|
|
||||||
|
|
||||||
type routeCandidate struct {
|
|
||||||
route Route
|
|
||||||
matchesCount int // the number of capturing groups
|
|
||||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeCandidate) expressionToMatch() string {
|
|
||||||
return r.route.pathExpr.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeCandidate) String() string {
|
|
||||||
return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableRouteCandidates struct {
|
|
||||||
candidates []routeCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rcs *sortableRouteCandidates) Len() int {
|
|
||||||
return len(rcs.candidates)
|
|
||||||
}
|
|
||||||
func (rcs *sortableRouteCandidates) Swap(i, j int) {
|
|
||||||
rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
|
|
||||||
}
|
|
||||||
func (rcs *sortableRouteCandidates) Less(i, j int) bool {
|
|
||||||
ci := rcs.candidates[i]
|
|
||||||
cj := rcs.candidates[j]
|
|
||||||
// primary key
|
|
||||||
if ci.literalCount < cj.literalCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.literalCount > cj.literalCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.matchesCount < cj.matchesCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.matchesCount > cj.matchesCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// tertiary key
|
|
||||||
if ci.nonDefaultCount < cj.nonDefaultCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.nonDefaultCount > cj.nonDefaultCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// quaternary key ("source" is interpreted as Path)
|
|
||||||
return ci.route.Path < cj.route.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types and functions to support the sorting of Dispatchers
|
|
||||||
|
|
||||||
type dispatcherCandidate struct {
|
|
||||||
dispatcher *WebService
|
|
||||||
finalMatch string
|
|
||||||
matchesCount int // the number of capturing groups
|
|
||||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
|
||||||
}
|
|
||||||
type sortableDispatcherCandidates struct {
|
|
||||||
candidates []dispatcherCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *sortableDispatcherCandidates) Len() int {
|
|
||||||
return len(dc.candidates)
|
|
||||||
}
|
|
||||||
func (dc *sortableDispatcherCandidates) Swap(i, j int) {
|
|
||||||
dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
|
|
||||||
}
|
|
||||||
func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
|
|
||||||
ci := dc.candidates[i]
|
|
||||||
cj := dc.candidates[j]
|
|
||||||
// primary key
|
|
||||||
if ci.matchesCount < cj.matchesCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.matchesCount > cj.matchesCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.literalCount < cj.literalCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.literalCount > cj.literalCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// tertiary key
|
|
||||||
return ci.nonDefaultCount < cj.nonDefaultCount
|
|
||||||
}
|
|
||||||
34
vendor/github.com/emicklei/go-restful/v3/log/log.go
generated
vendored
34
vendor/github.com/emicklei/go-restful/v3/log/log.go
generated
vendored
@@ -1,34 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
stdlog "log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdLogger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(v ...interface{})
|
|
||||||
Printf(format string, v ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var Logger StdLogger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// default Logger
|
|
||||||
SetLogger(stdlog.New(os.Stderr, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger sets the logger for this package
|
|
||||||
func SetLogger(customLogger StdLogger) {
|
|
||||||
Logger = customLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print delegates to the Logger
|
|
||||||
func Print(v ...interface{}) {
|
|
||||||
Logger.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf delegates to the Logger
|
|
||||||
func Printf(format string, v ...interface{}) {
|
|
||||||
Logger.Printf(format, v...)
|
|
||||||
}
|
|
||||||
32
vendor/github.com/emicklei/go-restful/v3/logger.go
generated
vendored
32
vendor/github.com/emicklei/go-restful/v3/logger.go
generated
vendored
@@ -1,32 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2014 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful/v3/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var trace bool = false
|
|
||||||
var traceLogger log.StdLogger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
traceLogger = log.Logger // use the package logger by default
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set.
|
|
||||||
// You may call EnableTracing() directly to enable trace logging to the package-wide logger.
|
|
||||||
func TraceLogger(logger log.StdLogger) {
|
|
||||||
traceLogger = logger
|
|
||||||
EnableTracing(logger != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger exposes the setter for the global logger on the top-level package
|
|
||||||
func SetLogger(customLogger log.StdLogger) {
|
|
||||||
log.SetLogger(customLogger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableTracing can be used to Trace logging on and off.
|
|
||||||
func EnableTracing(enabled bool) {
|
|
||||||
trace = enabled
|
|
||||||
}
|
|
||||||
50
vendor/github.com/emicklei/go-restful/v3/mime.go
generated
vendored
50
vendor/github.com/emicklei/go-restful/v3/mime.go
generated
vendored
@@ -1,50 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mime struct {
|
|
||||||
media string
|
|
||||||
quality float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertMime adds a mime to a list and keeps it sorted by quality.
|
|
||||||
func insertMime(l []mime, e mime) []mime {
|
|
||||||
for i, each := range l {
|
|
||||||
// if current mime has lower quality then insert before
|
|
||||||
if e.quality > each.quality {
|
|
||||||
left := append([]mime{}, l[0:i]...)
|
|
||||||
return append(append(left, e), l[i:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return append(l, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const qFactorWeightingKey = "q"
|
|
||||||
|
|
||||||
// sortedMimes returns a list of mime sorted (desc) by its specified quality.
|
|
||||||
// e.g. text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
|
|
||||||
func sortedMimes(accept string) (sorted []mime) {
|
|
||||||
for _, each := range strings.Split(accept, ",") {
|
|
||||||
typeAndQuality := strings.Split(strings.Trim(each, " "), ";")
|
|
||||||
if len(typeAndQuality) == 1 {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0})
|
|
||||||
} else {
|
|
||||||
// take factor
|
|
||||||
qAndWeight := strings.Split(typeAndQuality[1], "=")
|
|
||||||
if len(qAndWeight) == 2 && strings.Trim(qAndWeight[0], " ") == qFactorWeightingKey {
|
|
||||||
f, err := strconv.ParseFloat(qAndWeight[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
traceLogger.Printf("unable to parse quality in %s, %v", each, err)
|
|
||||||
} else {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], f})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
34
vendor/github.com/emicklei/go-restful/v3/options_filter.go
generated
vendored
34
vendor/github.com/emicklei/go-restful/v3/options_filter.go
generated
vendored
@@ -1,34 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
|
||||||
// and provides the response with a set of allowed methods for the request URL Path.
|
|
||||||
// As for any filter, you can also install it for a particular WebService within a Container.
|
|
||||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
|
||||||
func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
if "OPTIONS" != req.Request.Method {
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
archs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
|
||||||
methods := strings.Join(c.computeAllowedMethods(req), ",")
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
|
|
||||||
resp.AddHeader(HEADER_Allow, methods)
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, archs)
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowMethods, methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
|
||||||
// and provides the response with a set of allowed methods for the request URL Path.
|
|
||||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
|
||||||
func OPTIONSFilter() FilterFunction {
|
|
||||||
return DefaultContainer.OPTIONSFilter
|
|
||||||
}
|
|
||||||
242
vendor/github.com/emicklei/go-restful/v3/parameter.go
generated
vendored
242
vendor/github.com/emicklei/go-restful/v3/parameter.go
generated
vendored
@@ -1,242 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import "sort"
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PathParameterKind = indicator of Request parameter type "path"
|
|
||||||
PathParameterKind = iota
|
|
||||||
|
|
||||||
// QueryParameterKind = indicator of Request parameter type "query"
|
|
||||||
QueryParameterKind
|
|
||||||
|
|
||||||
// BodyParameterKind = indicator of Request parameter type "body"
|
|
||||||
BodyParameterKind
|
|
||||||
|
|
||||||
// HeaderParameterKind = indicator of Request parameter type "header"
|
|
||||||
HeaderParameterKind
|
|
||||||
|
|
||||||
// FormParameterKind = indicator of Request parameter type "form"
|
|
||||||
FormParameterKind
|
|
||||||
|
|
||||||
// MultiPartFormParameterKind = indicator of Request parameter type "multipart/form-data"
|
|
||||||
MultiPartFormParameterKind
|
|
||||||
|
|
||||||
// CollectionFormatCSV comma separated values `foo,bar`
|
|
||||||
CollectionFormatCSV = CollectionFormat("csv")
|
|
||||||
|
|
||||||
// CollectionFormatSSV space separated values `foo bar`
|
|
||||||
CollectionFormatSSV = CollectionFormat("ssv")
|
|
||||||
|
|
||||||
// CollectionFormatTSV tab separated values `foo\tbar`
|
|
||||||
CollectionFormatTSV = CollectionFormat("tsv")
|
|
||||||
|
|
||||||
// CollectionFormatPipes pipe separated values `foo|bar`
|
|
||||||
CollectionFormatPipes = CollectionFormat("pipes")
|
|
||||||
|
|
||||||
// CollectionFormatMulti corresponds to multiple parameter instances instead of multiple values for a single
|
|
||||||
// instance `foo=bar&foo=baz`. This is valid only for QueryParameters and FormParameters
|
|
||||||
CollectionFormatMulti = CollectionFormat("multi")
|
|
||||||
)
|
|
||||||
|
|
||||||
type CollectionFormat string
|
|
||||||
|
|
||||||
func (cf CollectionFormat) String() string {
|
|
||||||
return string(cf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameter is for documententing the parameter used in a Http Request
|
|
||||||
// ParameterData kinds are Path,Query and Body
|
|
||||||
type Parameter struct {
|
|
||||||
data *ParameterData
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterData represents the state of a Parameter.
|
|
||||||
// It is made public to make it accessible to e.g. the Swagger package.
|
|
||||||
type ParameterData struct {
|
|
||||||
ExtensionProperties
|
|
||||||
Name, Description, DataType, DataFormat string
|
|
||||||
Kind int
|
|
||||||
Required bool
|
|
||||||
// AllowableValues is deprecated. Use PossibleValues instead
|
|
||||||
AllowableValues map[string]string
|
|
||||||
PossibleValues []string
|
|
||||||
AllowMultiple bool
|
|
||||||
AllowEmptyValue bool
|
|
||||||
DefaultValue string
|
|
||||||
CollectionFormat string
|
|
||||||
Pattern string
|
|
||||||
Minimum *float64
|
|
||||||
Maximum *float64
|
|
||||||
MinLength *int64
|
|
||||||
MaxLength *int64
|
|
||||||
MinItems *int64
|
|
||||||
MaxItems *int64
|
|
||||||
UniqueItems bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data returns the state of the Parameter
|
|
||||||
func (p *Parameter) Data() ParameterData {
|
|
||||||
return *p.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind returns the parameter type indicator (see const for valid values)
|
|
||||||
func (p *Parameter) Kind() int {
|
|
||||||
return p.data.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) bePath() *Parameter {
|
|
||||||
p.data.Kind = PathParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (p *Parameter) beQuery() *Parameter {
|
|
||||||
p.data.Kind = QueryParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (p *Parameter) beBody() *Parameter {
|
|
||||||
p.data.Kind = BodyParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beHeader() *Parameter {
|
|
||||||
p.data.Kind = HeaderParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beForm() *Parameter {
|
|
||||||
p.data.Kind = FormParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beMultiPartForm() *Parameter {
|
|
||||||
p.data.Kind = MultiPartFormParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required sets the required field and returns the receiver
|
|
||||||
func (p *Parameter) Required(required bool) *Parameter {
|
|
||||||
p.data.Required = required
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowMultiple sets the allowMultiple field and returns the receiver
|
|
||||||
func (p *Parameter) AllowMultiple(multiple bool) *Parameter {
|
|
||||||
p.data.AllowMultiple = multiple
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExtension adds or updates a key=value pair to the extension map
|
|
||||||
func (p *Parameter) AddExtension(key string, value interface{}) *Parameter {
|
|
||||||
p.data.AddExtension(key, value)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowEmptyValue sets the AllowEmptyValue field and returns the receiver
|
|
||||||
func (p *Parameter) AllowEmptyValue(multiple bool) *Parameter {
|
|
||||||
p.data.AllowEmptyValue = multiple
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowableValues is deprecated. Use PossibleValues instead. Both will be set.
|
|
||||||
func (p *Parameter) AllowableValues(values map[string]string) *Parameter {
|
|
||||||
p.data.AllowableValues = values
|
|
||||||
|
|
||||||
allowableSortedKeys := make([]string, 0, len(values))
|
|
||||||
for k := range values {
|
|
||||||
allowableSortedKeys = append(allowableSortedKeys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(allowableSortedKeys)
|
|
||||||
|
|
||||||
p.data.PossibleValues = make([]string, 0, len(values))
|
|
||||||
for _, k := range allowableSortedKeys {
|
|
||||||
p.data.PossibleValues = append(p.data.PossibleValues, values[k])
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// PossibleValues sets the possible values field and returns the receiver
|
|
||||||
func (p *Parameter) PossibleValues(values []string) *Parameter {
|
|
||||||
p.data.PossibleValues = values
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataType sets the dataType field and returns the receiver
|
|
||||||
func (p *Parameter) DataType(typeName string) *Parameter {
|
|
||||||
p.data.DataType = typeName
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataFormat sets the dataFormat field for Swagger UI
|
|
||||||
func (p *Parameter) DataFormat(formatName string) *Parameter {
|
|
||||||
p.data.DataFormat = formatName
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultValue sets the default value field and returns the receiver
|
|
||||||
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
|
||||||
p.data.DefaultValue = stringRepresentation
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description sets the description value field and returns the receiver
|
|
||||||
func (p *Parameter) Description(doc string) *Parameter {
|
|
||||||
p.data.Description = doc
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// CollectionFormat sets the collection format for an array type
|
|
||||||
func (p *Parameter) CollectionFormat(format CollectionFormat) *Parameter {
|
|
||||||
p.data.CollectionFormat = format.String()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern sets the pattern field and returns the receiver
|
|
||||||
func (p *Parameter) Pattern(pattern string) *Parameter {
|
|
||||||
p.data.Pattern = pattern
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimum sets the minimum field and returns the receiver
|
|
||||||
func (p *Parameter) Minimum(minimum float64) *Parameter {
|
|
||||||
p.data.Minimum = &minimum
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximum sets the maximum field and returns the receiver
|
|
||||||
func (p *Parameter) Maximum(maximum float64) *Parameter {
|
|
||||||
p.data.Maximum = &maximum
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinLength sets the minLength field and returns the receiver
|
|
||||||
func (p *Parameter) MinLength(minLength int64) *Parameter {
|
|
||||||
p.data.MinLength = &minLength
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxLength sets the maxLength field and returns the receiver
|
|
||||||
func (p *Parameter) MaxLength(maxLength int64) *Parameter {
|
|
||||||
p.data.MaxLength = &maxLength
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinItems sets the minItems field and returns the receiver
|
|
||||||
func (p *Parameter) MinItems(minItems int64) *Parameter {
|
|
||||||
p.data.MinItems = &minItems
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxItems sets the maxItems field and returns the receiver
|
|
||||||
func (p *Parameter) MaxItems(maxItems int64) *Parameter {
|
|
||||||
p.data.MaxItems = &maxItems
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniqueItems sets the uniqueItems field and returns the receiver
|
|
||||||
func (p *Parameter) UniqueItems(uniqueItems bool) *Parameter {
|
|
||||||
p.data.UniqueItems = uniqueItems
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
74
vendor/github.com/emicklei/go-restful/v3/path_expression.go
generated
vendored
74
vendor/github.com/emicklei/go-restful/v3/path_expression.go
generated
vendored
@@ -1,74 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PathExpression holds a compiled path expression (RegExp) needed to match against
|
|
||||||
// Http request paths and to extract path parameter values.
|
|
||||||
type pathExpression struct {
|
|
||||||
LiteralCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
VarNames []string // the names of parameters (enclosed by {}) in the path
|
|
||||||
VarCount int // the number of named parameters (enclosed by {}) in the path
|
|
||||||
Matcher *regexp.Regexp
|
|
||||||
Source string // Path as defined by the RouteBuilder
|
|
||||||
tokens []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPathExpression creates a PathExpression from the input URL path.
|
|
||||||
// Returns an error if the path is invalid.
|
|
||||||
func newPathExpression(path string) (*pathExpression, error) {
|
|
||||||
expression, literalCount, varNames, varCount, tokens := templateToRegularExpression(path)
|
|
||||||
compiled, err := regexp.Compile(expression)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &pathExpression{literalCount, varNames, varCount, compiled, expression, tokens}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-370003.7.3
|
|
||||||
func templateToRegularExpression(template string) (expression string, literalCount int, varNames []string, varCount int, tokens []string) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString("^")
|
|
||||||
//tokens = strings.Split(template, "/")
|
|
||||||
tokens = tokenizePath(template)
|
|
||||||
for _, each := range tokens {
|
|
||||||
if each == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buffer.WriteString("/")
|
|
||||||
if strings.HasPrefix(each, "{") {
|
|
||||||
// check for regular expression in variable
|
|
||||||
colon := strings.Index(each, ":")
|
|
||||||
var varName string
|
|
||||||
if colon != -1 {
|
|
||||||
// extract expression
|
|
||||||
varName = strings.TrimSpace(each[1:colon])
|
|
||||||
paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1])
|
|
||||||
if paramExpr == "*" { // special case
|
|
||||||
buffer.WriteString("(.*)")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// plain var
|
|
||||||
varName = strings.TrimSpace(each[1 : len(each)-1])
|
|
||||||
buffer.WriteString("([^/]+?)")
|
|
||||||
}
|
|
||||||
varNames = append(varNames, varName)
|
|
||||||
varCount += 1
|
|
||||||
} else {
|
|
||||||
literalCount += len(each)
|
|
||||||
encoded := each // TODO URI encode
|
|
||||||
buffer.WriteString(regexp.QuoteMeta(encoded))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varNames, varCount, tokens
|
|
||||||
}
|
|
||||||
74
vendor/github.com/emicklei/go-restful/v3/path_processor.go
generated
vendored
74
vendor/github.com/emicklei/go-restful/v3/path_processor.go
generated
vendored
@@ -1,74 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copyright 2018 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// PathProcessor is extra behaviour that a Router can provide to extract path parameters from the path.
|
|
||||||
// If a Router does not implement this interface then the default behaviour will be used.
|
|
||||||
type PathProcessor interface {
|
|
||||||
// ExtractParameters gets the path parameters defined in the route and webService from the urlPath
|
|
||||||
ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultPathProcessor struct{}
|
|
||||||
|
|
||||||
// Extract the parameters from the request url path
|
|
||||||
func (d defaultPathProcessor) ExtractParameters(r *Route, _ *WebService, urlPath string) map[string]string {
|
|
||||||
urlParts := tokenizePath(urlPath)
|
|
||||||
pathParameters := map[string]string{}
|
|
||||||
for i, key := range r.pathParts {
|
|
||||||
var value string
|
|
||||||
if i >= len(urlParts) {
|
|
||||||
value = ""
|
|
||||||
} else {
|
|
||||||
value = urlParts[i]
|
|
||||||
}
|
|
||||||
if r.hasCustomVerb && hasCustomVerb(key) {
|
|
||||||
key = removeCustomVerb(key)
|
|
||||||
value = removeCustomVerb(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Index(key, "{") > -1 { // path-parameter
|
|
||||||
if colon := strings.Index(key, ":"); colon != -1 {
|
|
||||||
// extract by regex
|
|
||||||
regPart := key[colon+1 : len(key)-1]
|
|
||||||
keyPart := key[1:colon]
|
|
||||||
if regPart == "*" {
|
|
||||||
pathParameters[keyPart] = untokenizePath(i, urlParts)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
pathParameters[keyPart] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// without enclosing {}
|
|
||||||
startIndex := strings.Index(key, "{")
|
|
||||||
endKeyIndex := strings.Index(key, "}")
|
|
||||||
|
|
||||||
suffixLength := len(key) - endKeyIndex - 1
|
|
||||||
endValueIndex := len(value) - suffixLength
|
|
||||||
|
|
||||||
pathParameters[key[startIndex+1:endKeyIndex]] = value[startIndex:endValueIndex]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// Untokenize back into an URL path using the slash separator
|
|
||||||
func untokenizePath(offset int, parts []string) string {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for p := offset; p < len(parts); p++ {
|
|
||||||
buffer.WriteString(parts[p])
|
|
||||||
// do not end
|
|
||||||
if p < len(parts)-1 {
|
|
||||||
buffer.WriteString("/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
133
vendor/github.com/emicklei/go-restful/v3/request.go
generated
vendored
133
vendor/github.com/emicklei/go-restful/v3/request.go
generated
vendored
@@ -1,133 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/zlib"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultRequestContentType string
|
|
||||||
|
|
||||||
// Request is a wrapper for a http Request that provides convenience methods
|
|
||||||
type Request struct {
|
|
||||||
Request *http.Request
|
|
||||||
pathParameters map[string]string
|
|
||||||
attributes map[string]interface{} // for storing request-scoped values
|
|
||||||
selectedRoute *Route // is nil when no route was matched
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(httpRequest *http.Request) *Request {
|
|
||||||
return &Request{
|
|
||||||
Request: httpRequest,
|
|
||||||
pathParameters: map[string]string{},
|
|
||||||
attributes: map[string]interface{}{},
|
|
||||||
} // empty parameters, attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ContentType is missing or */* is given then fall back to this type, otherwise
|
|
||||||
// a "Unable to unmarshal content of type:" response is returned.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// restful.DefaultRequestContentType(restful.MIME_JSON)
|
|
||||||
func DefaultRequestContentType(mime string) {
|
|
||||||
defaultRequestContentType = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter accesses the Path parameter value by its name
|
|
||||||
func (r *Request) PathParameter(name string) string {
|
|
||||||
return r.pathParameters[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameters accesses the Path parameter values
|
|
||||||
func (r *Request) PathParameters() map[string]string {
|
|
||||||
return r.pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter returns the (first) Query parameter value by its name
|
|
||||||
func (r *Request) QueryParameter(name string) string {
|
|
||||||
return r.Request.URL.Query().Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameters returns the all the query parameters values by name
|
|
||||||
func (r *Request) QueryParameters(name string) []string {
|
|
||||||
return r.Request.URL.Query()[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter parses the body of the request (once for typically a POST or a PUT) and returns the value of the given name or an error.
|
|
||||||
func (r *Request) BodyParameter(name string) (string, error) {
|
|
||||||
err := r.Request.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return r.Request.PostFormValue(name), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter returns the HTTP Header value of a Header name or empty if missing
|
|
||||||
func (r *Request) HeaderParameter(name string) string {
|
|
||||||
return r.Request.Header.Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadEntity checks the Accept header and reads the content into the entityPointer.
|
|
||||||
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
|
|
||||||
contentType := r.Request.Header.Get(HEADER_ContentType)
|
|
||||||
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
|
|
||||||
|
|
||||||
// check if the request body needs decompression
|
|
||||||
if ENCODING_GZIP == contentEncoding {
|
|
||||||
gzipReader := currentCompressorProvider.AcquireGzipReader()
|
|
||||||
defer currentCompressorProvider.ReleaseGzipReader(gzipReader)
|
|
||||||
gzipReader.Reset(r.Request.Body)
|
|
||||||
r.Request.Body = gzipReader
|
|
||||||
} else if ENCODING_DEFLATE == contentEncoding {
|
|
||||||
zlibReader, err := zlib.NewReader(r.Request.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Request.Body = zlibReader
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup the EntityReader, use defaultRequestContentType if needed and provided
|
|
||||||
entityReader, ok := entityAccessRegistry.accessorAt(contentType)
|
|
||||||
if !ok {
|
|
||||||
if len(defaultRequestContentType) != 0 {
|
|
||||||
entityReader, ok = entityAccessRegistry.accessorAt(defaultRequestContentType)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entityReader.Read(r, entityPointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAttribute adds or replaces the attribute with the given value.
|
|
||||||
func (r *Request) SetAttribute(name string, value interface{}) {
|
|
||||||
r.attributes[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute returns the value associated to the given name. Returns nil if absent.
|
|
||||||
func (r Request) Attribute(name string) interface{} {
|
|
||||||
return r.attributes[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectedRoutePath root path + route path that matched the request, e.g. /meetings/{id}/attendees
|
|
||||||
// If no route was matched then return an empty string.
|
|
||||||
func (r Request) SelectedRoutePath() string {
|
|
||||||
if r.selectedRoute == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// skip creating an accessor
|
|
||||||
return r.selectedRoute.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectedRoute returns a reader to access the selected Route by the container
|
|
||||||
// Returns nil if no route was matched.
|
|
||||||
func (r Request) SelectedRoute() RouteReader {
|
|
||||||
if r.selectedRoute == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return routeAccessor{route: r.selectedRoute}
|
|
||||||
}
|
|
||||||
259
vendor/github.com/emicklei/go-restful/v3/response.go
generated
vendored
259
vendor/github.com/emicklei/go-restful/v3/response.go
generated
vendored
@@ -1,259 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultResponseMimeType is DEPRECATED, use DefaultResponseContentType(mime)
|
|
||||||
var DefaultResponseMimeType string
|
|
||||||
|
|
||||||
//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
|
|
||||||
var PrettyPrintResponses = true
|
|
||||||
|
|
||||||
// Response is a wrapper on the actual http ResponseWriter
|
|
||||||
// It provides several convenience methods to prepare and write response content.
|
|
||||||
type Response struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
requestAccept string // mime-type what the Http Request says it wants to receive
|
|
||||||
routeProduces []string // mime-types what the Route says it can produce
|
|
||||||
statusCode int // HTTP status code that has been written explicitly (if zero then net/http has written 200)
|
|
||||||
contentLength int // number of bytes written for the response body
|
|
||||||
prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
|
|
||||||
err error // err property is kept when WriteError is called
|
|
||||||
hijacker http.Hijacker // if underlying ResponseWriter supports it
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResponse creates a new response based on a http ResponseWriter.
|
|
||||||
func NewResponse(httpWriter http.ResponseWriter) *Response {
|
|
||||||
hijacker, _ := httpWriter.(http.Hijacker)
|
|
||||||
return &Response{ResponseWriter: httpWriter, routeProduces: []string{}, statusCode: http.StatusOK, prettyPrint: PrettyPrintResponses, hijacker: hijacker}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultResponseContentType set a default.
|
|
||||||
// If Accept header matching fails, fall back to this type.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
|
||||||
// Example:
|
|
||||||
// restful.DefaultResponseContentType(restful.MIME_JSON)
|
|
||||||
func DefaultResponseContentType(mime string) {
|
|
||||||
DefaultResponseMimeType = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalServerError writes the StatusInternalServerError header.
|
|
||||||
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
|
|
||||||
func (r Response) InternalServerError() Response {
|
|
||||||
r.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hijack implements the http.Hijacker interface. This expands
|
|
||||||
// the Response to fulfill http.Hijacker if the underlying
|
|
||||||
// http.ResponseWriter supports it.
|
|
||||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
if r.hijacker == nil {
|
|
||||||
return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter")
|
|
||||||
}
|
|
||||||
return r.hijacker.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
|
|
||||||
func (r *Response) PrettyPrint(bePretty bool) {
|
|
||||||
r.prettyPrint = bePretty
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHeader is a shortcut for .Header().Add(header,value)
|
|
||||||
func (r Response) AddHeader(header string, value string) Response {
|
|
||||||
r.Header().Add(header, value)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
|
|
||||||
func (r *Response) SetRequestAccepts(mime string) {
|
|
||||||
r.requestAccept = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntityWriter returns the registered EntityWriter that the entity (requested resource)
|
|
||||||
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
|
|
||||||
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
|
|
||||||
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
|
|
||||||
sorted := sortedMimes(r.requestAccept)
|
|
||||||
for _, eachAccept := range sorted {
|
|
||||||
for _, eachProduce := range r.routeProduces {
|
|
||||||
if eachProduce == eachAccept.media {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if eachAccept.media == "*/*" {
|
|
||||||
for _, each := range r.routeProduces {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(each); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if requestAccept is empty
|
|
||||||
writer, ok := entityAccessRegistry.accessorAt(r.requestAccept)
|
|
||||||
if !ok {
|
|
||||||
// if not registered then fallback to the defaults (if set)
|
|
||||||
if DefaultResponseMimeType == MIME_JSON {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_JSON)
|
|
||||||
}
|
|
||||||
if DefaultResponseMimeType == MIME_XML {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_XML)
|
|
||||||
}
|
|
||||||
if DefaultResponseMimeType == MIME_ZIP {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_ZIP)
|
|
||||||
}
|
|
||||||
// Fallback to whatever the route says it can produce.
|
|
||||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
||||||
for _, each := range r.routeProduces {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(each); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return writer, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
|
|
||||||
func (r *Response) WriteEntity(value interface{}) error {
|
|
||||||
return r.WriteHeaderAndEntity(http.StatusOK, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
|
|
||||||
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
|
|
||||||
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
|
|
||||||
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
|
|
||||||
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
|
|
||||||
// Current implementation ignores any q-parameters in the Accept Header.
|
|
||||||
// Returns an error if the value could not be written on the response.
|
|
||||||
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
|
|
||||||
writer, ok := r.EntityWriter()
|
|
||||||
if !ok {
|
|
||||||
r.WriteHeader(http.StatusNotAcceptable)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return writer.Write(r, status, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
|
|
||||||
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteAsXml(value interface{}) error {
|
|
||||||
return writeXML(r, http.StatusOK, MIME_XML, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
|
|
||||||
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
|
|
||||||
return writeXML(r, status, MIME_XML, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAsJson is a convenience method for writing a value in json.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteAsJson(value interface{}) error {
|
|
||||||
return writeJSON(r, http.StatusOK, MIME_JSON, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteJson(value interface{}, contentType string) error {
|
|
||||||
return writeJSON(r, http.StatusOK, contentType, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
|
|
||||||
return writeJSON(r, status, contentType, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteError writes the http status and the error string on the response. err can be nil.
|
|
||||||
// Return an error if writing was not successful.
|
|
||||||
func (r *Response) WriteError(httpStatus int, err error) (writeErr error) {
|
|
||||||
r.err = err
|
|
||||||
if err == nil {
|
|
||||||
writeErr = r.WriteErrorString(httpStatus, "")
|
|
||||||
} else {
|
|
||||||
writeErr = r.WriteErrorString(httpStatus, err.Error())
|
|
||||||
}
|
|
||||||
return writeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteServiceError is a convenience method for a responding with a status and a ServiceError
|
|
||||||
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
|
|
||||||
r.err = err
|
|
||||||
return r.WriteHeaderAndEntity(httpStatus, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteErrorString is a convenience method for an error status with the actual error
|
|
||||||
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
|
|
||||||
if r.err == nil {
|
|
||||||
// if not called from WriteError
|
|
||||||
r.err = errors.New(errorReason)
|
|
||||||
}
|
|
||||||
r.WriteHeader(httpStatus)
|
|
||||||
if _, err := r.Write([]byte(errorReason)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush implements http.Flusher interface, which sends any buffered data to the client.
|
|
||||||
func (r *Response) Flush() {
|
|
||||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
|
||||||
f.Flush()
|
|
||||||
} else if trace {
|
|
||||||
traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader is overridden to remember the Status Code that has been written.
|
|
||||||
// Changes to the Header of the response have no effect after this.
|
|
||||||
func (r *Response) WriteHeader(httpStatus int) {
|
|
||||||
r.statusCode = httpStatus
|
|
||||||
r.ResponseWriter.WriteHeader(httpStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatusCode returns the code that has been written using WriteHeader.
|
|
||||||
func (r Response) StatusCode() int {
|
|
||||||
if 0 == r.statusCode {
|
|
||||||
// no status code has been written yet; assume OK
|
|
||||||
return http.StatusOK
|
|
||||||
}
|
|
||||||
return r.statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes the data to the connection as part of an HTTP reply.
|
|
||||||
// Write is part of http.ResponseWriter interface.
|
|
||||||
func (r *Response) Write(bytes []byte) (int, error) {
|
|
||||||
written, err := r.ResponseWriter.Write(bytes)
|
|
||||||
r.contentLength += written
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentLength returns the number of bytes written for the response content.
|
|
||||||
// Note that this value is only correct if all data is written through the Response using its Write* methods.
|
|
||||||
// Data written directly using the underlying http.ResponseWriter is not accounted for.
|
|
||||||
func (r Response) ContentLength() int {
|
|
||||||
return r.contentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseNotify is part of http.CloseNotifier interface
|
|
||||||
func (r Response) CloseNotify() <-chan bool {
|
|
||||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the err created by WriteError
|
|
||||||
func (r Response) Error() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
191
vendor/github.com/emicklei/go-restful/v3/route.go
generated
vendored
191
vendor/github.com/emicklei/go-restful/v3/route.go
generated
vendored
@@ -1,191 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouteFunction declares the signature of a function that can be bound to a Route.
|
|
||||||
type RouteFunction func(*Request, *Response)
|
|
||||||
|
|
||||||
// RouteSelectionConditionFunction declares the signature of a function that
|
|
||||||
// can be used to add extra conditional logic when selecting whether the route
|
|
||||||
// matches the HTTP request.
|
|
||||||
type RouteSelectionConditionFunction func(httpRequest *http.Request) bool
|
|
||||||
|
|
||||||
// Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
|
|
||||||
type Route struct {
|
|
||||||
ExtensionProperties
|
|
||||||
Method string
|
|
||||||
Produces []string
|
|
||||||
Consumes []string
|
|
||||||
Path string // webservice root path + described path
|
|
||||||
Function RouteFunction
|
|
||||||
Filters []FilterFunction
|
|
||||||
If []RouteSelectionConditionFunction
|
|
||||||
|
|
||||||
// cached values for dispatching
|
|
||||||
relativePath string
|
|
||||||
pathParts []string
|
|
||||||
pathExpr *pathExpression // cached compilation of relativePath as RegExp
|
|
||||||
|
|
||||||
// documentation
|
|
||||||
Doc string
|
|
||||||
Notes string
|
|
||||||
Operation string
|
|
||||||
ParameterDocs []*Parameter
|
|
||||||
ResponseErrors map[int]ResponseError
|
|
||||||
DefaultResponse *ResponseError
|
|
||||||
ReadSample, WriteSample interface{} // structs that model an example request or response payload
|
|
||||||
WriteSamples []interface{} // if more than one return types is possible (oneof) then this will contain multiple values
|
|
||||||
|
|
||||||
// Extra information used to store custom information about the route.
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
|
|
||||||
// marks a route as deprecated
|
|
||||||
Deprecated bool
|
|
||||||
|
|
||||||
//Overrides the container.contentEncodingEnabled
|
|
||||||
contentEncodingEnabled *bool
|
|
||||||
|
|
||||||
// indicate route path has custom verb
|
|
||||||
hasCustomVerb bool
|
|
||||||
|
|
||||||
// if a request does not include a content-type header then
|
|
||||||
// depending on the method, it may return a 415 Unsupported Media
|
|
||||||
// Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
|
|
||||||
allowedMethodsWithoutContentType []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize for Route
|
|
||||||
func (r *Route) postBuild() {
|
|
||||||
r.pathParts = tokenizePath(r.Path)
|
|
||||||
r.hasCustomVerb = hasCustomVerb(r.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Request and Response from their http versions
|
|
||||||
func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) {
|
|
||||||
wrappedRequest := NewRequest(httpRequest)
|
|
||||||
wrappedRequest.pathParameters = pathParams
|
|
||||||
wrappedRequest.selectedRoute = r
|
|
||||||
wrappedResponse := NewResponse(httpWriter)
|
|
||||||
wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
wrappedResponse.routeProduces = r.Produces
|
|
||||||
return wrappedRequest, wrappedResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringTrimSpaceCutset(r rune) bool {
|
|
||||||
return r == ' '
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether the mimeType matches to what this Route can produce.
|
|
||||||
func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
|
|
||||||
remaining := mimeTypesWithQuality
|
|
||||||
for {
|
|
||||||
var mimeType string
|
|
||||||
if end := strings.Index(remaining, ","); end == -1 {
|
|
||||||
mimeType, remaining = remaining, ""
|
|
||||||
} else {
|
|
||||||
mimeType, remaining = remaining[:end], remaining[end+1:]
|
|
||||||
}
|
|
||||||
if quality := strings.Index(mimeType, ";"); quality != -1 {
|
|
||||||
mimeType = mimeType[:quality]
|
|
||||||
}
|
|
||||||
mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
|
|
||||||
if mimeType == "*/*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, producibleType := range r.Produces {
|
|
||||||
if producibleType == "*/*" || producibleType == mimeType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(remaining) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
|
|
||||||
func (r Route) matchesContentType(mimeTypes string) bool {
|
|
||||||
|
|
||||||
if len(r.Consumes) == 0 {
|
|
||||||
// did not specify what it can consume ; any media type (“*/*”) is assumed
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mimeTypes) == 0 {
|
|
||||||
// idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
|
|
||||||
m := r.Method
|
|
||||||
// if route specifies less or non-idempotent methods then use that
|
|
||||||
if len(r.allowedMethodsWithoutContentType) > 0 {
|
|
||||||
for _, each := range r.allowedMethodsWithoutContentType {
|
|
||||||
if m == each {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// proceed with default
|
|
||||||
mimeTypes = MIME_OCTET
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining := mimeTypes
|
|
||||||
for {
|
|
||||||
var mimeType string
|
|
||||||
if end := strings.Index(remaining, ","); end == -1 {
|
|
||||||
mimeType, remaining = remaining, ""
|
|
||||||
} else {
|
|
||||||
mimeType, remaining = remaining[:end], remaining[end+1:]
|
|
||||||
}
|
|
||||||
if quality := strings.Index(mimeType, ";"); quality != -1 {
|
|
||||||
mimeType = mimeType[:quality]
|
|
||||||
}
|
|
||||||
mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset)
|
|
||||||
for _, consumeableType := range r.Consumes {
|
|
||||||
if consumeableType == "*/*" || consumeableType == mimeType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(remaining) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokenize an URL path using the slash separator ; the result does not have empty tokens
|
|
||||||
func tokenizePath(path string) []string {
|
|
||||||
if "/" == path {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if TrimRightSlashEnabled {
|
|
||||||
// 3.9.0
|
|
||||||
return strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
} else {
|
|
||||||
// 3.10.2
|
|
||||||
return strings.Split(strings.TrimLeft(path, "/"), "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debugging
|
|
||||||
func (r *Route) String() string {
|
|
||||||
return r.Method + " " + r.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value.
|
|
||||||
func (r *Route) EnableContentEncoding(enabled bool) {
|
|
||||||
r.contentEncodingEnabled = &enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrimRightSlashEnabled controls whether
|
|
||||||
// - path on route building is using path.Join
|
|
||||||
// - the path of the incoming request is trimmed of its slash suffux.
|
|
||||||
// Value of true matches the behavior of <= 3.9.0
|
|
||||||
var TrimRightSlashEnabled = true
|
|
||||||
389
vendor/github.com/emicklei/go-restful/v3/route_builder.go
generated
vendored
389
vendor/github.com/emicklei/go-restful/v3/route_builder.go
generated
vendored
@@ -1,389 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/v3/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouteBuilder is a helper to construct Routes.
|
|
||||||
type RouteBuilder struct {
|
|
||||||
rootPath string
|
|
||||||
currentPath string
|
|
||||||
produces []string
|
|
||||||
consumes []string
|
|
||||||
httpMethod string // required
|
|
||||||
function RouteFunction // required
|
|
||||||
filters []FilterFunction
|
|
||||||
conditions []RouteSelectionConditionFunction
|
|
||||||
allowedMethodsWithoutContentType []string // see Route
|
|
||||||
|
|
||||||
typeNameHandleFunc TypeNameHandleFunction // required
|
|
||||||
|
|
||||||
// documentation
|
|
||||||
doc string
|
|
||||||
notes string
|
|
||||||
operation string
|
|
||||||
readSample interface{}
|
|
||||||
writeSamples []interface{}
|
|
||||||
parameters []*Parameter
|
|
||||||
errorMap map[int]ResponseError
|
|
||||||
defaultResponse *ResponseError
|
|
||||||
metadata map[string]interface{}
|
|
||||||
extensions map[string]interface{}
|
|
||||||
deprecated bool
|
|
||||||
contentEncodingEnabled *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do evaluates each argument with the RouteBuilder itself.
|
|
||||||
// This allows you to follow DRY principles without breaking the fluent programming style.
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
|
|
||||||
//
|
|
||||||
// func Returns500(b *RouteBuilder) {
|
|
||||||
// b.Returns(500, "Internal Server Error", restful.ServiceError{})
|
|
||||||
// }
|
|
||||||
func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
|
|
||||||
for _, each := range oneArgBlocks {
|
|
||||||
each(b)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// To bind the route to a function.
|
|
||||||
// If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
|
|
||||||
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
|
|
||||||
b.function = function
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method specifies what HTTP method to match. Required.
|
|
||||||
func (b *RouteBuilder) Method(method string) *RouteBuilder {
|
|
||||||
b.httpMethod = method
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
|
|
||||||
func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
|
|
||||||
b.produces = mimeTypes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
|
|
||||||
func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
|
|
||||||
b.consumes = mimeTypes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
|
|
||||||
func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
|
|
||||||
b.currentPath = subPath
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doc tells what this route is all about. Optional.
|
|
||||||
func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
|
|
||||||
b.doc = documentation
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notes is a verbose explanation of the operation behavior. Optional.
|
|
||||||
func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
|
|
||||||
b.notes = notes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads tells what resource type will be read from the request payload. Optional.
|
|
||||||
// A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type.
|
|
||||||
func (b *RouteBuilder) Reads(sample interface{}, optionalDescription ...string) *RouteBuilder {
|
|
||||||
fn := b.typeNameHandleFunc
|
|
||||||
if fn == nil {
|
|
||||||
fn = reflectTypeName
|
|
||||||
}
|
|
||||||
typeAsName := fn(sample)
|
|
||||||
description := ""
|
|
||||||
if len(optionalDescription) > 0 {
|
|
||||||
description = optionalDescription[0]
|
|
||||||
}
|
|
||||||
b.readSample = sample
|
|
||||||
bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}}
|
|
||||||
bodyParameter.beBody()
|
|
||||||
bodyParameter.Required(true)
|
|
||||||
bodyParameter.DataType(typeAsName)
|
|
||||||
b.Param(bodyParameter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
|
|
||||||
// Use this to modify or extend information for the Parameter (through its Data()).
|
|
||||||
func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
|
|
||||||
for _, each := range b.parameters {
|
|
||||||
if each.Data().Name == name {
|
|
||||||
return each
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes tells which one of the resource types will be written as the response payload. Optional.
|
|
||||||
func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder {
|
|
||||||
b.writeSamples = samples // oneof
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
|
|
||||||
func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
|
|
||||||
if b.parameters == nil {
|
|
||||||
b.parameters = []*Parameter{}
|
|
||||||
}
|
|
||||||
b.parameters = append(b.parameters, parameter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation allows you to document what the actual method/function call is of the Route.
|
|
||||||
// Unless called, the operation name is derived from the RouteFunction set using To(..).
|
|
||||||
func (b *RouteBuilder) Operation(name string) *RouteBuilder {
|
|
||||||
b.operation = name
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReturnsError is deprecated, use Returns instead.
|
|
||||||
func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
|
|
||||||
log.Print("ReturnsError is deprecated, use Returns instead.")
|
|
||||||
return b.Returns(code, message, model)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns allows you to document what responses (errors or regular) can be expected.
|
|
||||||
// The model parameter is optional ; either pass a struct instance or use nil if not applicable.
|
|
||||||
func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
|
|
||||||
err := ResponseError{
|
|
||||||
Code: code,
|
|
||||||
Message: message,
|
|
||||||
Model: model,
|
|
||||||
IsDefault: false, // this field is deprecated, use default response instead.
|
|
||||||
}
|
|
||||||
// lazy init because there is no NewRouteBuilder (yet)
|
|
||||||
if b.errorMap == nil {
|
|
||||||
b.errorMap = map[int]ResponseError{}
|
|
||||||
}
|
|
||||||
b.errorMap[code] = err
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReturnsWithHeaders is similar to Returns, but can specify response headers
|
|
||||||
func (b *RouteBuilder) ReturnsWithHeaders(code int, message string, model interface{}, headers map[string]Header) *RouteBuilder {
|
|
||||||
b.Returns(code, message, model)
|
|
||||||
err := b.errorMap[code]
|
|
||||||
err.Headers = headers
|
|
||||||
b.errorMap[code] = err
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultReturns is a special Returns call that sets the default of the response.
|
|
||||||
func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
|
|
||||||
b.defaultResponse = &ResponseError{
|
|
||||||
Message: message,
|
|
||||||
Model: model,
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata adds or updates a key=value pair to the metadata map.
|
|
||||||
func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
|
|
||||||
if b.metadata == nil {
|
|
||||||
b.metadata = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
b.metadata[key] = value
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExtension adds or updates a key=value pair to the extensions map.
|
|
||||||
func (b *RouteBuilder) AddExtension(key string, value interface{}) *RouteBuilder {
|
|
||||||
if b.extensions == nil {
|
|
||||||
b.extensions = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
b.extensions[key] = value
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecate sets the value of deprecated to true. Deprecated routes have a special UI treatment to warn against use
|
|
||||||
func (b *RouteBuilder) Deprecate() *RouteBuilder {
|
|
||||||
b.deprecated = true
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowedMethodsWithoutContentType overrides the default list GET,HEAD,OPTIONS,DELETE,TRACE
|
|
||||||
// If a request does not include a content-type header then
|
|
||||||
// depending on the method, it may return a 415 Unsupported Media.
|
|
||||||
// Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
|
|
||||||
func (b *RouteBuilder) AllowedMethodsWithoutContentType(methods []string) *RouteBuilder {
|
|
||||||
b.allowedMethodsWithoutContentType = methods
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseError represents a response; not necessarily an error.
|
|
||||||
type ResponseError struct {
|
|
||||||
ExtensionProperties
|
|
||||||
Code int
|
|
||||||
Message string
|
|
||||||
Model interface{}
|
|
||||||
Headers map[string]Header
|
|
||||||
IsDefault bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header describes a header for a response of the API
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#headerObject
|
|
||||||
type Header struct {
|
|
||||||
*Items
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items describe swagger simple schemas for headers
|
|
||||||
type Items struct {
|
|
||||||
Type string
|
|
||||||
Format string
|
|
||||||
Items *Items
|
|
||||||
CollectionFormat string
|
|
||||||
Default interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
|
|
||||||
b.rootPath = path
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a FilterFunction to the end of filters for this Route to build.
|
|
||||||
func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
|
|
||||||
b.filters = append(b.filters, filter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// If sets a condition function that controls matching the Route based on custom logic.
|
|
||||||
// The condition function is provided the HTTP request and should return true if the route
|
|
||||||
// should be considered.
|
|
||||||
//
|
|
||||||
// Efficiency note: the condition function is called before checking the method, produces, and
|
|
||||||
// consumes criteria, so that the correct HTTP status code can be returned.
|
|
||||||
//
|
|
||||||
// Lifecycle note: no filter functions have been called prior to calling the condition function,
|
|
||||||
// so the condition function should not depend on any context that might be set up by container
|
|
||||||
// or route filters.
|
|
||||||
func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
|
|
||||||
b.conditions = append(b.conditions, condition)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentEncodingEnabled allows you to override the Containers value for auto-compressing this route response.
|
|
||||||
func (b *RouteBuilder) ContentEncodingEnabled(enabled bool) *RouteBuilder {
|
|
||||||
b.contentEncodingEnabled = &enabled
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no specific Route path then set to rootPath
|
|
||||||
// If no specific Produces then set to rootProduces
|
|
||||||
// If no specific Consumes then set to rootConsumes
|
|
||||||
func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
|
|
||||||
if len(b.produces) == 0 {
|
|
||||||
b.produces = rootProduces
|
|
||||||
}
|
|
||||||
if len(b.consumes) == 0 {
|
|
||||||
b.consumes = rootConsumes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeNameHandler sets the function that will convert types to strings in the parameter
|
|
||||||
// and model definitions.
|
|
||||||
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
|
|
||||||
b.typeNameHandleFunc = handler
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build creates a new Route using the specification details collected by the RouteBuilder
|
|
||||||
func (b *RouteBuilder) Build() Route {
|
|
||||||
pathExpr, err := newPathExpression(b.currentPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Invalid path:%s because:%v", b.currentPath, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if b.function == nil {
|
|
||||||
log.Printf("No function specified for route:" + b.currentPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
operationName := b.operation
|
|
||||||
if len(operationName) == 0 && b.function != nil {
|
|
||||||
// extract from definition
|
|
||||||
operationName = nameOfFunction(b.function)
|
|
||||||
}
|
|
||||||
route := Route{
|
|
||||||
Method: b.httpMethod,
|
|
||||||
Path: concatPath(b.rootPath, b.currentPath),
|
|
||||||
Produces: b.produces,
|
|
||||||
Consumes: b.consumes,
|
|
||||||
Function: b.function,
|
|
||||||
Filters: b.filters,
|
|
||||||
If: b.conditions,
|
|
||||||
relativePath: b.currentPath,
|
|
||||||
pathExpr: pathExpr,
|
|
||||||
Doc: b.doc,
|
|
||||||
Notes: b.notes,
|
|
||||||
Operation: operationName,
|
|
||||||
ParameterDocs: b.parameters,
|
|
||||||
ResponseErrors: b.errorMap,
|
|
||||||
DefaultResponse: b.defaultResponse,
|
|
||||||
ReadSample: b.readSample,
|
|
||||||
WriteSamples: b.writeSamples,
|
|
||||||
Metadata: b.metadata,
|
|
||||||
Deprecated: b.deprecated,
|
|
||||||
contentEncodingEnabled: b.contentEncodingEnabled,
|
|
||||||
allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType,
|
|
||||||
}
|
|
||||||
// set WriteSample if one specified
|
|
||||||
if len(b.writeSamples) == 1 {
|
|
||||||
route.WriteSample = b.writeSamples[0]
|
|
||||||
}
|
|
||||||
route.Extensions = b.extensions
|
|
||||||
route.postBuild()
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge two paths using the current (package global) merge path strategy.
|
|
||||||
func concatPath(rootPath, routePath string) string {
|
|
||||||
|
|
||||||
if TrimRightSlashEnabled {
|
|
||||||
return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/")
|
|
||||||
} else {
|
|
||||||
return path.Join(rootPath, routePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var anonymousFuncCount int32
|
|
||||||
|
|
||||||
// nameOfFunction returns the short name of the function f for documentation.
|
|
||||||
// It uses a runtime feature for debugging ; its value may change for later Go versions.
|
|
||||||
func nameOfFunction(f interface{}) string {
|
|
||||||
fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
|
|
||||||
tokenized := strings.Split(fun.Name(), ".")
|
|
||||||
last := tokenized[len(tokenized)-1]
|
|
||||||
last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, ")-fm") // Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, "·fm") // < Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, "-fm") // Go 1.5
|
|
||||||
if last == "func1" { // this could mean conflicts in API docs
|
|
||||||
val := atomic.AddInt32(&anonymousFuncCount, 1)
|
|
||||||
last = "func" + fmt.Sprintf("%d", val)
|
|
||||||
atomic.StoreInt32(&anonymousFuncCount, val)
|
|
||||||
}
|
|
||||||
return last
|
|
||||||
}
|
|
||||||
66
vendor/github.com/emicklei/go-restful/v3/route_reader.go
generated
vendored
66
vendor/github.com/emicklei/go-restful/v3/route_reader.go
generated
vendored
@@ -1,66 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2021 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
type RouteReader interface {
|
|
||||||
Method() string
|
|
||||||
Consumes() []string
|
|
||||||
Path() string
|
|
||||||
Doc() string
|
|
||||||
Notes() string
|
|
||||||
Operation() string
|
|
||||||
ParameterDocs() []*Parameter
|
|
||||||
// Returns a copy
|
|
||||||
Metadata() map[string]interface{}
|
|
||||||
Deprecated() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type routeAccessor struct {
|
|
||||||
route *Route
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeAccessor) Method() string {
|
|
||||||
return r.route.Method
|
|
||||||
}
|
|
||||||
func (r routeAccessor) Consumes() []string {
|
|
||||||
return r.route.Consumes[:]
|
|
||||||
}
|
|
||||||
func (r routeAccessor) Path() string {
|
|
||||||
return r.route.Path
|
|
||||||
}
|
|
||||||
func (r routeAccessor) Doc() string {
|
|
||||||
return r.route.Doc
|
|
||||||
}
|
|
||||||
func (r routeAccessor) Notes() string {
|
|
||||||
return r.route.Notes
|
|
||||||
}
|
|
||||||
func (r routeAccessor) Operation() string {
|
|
||||||
return r.route.Operation
|
|
||||||
}
|
|
||||||
func (r routeAccessor) ParameterDocs() []*Parameter {
|
|
||||||
return r.route.ParameterDocs[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a copy
|
|
||||||
func (r routeAccessor) Metadata() map[string]interface{} {
|
|
||||||
return copyMap(r.route.Metadata)
|
|
||||||
}
|
|
||||||
func (r routeAccessor) Deprecated() bool {
|
|
||||||
return r.route.Deprecated
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/23057785/how-to-copy-a-map
|
|
||||||
func copyMap(m map[string]interface{}) map[string]interface{} {
|
|
||||||
cp := make(map[string]interface{})
|
|
||||||
for k, v := range m {
|
|
||||||
vm, ok := v.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
cp[k] = copyMap(vm)
|
|
||||||
} else {
|
|
||||||
cp[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
20
vendor/github.com/emicklei/go-restful/v3/router.go
generated
vendored
20
vendor/github.com/emicklei/go-restful/v3/router.go
generated
vendored
@@ -1,20 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// A RouteSelector finds the best matching Route given the input HTTP Request
|
|
||||||
// RouteSelectors can optionally also implement the PathProcessor interface to also calculate the
|
|
||||||
// path parameters after the route has been selected.
|
|
||||||
type RouteSelector interface {
|
|
||||||
|
|
||||||
// SelectRoute finds a Route given the input HTTP Request and a list of WebServices.
|
|
||||||
// It returns a selected Route and its containing WebService or an error indicating
|
|
||||||
// a problem.
|
|
||||||
SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error)
|
|
||||||
}
|
|
||||||
32
vendor/github.com/emicklei/go-restful/v3/service_error.go
generated
vendored
32
vendor/github.com/emicklei/go-restful/v3/service_error.go
generated
vendored
@@ -1,32 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServiceError is a transport object to pass information about a non-Http error occurred in a WebService while processing a request.
|
|
||||||
type ServiceError struct {
|
|
||||||
Code int
|
|
||||||
Message string
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewError returns a ServiceError using the code and reason
|
|
||||||
func NewError(code int, message string) ServiceError {
|
|
||||||
return ServiceError{Code: code, Message: message}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewErrorWithHeader returns a ServiceError using the code, reason and header
|
|
||||||
func NewErrorWithHeader(code int, message string, header http.Header) ServiceError {
|
|
||||||
return ServiceError{Code: code, Message: message, Header: header}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns a text representation of the service error
|
|
||||||
func (s ServiceError) Error() string {
|
|
||||||
return fmt.Sprintf("[ServiceError:%v] %v", s.Code, s.Message)
|
|
||||||
}
|
|
||||||
305
vendor/github.com/emicklei/go-restful/v3/web_service.go
generated
vendored
305
vendor/github.com/emicklei/go-restful/v3/web_service.go
generated
vendored
@@ -1,305 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/v3/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// WebService holds a collection of Route values that bind a Http Method + URL Path to a function.
|
|
||||||
type WebService struct {
|
|
||||||
rootPath string
|
|
||||||
pathExpr *pathExpression // cached compilation of rootPath as RegExp
|
|
||||||
routes []Route
|
|
||||||
produces []string
|
|
||||||
consumes []string
|
|
||||||
pathParameters []*Parameter
|
|
||||||
filters []FilterFunction
|
|
||||||
documentation string
|
|
||||||
apiVersion string
|
|
||||||
|
|
||||||
typeNameHandleFunc TypeNameHandleFunction
|
|
||||||
|
|
||||||
dynamicRoutes bool
|
|
||||||
|
|
||||||
// protects 'routes' if dynamic routes are enabled
|
|
||||||
routesLock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebService) SetDynamicRoutes(enable bool) {
|
|
||||||
w.dynamicRoutes = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeNameHandleFunction declares functions that can handle translating the name of a sample object
|
|
||||||
// into the restful documentation for the service.
|
|
||||||
type TypeNameHandleFunction func(sample interface{}) string
|
|
||||||
|
|
||||||
// TypeNameHandler sets the function that will convert types to strings in the parameter
|
|
||||||
// and model definitions. If not set, the web service will invoke
|
|
||||||
// reflect.TypeOf(object).String().
|
|
||||||
func (w *WebService) TypeNameHandler(handler TypeNameHandleFunction) *WebService {
|
|
||||||
w.typeNameHandleFunc = handler
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// reflectTypeName is the default TypeNameHandleFunction and for a given object
|
|
||||||
// returns the name that Go identifies it with (e.g. "string" or "v1.Object") via
|
|
||||||
// the reflection API.
|
|
||||||
func reflectTypeName(sample interface{}) string {
|
|
||||||
return reflect.TypeOf(sample).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it.
|
|
||||||
func (w *WebService) compilePathExpression() {
|
|
||||||
compiled, err := newPathExpression(w.rootPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("invalid path:%s because:%v", w.rootPath, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
w.pathExpr = compiled
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApiVersion sets the API version for documentation purposes.
|
|
||||||
func (w *WebService) ApiVersion(apiVersion string) *WebService {
|
|
||||||
w.apiVersion = apiVersion
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the API version for documentation purposes.
|
|
||||||
func (w *WebService) Version() string { return w.apiVersion }
|
|
||||||
|
|
||||||
// Path specifies the root URL template path of the WebService.
|
|
||||||
// All Routes will be relative to this path.
|
|
||||||
func (w *WebService) Path(root string) *WebService {
|
|
||||||
w.rootPath = root
|
|
||||||
if len(w.rootPath) == 0 {
|
|
||||||
w.rootPath = "/"
|
|
||||||
}
|
|
||||||
w.compilePathExpression()
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Param adds a PathParameter to document parameters used in the root path.
|
|
||||||
func (w *WebService) Param(parameter *Parameter) *WebService {
|
|
||||||
if w.pathParameters == nil {
|
|
||||||
w.pathParameters = []*Parameter{}
|
|
||||||
}
|
|
||||||
w.pathParameters = append(w.pathParameters, parameter)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter creates a new Parameter of kind Path for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func (w *WebService) PathParameter(name, description string) *Parameter {
|
|
||||||
return PathParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter creates a new Parameter of kind Path for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func PathParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: true, DataType: "string"}}
|
|
||||||
p.bePath()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter creates a new Parameter of kind Query for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func (w *WebService) QueryParameter(name, description string) *Parameter {
|
|
||||||
return QueryParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter creates a new Parameter of kind Query for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func QueryParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string", CollectionFormat: CollectionFormatCSV.String()}}
|
|
||||||
p.beQuery()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter creates a new Parameter of kind Body for documentation purposes.
|
|
||||||
// It is initialized as required without a DataType.
|
|
||||||
func (w *WebService) BodyParameter(name, description string) *Parameter {
|
|
||||||
return BodyParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter creates a new Parameter of kind Body for documentation purposes.
|
|
||||||
// It is initialized as required without a DataType.
|
|
||||||
func BodyParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: true}}
|
|
||||||
p.beBody()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func (w *WebService) HeaderParameter(name, description string) *Parameter {
|
|
||||||
return HeaderParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func HeaderParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}}
|
|
||||||
p.beHeader()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func (w *WebService) FormParameter(name, description string) *Parameter {
|
|
||||||
return FormParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func FormParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}}
|
|
||||||
p.beForm()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiPartFormParameter creates a new Parameter of kind Form (using multipart/form-data) for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func (w *WebService) MultiPartFormParameter(name, description string) *Parameter {
|
|
||||||
return MultiPartFormParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MultiPartFormParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}}
|
|
||||||
p.beMultiPartForm()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route creates a new Route using the RouteBuilder and add to the ordered list of Routes.
|
|
||||||
func (w *WebService) Route(builder *RouteBuilder) *WebService {
|
|
||||||
w.routesLock.Lock()
|
|
||||||
defer w.routesLock.Unlock()
|
|
||||||
builder.copyDefaults(w.produces, w.consumes)
|
|
||||||
w.routes = append(w.routes, builder.Build())
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveRoute removes the specified route, looks for something that matches 'path' and 'method'
|
|
||||||
func (w *WebService) RemoveRoute(path, method string) error {
|
|
||||||
if !w.dynamicRoutes {
|
|
||||||
return errors.New("dynamic routes are not enabled.")
|
|
||||||
}
|
|
||||||
w.routesLock.Lock()
|
|
||||||
defer w.routesLock.Unlock()
|
|
||||||
newRoutes := []Route{}
|
|
||||||
for _, route := range w.routes {
|
|
||||||
if route.Method == method && route.Path == path {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newRoutes = append(newRoutes, route)
|
|
||||||
}
|
|
||||||
w.routes = newRoutes
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method creates a new RouteBuilder and initialize its http method
|
|
||||||
func (w *WebService) Method(httpMethod string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method(httpMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produces specifies that this WebService can produce one or more MIME types.
|
|
||||||
// Http requests must have one of these values set for the Accept header.
|
|
||||||
func (w *WebService) Produces(contentTypes ...string) *WebService {
|
|
||||||
w.produces = contentTypes
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumes specifies that this WebService can consume one or more MIME types.
|
|
||||||
// Http requests must have one of these values set for the Content-Type header.
|
|
||||||
func (w *WebService) Consumes(accepts ...string) *WebService {
|
|
||||||
w.consumes = accepts
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routes returns the Routes associated with this WebService
|
|
||||||
func (w *WebService) Routes() []Route {
|
|
||||||
if !w.dynamicRoutes {
|
|
||||||
return w.routes
|
|
||||||
}
|
|
||||||
// Make a copy of the array to prevent concurrency problems
|
|
||||||
w.routesLock.RLock()
|
|
||||||
defer w.routesLock.RUnlock()
|
|
||||||
result := make([]Route, len(w.routes))
|
|
||||||
for ix := range w.routes {
|
|
||||||
result[ix] = w.routes[ix]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootPath returns the RootPath associated with this WebService. Default "/"
|
|
||||||
func (w *WebService) RootPath() string {
|
|
||||||
return w.rootPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameters return the path parameter names for (shared among its Routes)
|
|
||||||
func (w *WebService) PathParameters() []*Parameter {
|
|
||||||
return w.pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter adds a filter function to the chain of filters applicable to all its Routes
|
|
||||||
func (w *WebService) Filter(filter FilterFunction) *WebService {
|
|
||||||
w.filters = append(w.filters, filter)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doc is used to set the documentation of this service.
|
|
||||||
func (w *WebService) Doc(plainText string) *WebService {
|
|
||||||
w.documentation = plainText
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Documentation returns it.
|
|
||||||
func (w *WebService) Documentation() string {
|
|
||||||
return w.documentation
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convenience methods
|
|
||||||
*/
|
|
||||||
|
|
||||||
// HEAD is a shortcut for .Method("HEAD").Path(subPath)
|
|
||||||
func (w *WebService) HEAD(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("HEAD").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET is a shortcut for .Method("GET").Path(subPath)
|
|
||||||
func (w *WebService) GET(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST is a shortcut for .Method("POST").Path(subPath)
|
|
||||||
func (w *WebService) POST(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("POST").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT is a shortcut for .Method("PUT").Path(subPath)
|
|
||||||
func (w *WebService) PUT(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PUT").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH is a shortcut for .Method("PATCH").Path(subPath)
|
|
||||||
func (w *WebService) PATCH(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PATCH").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE is a shortcut for .Method("DELETE").Path(subPath)
|
|
||||||
func (w *WebService) DELETE(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("DELETE").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPTIONS is a shortcut for .Method("OPTIONS").Path(subPath)
|
|
||||||
func (w *WebService) OPTIONS(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("OPTIONS").Path(subPath)
|
|
||||||
}
|
|
||||||
39
vendor/github.com/emicklei/go-restful/v3/web_service_container.go
generated
vendored
39
vendor/github.com/emicklei/go-restful/v3/web_service_container.go
generated
vendored
@@ -1,39 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultContainer is a restful.Container that uses http.DefaultServeMux
|
|
||||||
var DefaultContainer *Container
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
DefaultContainer = NewContainer()
|
|
||||||
DefaultContainer.ServeMux = http.DefaultServeMux
|
|
||||||
}
|
|
||||||
|
|
||||||
// If set the true then panics will not be caught to return HTTP 500.
|
|
||||||
// In that case, Route functions are responsible for handling any error situation.
|
|
||||||
// Default value is false = recover from panics. This has performance implications.
|
|
||||||
// OBSOLETE ; use restful.DefaultContainer.DoNotRecover(true)
|
|
||||||
var DoNotRecover = false
|
|
||||||
|
|
||||||
// Add registers a new WebService add it to the DefaultContainer.
|
|
||||||
func Add(service *WebService) {
|
|
||||||
DefaultContainer.Add(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a container FilterFunction from the DefaultContainer.
|
|
||||||
// These are called before dispatching a http.Request to a WebService.
|
|
||||||
func Filter(filter FilterFunction) {
|
|
||||||
DefaultContainer.Filter(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisteredWebServices returns the collections of WebServices from the DefaultContainer
|
|
||||||
func RegisteredWebServices() []*WebService {
|
|
||||||
return DefaultContainer.RegisteredWebServices()
|
|
||||||
}
|
|
||||||
6
vendor/github.com/evanphx/json-patch/.gitignore
generated
vendored
6
vendor/github.com/evanphx/json-patch/.gitignore
generated
vendored
@@ -1,6 +0,0 @@
|
|||||||
# editor and IDE paraphernalia
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
|
|
||||||
# macOS paraphernalia
|
|
||||||
.DS_Store
|
|
||||||
25
vendor/github.com/evanphx/json-patch/LICENSE
generated
vendored
25
vendor/github.com/evanphx/json-patch/LICENSE
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
Copyright (c) 2014, Evan Phoenix
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the Evan Phoenix nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
317
vendor/github.com/evanphx/json-patch/README.md
generated
vendored
317
vendor/github.com/evanphx/json-patch/README.md
generated
vendored
@@ -1,317 +0,0 @@
|
|||||||
# JSON-Patch
|
|
||||||
`jsonpatch` is a library which provides functionality for both applying
|
|
||||||
[RFC6902 JSON patches](http://tools.ietf.org/html/rfc6902) against documents, as
|
|
||||||
well as for calculating & applying [RFC7396 JSON merge patches](https://tools.ietf.org/html/rfc7396).
|
|
||||||
|
|
||||||
[](http://godoc.org/github.com/evanphx/json-patch)
|
|
||||||
[](https://travis-ci.org/evanphx/json-patch)
|
|
||||||
[](https://goreportcard.com/report/github.com/evanphx/json-patch)
|
|
||||||
|
|
||||||
# Get It!
|
|
||||||
|
|
||||||
**Latest and greatest**:
|
|
||||||
```bash
|
|
||||||
go get -u github.com/evanphx/json-patch/v5
|
|
||||||
```
|
|
||||||
|
|
||||||
**Stable Versions**:
|
|
||||||
* Version 5: `go get -u gopkg.in/evanphx/json-patch.v5`
|
|
||||||
* Version 4: `go get -u gopkg.in/evanphx/json-patch.v4`
|
|
||||||
|
|
||||||
(previous versions below `v3` are unavailable)
|
|
||||||
|
|
||||||
# Use It!
|
|
||||||
* [Create and apply a merge patch](#create-and-apply-a-merge-patch)
|
|
||||||
* [Create and apply a JSON Patch](#create-and-apply-a-json-patch)
|
|
||||||
* [Comparing JSON documents](#comparing-json-documents)
|
|
||||||
* [Combine merge patches](#combine-merge-patches)
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
|
|
||||||
* There is a global configuration variable `jsonpatch.SupportNegativeIndices`.
|
|
||||||
This defaults to `true` and enables the non-standard practice of allowing
|
|
||||||
negative indices to mean indices starting at the end of an array. This
|
|
||||||
functionality can be disabled by setting `jsonpatch.SupportNegativeIndices =
|
|
||||||
false`.
|
|
||||||
|
|
||||||
* There is a global configuration variable `jsonpatch.AccumulatedCopySizeLimit`,
|
|
||||||
which limits the total size increase in bytes caused by "copy" operations in a
|
|
||||||
patch. It defaults to 0, which means there is no limit.
|
|
||||||
|
|
||||||
These global variables control the behavior of `jsonpatch.Apply`.
|
|
||||||
|
|
||||||
An alternative to `jsonpatch.Apply` is `jsonpatch.ApplyWithOptions` whose behavior
|
|
||||||
is controlled by an `options` parameter of type `*jsonpatch.ApplyOptions`.
|
|
||||||
|
|
||||||
Structure `jsonpatch.ApplyOptions` includes the configuration options above
|
|
||||||
and adds two new options: `AllowMissingPathOnRemove` and `EnsurePathExistsOnAdd`.
|
|
||||||
|
|
||||||
When `AllowMissingPathOnRemove` is set to `true`, `jsonpatch.ApplyWithOptions` will ignore
|
|
||||||
`remove` operations whose `path` points to a non-existent location in the JSON document.
|
|
||||||
`AllowMissingPathOnRemove` defaults to `false` which will lead to `jsonpatch.ApplyWithOptions`
|
|
||||||
returning an error when hitting a missing `path` on `remove`.
|
|
||||||
|
|
||||||
When `EnsurePathExistsOnAdd` is set to `true`, `jsonpatch.ApplyWithOptions` will make sure
|
|
||||||
that `add` operations produce all the `path` elements that are missing from the target object.
|
|
||||||
|
|
||||||
Use `jsonpatch.NewApplyOptions` to create an instance of `jsonpatch.ApplyOptions`
|
|
||||||
whose values are populated from the global configuration variables.
|
|
||||||
|
|
||||||
## Create and apply a merge patch
|
|
||||||
Given both an original JSON document and a modified JSON document, you can create
|
|
||||||
a [Merge Patch](https://tools.ietf.org/html/rfc7396) document.
|
|
||||||
|
|
||||||
It can describe the changes needed to convert from the original to the
|
|
||||||
modified JSON document.
|
|
||||||
|
|
||||||
Once you have a merge patch, you can apply it to other JSON documents using the
|
|
||||||
`jsonpatch.MergePatch(document, patch)` function.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Let's create a merge patch from these two documents...
|
|
||||||
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
|
|
||||||
target := []byte(`{"name": "Jane", "age": 24}`)
|
|
||||||
|
|
||||||
patch, err := jsonpatch.CreateMergePatch(original, target)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now lets apply the patch against a different JSON document...
|
|
||||||
|
|
||||||
alternative := []byte(`{"name": "Tina", "age": 28, "height": 3.75}`)
|
|
||||||
modifiedAlternative, err := jsonpatch.MergePatch(alternative, patch)
|
|
||||||
|
|
||||||
fmt.Printf("patch document: %s\n", patch)
|
|
||||||
fmt.Printf("updated alternative doc: %s\n", modifiedAlternative)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When ran, you get the following output:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go run main.go
|
|
||||||
patch document: {"height":null,"name":"Jane"}
|
|
||||||
updated alternative doc: {"age":28,"name":"Jane"}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create and apply a JSON Patch
|
|
||||||
You can create patch objects using `DecodePatch([]byte)`, which can then
|
|
||||||
be applied against JSON documents.
|
|
||||||
|
|
||||||
The following is an example of creating a patch from two operations, and
|
|
||||||
applying it against a JSON document.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
|
|
||||||
patchJSON := []byte(`[
|
|
||||||
{"op": "replace", "path": "/name", "value": "Jane"},
|
|
||||||
{"op": "remove", "path": "/height"}
|
|
||||||
]`)
|
|
||||||
|
|
||||||
patch, err := jsonpatch.DecodePatch(patchJSON)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
modified, err := patch.Apply(original)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Original document: %s\n", original)
|
|
||||||
fmt.Printf("Modified document: %s\n", modified)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When ran, you get the following output:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go run main.go
|
|
||||||
Original document: {"name": "John", "age": 24, "height": 3.21}
|
|
||||||
Modified document: {"age":24,"name":"Jane"}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Comparing JSON documents
|
|
||||||
Due to potential whitespace and ordering differences, one cannot simply compare
|
|
||||||
JSON strings or byte-arrays directly.
|
|
||||||
|
|
||||||
As such, you can instead use `jsonpatch.Equal(document1, document2)` to
|
|
||||||
determine if two JSON documents are _structurally_ equal. This ignores
|
|
||||||
whitespace differences, and key-value ordering.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
|
|
||||||
similar := []byte(`
|
|
||||||
{
|
|
||||||
"age": 24,
|
|
||||||
"height": 3.21,
|
|
||||||
"name": "John"
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
different := []byte(`{"name": "Jane", "age": 20, "height": 3.37}`)
|
|
||||||
|
|
||||||
if jsonpatch.Equal(original, similar) {
|
|
||||||
fmt.Println(`"original" is structurally equal to "similar"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !jsonpatch.Equal(original, different) {
|
|
||||||
fmt.Println(`"original" is _not_ structurally equal to "different"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When ran, you get the following output:
|
|
||||||
```bash
|
|
||||||
$ go run main.go
|
|
||||||
"original" is structurally equal to "similar"
|
|
||||||
"original" is _not_ structurally equal to "different"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Combine merge patches
|
|
||||||
Given two JSON merge patch documents, it is possible to combine them into a
|
|
||||||
single merge patch which can describe both set of changes.
|
|
||||||
|
|
||||||
The resulting merge patch can be used such that applying it results in a
|
|
||||||
document structurally similar as merging each merge patch to the document
|
|
||||||
in succession.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
original := []byte(`{"name": "John", "age": 24, "height": 3.21}`)
|
|
||||||
|
|
||||||
nameAndHeight := []byte(`{"height":null,"name":"Jane"}`)
|
|
||||||
ageAndEyes := []byte(`{"age":4.23,"eyes":"blue"}`)
|
|
||||||
|
|
||||||
// Let's combine these merge patch documents...
|
|
||||||
combinedPatch, err := jsonpatch.MergeMergePatches(nameAndHeight, ageAndEyes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply each patch individual against the original document
|
|
||||||
withoutCombinedPatch, err := jsonpatch.MergePatch(original, nameAndHeight)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
withoutCombinedPatch, err = jsonpatch.MergePatch(withoutCombinedPatch, ageAndEyes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the combined patch against the original document
|
|
||||||
|
|
||||||
withCombinedPatch, err := jsonpatch.MergePatch(original, combinedPatch)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do both result in the same thing? They should!
|
|
||||||
if jsonpatch.Equal(withCombinedPatch, withoutCombinedPatch) {
|
|
||||||
fmt.Println("Both JSON documents are structurally the same!")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("combined merge patch: %s", combinedPatch)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When ran, you get the following output:
|
|
||||||
```bash
|
|
||||||
$ go run main.go
|
|
||||||
Both JSON documents are structurally the same!
|
|
||||||
combined merge patch: {"age":4.23,"eyes":"blue","height":null,"name":"Jane"}
|
|
||||||
```
|
|
||||||
|
|
||||||
# CLI for comparing JSON documents
|
|
||||||
You can install the commandline program `json-patch`.
|
|
||||||
|
|
||||||
This program can take multiple JSON patch documents as arguments,
|
|
||||||
and fed a JSON document from `stdin`. It will apply the patch(es) against
|
|
||||||
the document and output the modified doc.
|
|
||||||
|
|
||||||
**patch.1.json**
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"op": "replace", "path": "/name", "value": "Jane"},
|
|
||||||
{"op": "remove", "path": "/height"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**patch.2.json**
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"op": "add", "path": "/address", "value": "123 Main St"},
|
|
||||||
{"op": "replace", "path": "/age", "value": "21"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**document.json**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "John",
|
|
||||||
"age": 24,
|
|
||||||
"height": 3.21
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go install github.com/evanphx/json-patch/cmd/json-patch
|
|
||||||
$ cat document.json | json-patch -p patch.1.json -p patch.2.json
|
|
||||||
{"address":"123 Main St","age":"21","name":"Jane"}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Help It!
|
|
||||||
Contributions are welcomed! Leave [an issue](https://github.com/evanphx/json-patch/issues)
|
|
||||||
or [create a PR](https://github.com/evanphx/json-patch/compare).
|
|
||||||
|
|
||||||
|
|
||||||
Before creating a pull request, we'd ask that you make sure tests are passing
|
|
||||||
and that you have added new tests when applicable.
|
|
||||||
|
|
||||||
Contributors can run tests using:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test -cover ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
Builds for pull requests are tested automatically
|
|
||||||
using [TravisCI](https://travis-ci.org/evanphx/json-patch).
|
|
||||||
38
vendor/github.com/evanphx/json-patch/errors.go
generated
vendored
38
vendor/github.com/evanphx/json-patch/errors.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
package jsonpatch
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// AccumulatedCopySizeError is an error type returned when the accumulated size
|
|
||||||
// increase caused by copy operations in a patch operation has exceeded the
|
|
||||||
// limit.
|
|
||||||
type AccumulatedCopySizeError struct {
|
|
||||||
limit int64
|
|
||||||
accumulated int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAccumulatedCopySizeError returns an AccumulatedCopySizeError.
|
|
||||||
func NewAccumulatedCopySizeError(l, a int64) *AccumulatedCopySizeError {
|
|
||||||
return &AccumulatedCopySizeError{limit: l, accumulated: a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (a *AccumulatedCopySizeError) Error() string {
|
|
||||||
return fmt.Sprintf("Unable to complete the copy, the accumulated size increase of copy is %d, exceeding the limit %d", a.accumulated, a.limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArraySizeError is an error type returned when the array size has exceeded
|
|
||||||
// the limit.
|
|
||||||
type ArraySizeError struct {
|
|
||||||
limit int
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArraySizeError returns an ArraySizeError.
|
|
||||||
func NewArraySizeError(l, s int) *ArraySizeError {
|
|
||||||
return &ArraySizeError{limit: l, size: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (a *ArraySizeError) Error() string {
|
|
||||||
return fmt.Sprintf("Unable to create array of size %d, limit is %d", a.size, a.limit)
|
|
||||||
}
|
|
||||||
389
vendor/github.com/evanphx/json-patch/merge.go
generated
vendored
389
vendor/github.com/evanphx/json-patch/merge.go
generated
vendored
@@ -1,389 +0,0 @@
|
|||||||
package jsonpatch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func merge(cur, patch *lazyNode, mergeMerge bool) *lazyNode {
|
|
||||||
curDoc, err := cur.intoDoc()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
pruneNulls(patch)
|
|
||||||
return patch
|
|
||||||
}
|
|
||||||
|
|
||||||
patchDoc, err := patch.intoDoc()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return patch
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeDocs(curDoc, patchDoc, mergeMerge)
|
|
||||||
|
|
||||||
return cur
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
|
|
||||||
for k, v := range *patch {
|
|
||||||
if v == nil {
|
|
||||||
if mergeMerge {
|
|
||||||
(*doc)[k] = nil
|
|
||||||
} else {
|
|
||||||
delete(*doc, k)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cur, ok := (*doc)[k]
|
|
||||||
|
|
||||||
if !ok || cur == nil {
|
|
||||||
if !mergeMerge {
|
|
||||||
pruneNulls(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*doc)[k] = v
|
|
||||||
} else {
|
|
||||||
(*doc)[k] = merge(cur, v, mergeMerge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pruneNulls(n *lazyNode) {
|
|
||||||
sub, err := n.intoDoc()
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
pruneDocNulls(sub)
|
|
||||||
} else {
|
|
||||||
ary, err := n.intoAry()
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
pruneAryNulls(ary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pruneDocNulls(doc *partialDoc) *partialDoc {
|
|
||||||
for k, v := range *doc {
|
|
||||||
if v == nil {
|
|
||||||
delete(*doc, k)
|
|
||||||
} else {
|
|
||||||
pruneNulls(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
func pruneAryNulls(ary *partialArray) *partialArray {
|
|
||||||
newAry := []*lazyNode{}
|
|
||||||
|
|
||||||
for _, v := range *ary {
|
|
||||||
if v != nil {
|
|
||||||
pruneNulls(v)
|
|
||||||
}
|
|
||||||
newAry = append(newAry, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
*ary = newAry
|
|
||||||
|
|
||||||
return ary
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrBadJSONDoc = fmt.Errorf("Invalid JSON Document")
|
|
||||||
var ErrBadJSONPatch = fmt.Errorf("Invalid JSON Patch")
|
|
||||||
var errBadMergeTypes = fmt.Errorf("Mismatched JSON Documents")
|
|
||||||
|
|
||||||
// MergeMergePatches merges two merge patches together, such that
|
|
||||||
// applying this resulting merged merge patch to a document yields the same
|
|
||||||
// as merging each merge patch to the document in succession.
|
|
||||||
func MergeMergePatches(patch1Data, patch2Data []byte) ([]byte, error) {
|
|
||||||
return doMergePatch(patch1Data, patch2Data, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePatch merges the patchData into the docData.
|
|
||||||
func MergePatch(docData, patchData []byte) ([]byte, error) {
|
|
||||||
return doMergePatch(docData, patchData, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
|
|
||||||
doc := &partialDoc{}
|
|
||||||
|
|
||||||
docErr := json.Unmarshal(docData, doc)
|
|
||||||
|
|
||||||
patch := &partialDoc{}
|
|
||||||
|
|
||||||
patchErr := json.Unmarshal(patchData, patch)
|
|
||||||
|
|
||||||
if _, ok := docErr.(*json.SyntaxError); ok {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := patchErr.(*json.SyntaxError); ok {
|
|
||||||
return nil, ErrBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
if docErr == nil && *doc == nil {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
if patchErr == nil && *patch == nil {
|
|
||||||
return nil, ErrBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
if docErr != nil || patchErr != nil {
|
|
||||||
// Not an error, just not a doc, so we turn straight into the patch
|
|
||||||
if patchErr == nil {
|
|
||||||
if mergeMerge {
|
|
||||||
doc = patch
|
|
||||||
} else {
|
|
||||||
doc = pruneDocNulls(patch)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
patchAry := &partialArray{}
|
|
||||||
patchErr = json.Unmarshal(patchData, patchAry)
|
|
||||||
|
|
||||||
if patchErr != nil {
|
|
||||||
return nil, ErrBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
pruneAryNulls(patchAry)
|
|
||||||
|
|
||||||
out, patchErr := json.Marshal(patchAry)
|
|
||||||
|
|
||||||
if patchErr != nil {
|
|
||||||
return nil, ErrBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mergeDocs(doc, patch, mergeMerge)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resemblesJSONArray indicates whether the byte-slice "appears" to be
|
|
||||||
// a JSON array or not.
|
|
||||||
// False-positives are possible, as this function does not check the internal
|
|
||||||
// structure of the array. It only checks that the outer syntax is present and
|
|
||||||
// correct.
|
|
||||||
func resemblesJSONArray(input []byte) bool {
|
|
||||||
input = bytes.TrimSpace(input)
|
|
||||||
|
|
||||||
hasPrefix := bytes.HasPrefix(input, []byte("["))
|
|
||||||
hasSuffix := bytes.HasSuffix(input, []byte("]"))
|
|
||||||
|
|
||||||
return hasPrefix && hasSuffix
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergePatch will return a merge patch document capable of converting
|
|
||||||
// the original document(s) to the modified document(s).
|
|
||||||
// The parameters can be bytes of either two JSON Documents, or two arrays of
|
|
||||||
// JSON documents.
|
|
||||||
// The merge patch returned follows the specification defined at http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-07
|
|
||||||
func CreateMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) {
|
|
||||||
originalResemblesArray := resemblesJSONArray(originalJSON)
|
|
||||||
modifiedResemblesArray := resemblesJSONArray(modifiedJSON)
|
|
||||||
|
|
||||||
// Do both byte-slices seem like JSON arrays?
|
|
||||||
if originalResemblesArray && modifiedResemblesArray {
|
|
||||||
return createArrayMergePatch(originalJSON, modifiedJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Are both byte-slices are not arrays? Then they are likely JSON objects...
|
|
||||||
if !originalResemblesArray && !modifiedResemblesArray {
|
|
||||||
return createObjectMergePatch(originalJSON, modifiedJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// None of the above? Then return an error because of mismatched types.
|
|
||||||
return nil, errBadMergeTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// createObjectMergePatch will return a merge-patch document capable of
|
|
||||||
// converting the original document to the modified document.
|
|
||||||
func createObjectMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) {
|
|
||||||
originalDoc := map[string]interface{}{}
|
|
||||||
modifiedDoc := map[string]interface{}{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(originalJSON, &originalDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(modifiedJSON, &modifiedDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
dest, err := getDiff(originalDoc, modifiedDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createArrayMergePatch will return an array of merge-patch documents capable
|
|
||||||
// of converting the original document to the modified document for each
|
|
||||||
// pair of JSON documents provided in the arrays.
|
|
||||||
// Arrays of mismatched sizes will result in an error.
|
|
||||||
func createArrayMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) {
|
|
||||||
originalDocs := []json.RawMessage{}
|
|
||||||
modifiedDocs := []json.RawMessage{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(originalJSON, &originalDocs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(modifiedJSON, &modifiedDocs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
total := len(originalDocs)
|
|
||||||
if len(modifiedDocs) != total {
|
|
||||||
return nil, ErrBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
result := []json.RawMessage{}
|
|
||||||
for i := 0; i < len(originalDocs); i++ {
|
|
||||||
original := originalDocs[i]
|
|
||||||
modified := modifiedDocs[i]
|
|
||||||
|
|
||||||
patch, err := createObjectMergePatch(original, modified)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, json.RawMessage(patch))
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the array matches (must be json types).
|
|
||||||
// As is idiomatic for go, an empty array is not the same as a nil array.
|
|
||||||
func matchesArray(a, b []interface{}) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (a == nil && b != nil) || (a != nil && b == nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if !matchesValue(a[i], b[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the values matches (must be json types)
|
|
||||||
// The types of the values must match, otherwise it will always return false
|
|
||||||
// If two map[string]interface{} are given, all elements must match.
|
|
||||||
func matchesValue(av, bv interface{}) bool {
|
|
||||||
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch at := av.(type) {
|
|
||||||
case string:
|
|
||||||
bt := bv.(string)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
bt := bv.(float64)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
bt := bv.(bool)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
// Both nil, fine.
|
|
||||||
return true
|
|
||||||
case map[string]interface{}:
|
|
||||||
bt := bv.(map[string]interface{})
|
|
||||||
if len(bt) != len(at) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for key := range bt {
|
|
||||||
av, aOK := at[key]
|
|
||||||
bv, bOK := bt[key]
|
|
||||||
if aOK != bOK {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !matchesValue(av, bv) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case []interface{}:
|
|
||||||
bt := bv.([]interface{})
|
|
||||||
return matchesArray(at, bt)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDiff returns the (recursive) difference between a and b as a map[string]interface{}.
|
|
||||||
func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) {
|
|
||||||
into := map[string]interface{}{}
|
|
||||||
for key, bv := range b {
|
|
||||||
av, ok := a[key]
|
|
||||||
// value was added
|
|
||||||
if !ok {
|
|
||||||
into[key] = bv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If types have changed, replace completely
|
|
||||||
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
|
|
||||||
into[key] = bv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Types are the same, compare values
|
|
||||||
switch at := av.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
bt := bv.(map[string]interface{})
|
|
||||||
dst := make(map[string]interface{}, len(bt))
|
|
||||||
dst, err := getDiff(at, bt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(dst) > 0 {
|
|
||||||
into[key] = dst
|
|
||||||
}
|
|
||||||
case string, float64, bool:
|
|
||||||
if !matchesValue(av, bv) {
|
|
||||||
into[key] = bv
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
bt := bv.([]interface{})
|
|
||||||
if !matchesArray(at, bt) {
|
|
||||||
into[key] = bv
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
switch bv.(type) {
|
|
||||||
case nil:
|
|
||||||
// Both nil, fine.
|
|
||||||
default:
|
|
||||||
into[key] = bv
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("Unknown type:%T in key %s", av, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now add all deleted values as nil
|
|
||||||
for key := range a {
|
|
||||||
_, found := b[key]
|
|
||||||
if !found {
|
|
||||||
into[key] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return into, nil
|
|
||||||
}
|
|
||||||
809
vendor/github.com/evanphx/json-patch/patch.go
generated
vendored
809
vendor/github.com/evanphx/json-patch/patch.go
generated
vendored
@@ -1,809 +0,0 @@
|
|||||||
package jsonpatch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
eRaw = iota
|
|
||||||
eDoc
|
|
||||||
eAry
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// SupportNegativeIndices decides whether to support non-standard practice of
|
|
||||||
// allowing negative indices to mean indices starting at the end of an array.
|
|
||||||
// Default to true.
|
|
||||||
SupportNegativeIndices bool = true
|
|
||||||
// AccumulatedCopySizeLimit limits the total size increase in bytes caused by
|
|
||||||
// "copy" operations in a patch.
|
|
||||||
AccumulatedCopySizeLimit int64 = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrTestFailed = errors.New("test failed")
|
|
||||||
ErrMissing = errors.New("missing value")
|
|
||||||
ErrUnknownType = errors.New("unknown object type")
|
|
||||||
ErrInvalid = errors.New("invalid state detected")
|
|
||||||
ErrInvalidIndex = errors.New("invalid index referenced")
|
|
||||||
)
|
|
||||||
|
|
||||||
type lazyNode struct {
|
|
||||||
raw *json.RawMessage
|
|
||||||
doc partialDoc
|
|
||||||
ary partialArray
|
|
||||||
which int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation is a single JSON-Patch step, such as a single 'add' operation.
|
|
||||||
type Operation map[string]*json.RawMessage
|
|
||||||
|
|
||||||
// Patch is an ordered collection of Operations.
|
|
||||||
type Patch []Operation
|
|
||||||
|
|
||||||
type partialDoc map[string]*lazyNode
|
|
||||||
type partialArray []*lazyNode
|
|
||||||
|
|
||||||
type container interface {
|
|
||||||
get(key string) (*lazyNode, error)
|
|
||||||
set(key string, val *lazyNode) error
|
|
||||||
add(key string, val *lazyNode) error
|
|
||||||
remove(key string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLazyNode(raw *json.RawMessage) *lazyNode {
|
|
||||||
return &lazyNode{raw: raw, doc: nil, ary: nil, which: eRaw}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) MarshalJSON() ([]byte, error) {
|
|
||||||
switch n.which {
|
|
||||||
case eRaw:
|
|
||||||
return json.Marshal(n.raw)
|
|
||||||
case eDoc:
|
|
||||||
return json.Marshal(n.doc)
|
|
||||||
case eAry:
|
|
||||||
return json.Marshal(n.ary)
|
|
||||||
default:
|
|
||||||
return nil, ErrUnknownType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) UnmarshalJSON(data []byte) error {
|
|
||||||
dest := make(json.RawMessage, len(data))
|
|
||||||
copy(dest, data)
|
|
||||||
n.raw = &dest
|
|
||||||
n.which = eRaw
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deepCopy(src *lazyNode) (*lazyNode, int, error) {
|
|
||||||
if src == nil {
|
|
||||||
return nil, 0, nil
|
|
||||||
}
|
|
||||||
a, err := src.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
sz := len(a)
|
|
||||||
ra := make(json.RawMessage, sz)
|
|
||||||
copy(ra, a)
|
|
||||||
return newLazyNode(&ra), sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) intoDoc() (*partialDoc, error) {
|
|
||||||
if n.which == eDoc {
|
|
||||||
return &n.doc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.raw == nil {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(*n.raw, &n.doc)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n.which = eDoc
|
|
||||||
return &n.doc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) intoAry() (*partialArray, error) {
|
|
||||||
if n.which == eAry {
|
|
||||||
return &n.ary, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.raw == nil {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(*n.raw, &n.ary)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n.which = eAry
|
|
||||||
return &n.ary, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) compact() []byte {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
if n.raw == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Compact(buf, *n.raw)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return *n.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) tryDoc() bool {
|
|
||||||
if n.raw == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(*n.raw, &n.doc)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
n.which = eDoc
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) tryAry() bool {
|
|
||||||
if n.raw == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(*n.raw, &n.ary)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
n.which = eAry
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lazyNode) equal(o *lazyNode) bool {
|
|
||||||
if n.which == eRaw {
|
|
||||||
if !n.tryDoc() && !n.tryAry() {
|
|
||||||
if o.which != eRaw {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.Equal(n.compact(), o.compact())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.which == eDoc {
|
|
||||||
if o.which == eRaw {
|
|
||||||
if !o.tryDoc() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.which != eDoc {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(n.doc) != len(o.doc) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range n.doc {
|
|
||||||
ov, ok := o.doc[k]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v == nil) != (ov == nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if v == nil && ov == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !v.equal(ov) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.which != eAry && !o.tryAry() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(n.ary) != len(o.ary) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, val := range n.ary {
|
|
||||||
if !val.equal(o.ary[idx]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind reads the "op" field of the Operation.
|
|
||||||
func (o Operation) Kind() string {
|
|
||||||
if obj, ok := o["op"]; ok && obj != nil {
|
|
||||||
var op string
|
|
||||||
|
|
||||||
err := json.Unmarshal(*obj, &op)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path reads the "path" field of the Operation.
|
|
||||||
func (o Operation) Path() (string, error) {
|
|
||||||
if obj, ok := o["path"]; ok && obj != nil {
|
|
||||||
var op string
|
|
||||||
|
|
||||||
err := json.Unmarshal(*obj, &op)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "unknown", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return op, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown", errors.Wrapf(ErrMissing, "operation missing path field")
|
|
||||||
}
|
|
||||||
|
|
||||||
// From reads the "from" field of the Operation.
|
|
||||||
func (o Operation) From() (string, error) {
|
|
||||||
if obj, ok := o["from"]; ok && obj != nil {
|
|
||||||
var op string
|
|
||||||
|
|
||||||
err := json.Unmarshal(*obj, &op)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "unknown", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return op, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown", errors.Wrapf(ErrMissing, "operation, missing from field")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Operation) value() *lazyNode {
|
|
||||||
if obj, ok := o["value"]; ok {
|
|
||||||
return newLazyNode(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueInterface decodes the operation value into an interface.
|
|
||||||
func (o Operation) ValueInterface() (interface{}, error) {
|
|
||||||
if obj, ok := o["value"]; ok && obj != nil {
|
|
||||||
var v interface{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(*obj, &v)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Wrapf(ErrMissing, "operation, missing value field")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isArray(buf []byte) bool {
|
|
||||||
Loop:
|
|
||||||
for _, c := range buf {
|
|
||||||
switch c {
|
|
||||||
case ' ':
|
|
||||||
case '\n':
|
|
||||||
case '\t':
|
|
||||||
continue
|
|
||||||
case '[':
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func findObject(pd *container, path string) (container, string) {
|
|
||||||
doc := *pd
|
|
||||||
|
|
||||||
split := strings.Split(path, "/")
|
|
||||||
|
|
||||||
if len(split) < 2 {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := split[1 : len(split)-1]
|
|
||||||
|
|
||||||
key := split[len(split)-1]
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, part := range parts {
|
|
||||||
|
|
||||||
next, ok := doc.get(decodePatchKey(part))
|
|
||||||
|
|
||||||
if next == nil || ok != nil {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if isArray(*next.raw) {
|
|
||||||
doc, err = next.intoAry()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doc, err = next.intoDoc()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc, decodePatchKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialDoc) set(key string, val *lazyNode) error {
|
|
||||||
(*d)[key] = val
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialDoc) add(key string, val *lazyNode) error {
|
|
||||||
(*d)[key] = val
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialDoc) get(key string) (*lazyNode, error) {
|
|
||||||
return (*d)[key], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialDoc) remove(key string) error {
|
|
||||||
_, ok := (*d)[key]
|
|
||||||
if !ok {
|
|
||||||
return errors.Wrapf(ErrMissing, "Unable to remove nonexistent key: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(*d, key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// set should only be used to implement the "replace" operation, so "key" must
|
|
||||||
// be an already existing index in "d".
|
|
||||||
func (d *partialArray) set(key string, val *lazyNode) error {
|
|
||||||
idx, err := strconv.Atoi(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx < 0 {
|
|
||||||
if !SupportNegativeIndices {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
if idx < -len(*d) {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
idx += len(*d)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*d)[idx] = val
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialArray) add(key string, val *lazyNode) error {
|
|
||||||
if key == "-" {
|
|
||||||
*d = append(*d, val)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
idx, err := strconv.Atoi(key)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "value was not a proper array index: '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
sz := len(*d) + 1
|
|
||||||
|
|
||||||
ary := make([]*lazyNode, sz)
|
|
||||||
|
|
||||||
cur := *d
|
|
||||||
|
|
||||||
if idx >= len(ary) {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx < 0 {
|
|
||||||
if !SupportNegativeIndices {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
if idx < -len(ary) {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
idx += len(ary)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(ary[0:idx], cur[0:idx])
|
|
||||||
ary[idx] = val
|
|
||||||
copy(ary[idx+1:], cur[idx:])
|
|
||||||
|
|
||||||
*d = ary
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialArray) get(key string) (*lazyNode, error) {
|
|
||||||
idx, err := strconv.Atoi(key)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx < 0 {
|
|
||||||
if !SupportNegativeIndices {
|
|
||||||
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
if idx < -len(*d) {
|
|
||||||
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
idx += len(*d)
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx >= len(*d) {
|
|
||||||
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (*d)[idx], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *partialArray) remove(key string) error {
|
|
||||||
idx, err := strconv.Atoi(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cur := *d
|
|
||||||
|
|
||||||
if idx >= len(cur) {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx < 0 {
|
|
||||||
if !SupportNegativeIndices {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
if idx < -len(cur) {
|
|
||||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
|
||||||
}
|
|
||||||
idx += len(cur)
|
|
||||||
}
|
|
||||||
|
|
||||||
ary := make([]*lazyNode, len(cur)-1)
|
|
||||||
|
|
||||||
copy(ary[0:idx], cur[0:idx])
|
|
||||||
copy(ary[idx:], cur[idx+1:])
|
|
||||||
|
|
||||||
*d = ary
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Patch) add(doc *container, op Operation) error {
|
|
||||||
path, err := op.Path()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "add operation failed to decode path")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key := findObject(doc, path)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "add operation does not apply: doc is missing path: \"%s\"", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = con.add(key, op.value())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in add for path: '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Patch) remove(doc *container, op Operation) error {
|
|
||||||
path, err := op.Path()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "remove operation failed to decode path")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key := findObject(doc, path)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "remove operation does not apply: doc is missing path: \"%s\"", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = con.remove(key)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in remove for path: '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Patch) replace(doc *container, op Operation) error {
|
|
||||||
path, err := op.Path()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "replace operation failed to decode path")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key := findObject(doc, path)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing path: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := con.get(key)
|
|
||||||
if ok != nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing key: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = con.set(key, op.value())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in remove for path: '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Patch) move(doc *container, op Operation) error {
|
|
||||||
from, err := op.From()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "move operation failed to decode from")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key := findObject(doc, from)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing from path: %s", from)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := con.get(key)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in move for path: '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = con.remove(key)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in move for path: '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := op.Path()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "move operation failed to decode path")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key = findObject(doc, path)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing destination path: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = con.add(key, val)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in move for path: '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Patch) test(doc *container, op Operation) error {
|
|
||||||
path, err := op.Path()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "test operation failed to decode path")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key := findObject(doc, path)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "test operation does not apply: is missing path: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := con.get(key)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in test for path: '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val == nil {
|
|
||||||
if op.value().raw == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Wrapf(ErrTestFailed, "testing value %s failed", path)
|
|
||||||
} else if op.value() == nil {
|
|
||||||
return errors.Wrapf(ErrTestFailed, "testing value %s failed", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.equal(op.value()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Wrapf(ErrTestFailed, "testing value %s failed", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64) error {
|
|
||||||
from, err := op.From()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "copy operation failed to decode from")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key := findObject(doc, from)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing from path: %s", from)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := con.get(key)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error in copy for from: '%s'", from)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := op.Path()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "copy operation failed to decode path")
|
|
||||||
}
|
|
||||||
|
|
||||||
con, key = findObject(doc, path)
|
|
||||||
|
|
||||||
if con == nil {
|
|
||||||
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
valCopy, sz, err := deepCopy(val)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error while performing deep copy")
|
|
||||||
}
|
|
||||||
|
|
||||||
(*accumulatedCopySize) += int64(sz)
|
|
||||||
if AccumulatedCopySizeLimit > 0 && *accumulatedCopySize > AccumulatedCopySizeLimit {
|
|
||||||
return NewAccumulatedCopySizeError(AccumulatedCopySizeLimit, *accumulatedCopySize)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = con.add(key, valCopy)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error while adding value during copy")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal indicates if 2 JSON documents have the same structural equality.
|
|
||||||
func Equal(a, b []byte) bool {
|
|
||||||
ra := make(json.RawMessage, len(a))
|
|
||||||
copy(ra, a)
|
|
||||||
la := newLazyNode(&ra)
|
|
||||||
|
|
||||||
rb := make(json.RawMessage, len(b))
|
|
||||||
copy(rb, b)
|
|
||||||
lb := newLazyNode(&rb)
|
|
||||||
|
|
||||||
return la.equal(lb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodePatch decodes the passed JSON document as an RFC 6902 patch.
|
|
||||||
func DecodePatch(buf []byte) (Patch, error) {
|
|
||||||
var p Patch
|
|
||||||
|
|
||||||
err := json.Unmarshal(buf, &p)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply mutates a JSON document according to the patch, and returns the new
|
|
||||||
// document.
|
|
||||||
func (p Patch) Apply(doc []byte) ([]byte, error) {
|
|
||||||
return p.ApplyIndent(doc, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyIndent mutates a JSON document according to the patch, and returns the new
|
|
||||||
// document indented.
|
|
||||||
func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
|
|
||||||
if len(doc) == 0 {
|
|
||||||
return doc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pd container
|
|
||||||
if doc[0] == '[' {
|
|
||||||
pd = &partialArray{}
|
|
||||||
} else {
|
|
||||||
pd = &partialDoc{}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(doc, pd)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = nil
|
|
||||||
|
|
||||||
var accumulatedCopySize int64
|
|
||||||
|
|
||||||
for _, op := range p {
|
|
||||||
switch op.Kind() {
|
|
||||||
case "add":
|
|
||||||
err = p.add(&pd, op)
|
|
||||||
case "remove":
|
|
||||||
err = p.remove(&pd, op)
|
|
||||||
case "replace":
|
|
||||||
err = p.replace(&pd, op)
|
|
||||||
case "move":
|
|
||||||
err = p.move(&pd, op)
|
|
||||||
case "test":
|
|
||||||
err = p.test(&pd, op)
|
|
||||||
case "copy":
|
|
||||||
err = p.copy(&pd, op, &accumulatedCopySize)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("Unexpected kind: %s", op.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if indent != "" {
|
|
||||||
return json.MarshalIndent(pd, "", indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(pd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// From http://tools.ietf.org/html/rfc6901#section-4 :
|
|
||||||
//
|
|
||||||
// Evaluation of each reference token begins by decoding any escaped
|
|
||||||
// character sequence. This is performed by first transforming any
|
|
||||||
// occurrence of the sequence '~1' to '/', and then transforming any
|
|
||||||
// occurrence of the sequence '~0' to '~'.
|
|
||||||
|
|
||||||
var (
|
|
||||||
rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
|
|
||||||
)
|
|
||||||
|
|
||||||
func decodePatchKey(k string) string {
|
|
||||||
return rfc6901Decoder.Replace(k)
|
|
||||||
}
|
|
||||||
25
vendor/github.com/evanphx/json-patch/v5/LICENSE
generated
vendored
25
vendor/github.com/evanphx/json-patch/v5/LICENSE
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
Copyright (c) 2014, Evan Phoenix
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the Evan Phoenix nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
38
vendor/github.com/evanphx/json-patch/v5/errors.go
generated
vendored
38
vendor/github.com/evanphx/json-patch/v5/errors.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
package jsonpatch
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// AccumulatedCopySizeError is an error type returned when the accumulated size
|
|
||||||
// increase caused by copy operations in a patch operation has exceeded the
|
|
||||||
// limit.
|
|
||||||
type AccumulatedCopySizeError struct {
|
|
||||||
limit int64
|
|
||||||
accumulated int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAccumulatedCopySizeError returns an AccumulatedCopySizeError.
|
|
||||||
func NewAccumulatedCopySizeError(l, a int64) *AccumulatedCopySizeError {
|
|
||||||
return &AccumulatedCopySizeError{limit: l, accumulated: a}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (a *AccumulatedCopySizeError) Error() string {
|
|
||||||
return fmt.Sprintf("Unable to complete the copy, the accumulated size increase of copy is %d, exceeding the limit %d", a.accumulated, a.limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArraySizeError is an error type returned when the array size has exceeded
|
|
||||||
// the limit.
|
|
||||||
type ArraySizeError struct {
|
|
||||||
limit int
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArraySizeError returns an ArraySizeError.
|
|
||||||
func NewArraySizeError(l, s int) *ArraySizeError {
|
|
||||||
return &ArraySizeError{limit: l, size: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (a *ArraySizeError) Error() string {
|
|
||||||
return fmt.Sprintf("Unable to create array of size %d, limit is %d", a.size, a.limit)
|
|
||||||
}
|
|
||||||
1385
vendor/github.com/evanphx/json-patch/v5/internal/json/decode.go
generated
vendored
1385
vendor/github.com/evanphx/json-patch/v5/internal/json/decode.go
generated
vendored
File diff suppressed because it is too large
Load Diff
1486
vendor/github.com/evanphx/json-patch/v5/internal/json/encode.go
generated
vendored
1486
vendor/github.com/evanphx/json-patch/v5/internal/json/encode.go
generated
vendored
File diff suppressed because it is too large
Load Diff
141
vendor/github.com/evanphx/json-patch/v5/internal/json/fold.go
generated
vendored
141
vendor/github.com/evanphx/json-patch/v5/internal/json/fold.go
generated
vendored
@@ -1,141 +0,0 @@
|
|||||||
// Copyright 2013 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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
|
||||||
kelvin = '\u212a'
|
|
||||||
smallLongEss = '\u017f'
|
|
||||||
)
|
|
||||||
|
|
||||||
// foldFunc returns one of four different case folding equivalence
|
|
||||||
// functions, from most general (and slow) to fastest:
|
|
||||||
//
|
|
||||||
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
|
||||||
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
|
||||||
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
|
||||||
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
|
||||||
//
|
|
||||||
// The letters S and K are special because they map to 3 runes, not just 2:
|
|
||||||
// - S maps to s and to U+017F 'ſ' Latin small letter long s
|
|
||||||
// - k maps to K and to U+212A 'K' Kelvin sign
|
|
||||||
//
|
|
||||||
// See https://play.golang.org/p/tTxjOc0OGo
|
|
||||||
//
|
|
||||||
// The returned function is specialized for matching against s and
|
|
||||||
// should only be given s. It's not curried for performance reasons.
|
|
||||||
func foldFunc(s []byte) func(s, t []byte) bool {
|
|
||||||
nonLetter := false
|
|
||||||
special := false // special letter
|
|
||||||
for _, b := range s {
|
|
||||||
if b >= utf8.RuneSelf {
|
|
||||||
return bytes.EqualFold
|
|
||||||
}
|
|
||||||
upper := b & caseMask
|
|
||||||
if upper < 'A' || upper > 'Z' {
|
|
||||||
nonLetter = true
|
|
||||||
} else if upper == 'K' || upper == 'S' {
|
|
||||||
// See above for why these letters are special.
|
|
||||||
special = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if special {
|
|
||||||
return equalFoldRight
|
|
||||||
}
|
|
||||||
if nonLetter {
|
|
||||||
return asciiEqualFold
|
|
||||||
}
|
|
||||||
return simpleLetterEqualFold
|
|
||||||
}
|
|
||||||
|
|
||||||
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
|
||||||
// known to be all ASCII (including punctuation), but contains an 's',
|
|
||||||
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
|
||||||
// See comments on foldFunc.
|
|
||||||
func equalFoldRight(s, t []byte) bool {
|
|
||||||
for _, sb := range s {
|
|
||||||
if len(t) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
tb := t[0]
|
|
||||||
if tb < utf8.RuneSelf {
|
|
||||||
if sb != tb {
|
|
||||||
sbUpper := sb & caseMask
|
|
||||||
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
|
||||||
if sbUpper != tb&caseMask {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t = t[1:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// sb is ASCII and t is not. t must be either kelvin
|
|
||||||
// sign or long s; sb must be s, S, k, or K.
|
|
||||||
tr, size := utf8.DecodeRune(t)
|
|
||||||
switch sb {
|
|
||||||
case 's', 'S':
|
|
||||||
if tr != smallLongEss {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case 'k', 'K':
|
|
||||||
if tr != kelvin {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
t = t[size:]
|
|
||||||
|
|
||||||
}
|
|
||||||
return len(t) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
|
||||||
// s is all ASCII (but may contain non-letters) and contains no
|
|
||||||
// special-folding letters.
|
|
||||||
// See comments on foldFunc.
|
|
||||||
func asciiEqualFold(s, t []byte) bool {
|
|
||||||
if len(s) != len(t) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, sb := range s {
|
|
||||||
tb := t[i]
|
|
||||||
if sb == tb {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
|
||||||
if sb&caseMask != tb&caseMask {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
|
||||||
// use when s is all ASCII letters (no underscores, etc) and also
|
|
||||||
// doesn't contain 'k', 'K', 's', or 'S'.
|
|
||||||
// See comments on foldFunc.
|
|
||||||
func simpleLetterEqualFold(s, t []byte) bool {
|
|
||||||
if len(s) != len(t) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, b := range s {
|
|
||||||
if b&caseMask != t[i]&caseMask {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
42
vendor/github.com/evanphx/json-patch/v5/internal/json/fuzz.go
generated
vendored
42
vendor/github.com/evanphx/json-patch/v5/internal/json/fuzz.go
generated
vendored
@@ -1,42 +0,0 @@
|
|||||||
// Copyright 2019 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.
|
|
||||||
|
|
||||||
//go:build gofuzz
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Fuzz(data []byte) (score int) {
|
|
||||||
for _, ctor := range []func() any{
|
|
||||||
func() any { return new(any) },
|
|
||||||
func() any { return new(map[string]any) },
|
|
||||||
func() any { return new([]any) },
|
|
||||||
} {
|
|
||||||
v := ctor()
|
|
||||||
err := Unmarshal(data, v)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
score = 1
|
|
||||||
|
|
||||||
m, err := Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("v=%#v\n", v)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u := ctor()
|
|
||||||
err = Unmarshal(m, u)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("v=%#v\n", v)
|
|
||||||
fmt.Printf("m=%s\n", m)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
143
vendor/github.com/evanphx/json-patch/v5/internal/json/indent.go
generated
vendored
143
vendor/github.com/evanphx/json-patch/v5/internal/json/indent.go
generated
vendored
@@ -1,143 +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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compact appends to dst the JSON-encoded src with
|
|
||||||
// insignificant space characters elided.
|
|
||||||
func Compact(dst *bytes.Buffer, src []byte) error {
|
|
||||||
return compact(dst, src, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func compact(dst *bytes.Buffer, src []byte, escape bool) error {
|
|
||||||
origLen := dst.Len()
|
|
||||||
scan := newScanner()
|
|
||||||
defer freeScanner(scan)
|
|
||||||
start := 0
|
|
||||||
for i, c := range src {
|
|
||||||
if escape && (c == '<' || c == '>' || c == '&') {
|
|
||||||
if start < i {
|
|
||||||
dst.Write(src[start:i])
|
|
||||||
}
|
|
||||||
dst.WriteString(`\u00`)
|
|
||||||
dst.WriteByte(hex[c>>4])
|
|
||||||
dst.WriteByte(hex[c&0xF])
|
|
||||||
start = i + 1
|
|
||||||
}
|
|
||||||
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
|
||||||
if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
|
||||||
if start < i {
|
|
||||||
dst.Write(src[start:i])
|
|
||||||
}
|
|
||||||
dst.WriteString(`\u202`)
|
|
||||||
dst.WriteByte(hex[src[i+2]&0xF])
|
|
||||||
start = i + 3
|
|
||||||
}
|
|
||||||
v := scan.step(scan, c)
|
|
||||||
if v >= scanSkipSpace {
|
|
||||||
if v == scanError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if start < i {
|
|
||||||
dst.Write(src[start:i])
|
|
||||||
}
|
|
||||||
start = i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if scan.eof() == scanError {
|
|
||||||
dst.Truncate(origLen)
|
|
||||||
return scan.err
|
|
||||||
}
|
|
||||||
if start < len(src) {
|
|
||||||
dst.Write(src[start:])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
|
|
||||||
dst.WriteByte('\n')
|
|
||||||
dst.WriteString(prefix)
|
|
||||||
for i := 0; i < depth; i++ {
|
|
||||||
dst.WriteString(indent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indent appends to dst an indented form of the JSON-encoded src.
|
|
||||||
// Each element in a JSON object or array begins on a new,
|
|
||||||
// indented line beginning with prefix followed by one or more
|
|
||||||
// copies of indent according to the indentation nesting.
|
|
||||||
// The data appended to dst does not begin with the prefix nor
|
|
||||||
// any indentation, to make it easier to embed inside other formatted JSON data.
|
|
||||||
// Although leading space characters (space, tab, carriage return, newline)
|
|
||||||
// at the beginning of src are dropped, trailing space characters
|
|
||||||
// at the end of src are preserved and copied to dst.
|
|
||||||
// For example, if src has no trailing spaces, neither will dst;
|
|
||||||
// if src ends in a trailing newline, so will dst.
|
|
||||||
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
|
||||||
origLen := dst.Len()
|
|
||||||
scan := newScanner()
|
|
||||||
defer freeScanner(scan)
|
|
||||||
needIndent := false
|
|
||||||
depth := 0
|
|
||||||
for _, c := range src {
|
|
||||||
scan.bytes++
|
|
||||||
v := scan.step(scan, c)
|
|
||||||
if v == scanSkipSpace {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v == scanError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if needIndent && v != scanEndObject && v != scanEndArray {
|
|
||||||
needIndent = false
|
|
||||||
depth++
|
|
||||||
newline(dst, prefix, indent, depth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit semantically uninteresting bytes
|
|
||||||
// (in particular, punctuation in strings) unmodified.
|
|
||||||
if v == scanContinue {
|
|
||||||
dst.WriteByte(c)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add spacing around real punctuation.
|
|
||||||
switch c {
|
|
||||||
case '{', '[':
|
|
||||||
// delay indent so that empty object and array are formatted as {} and [].
|
|
||||||
needIndent = true
|
|
||||||
dst.WriteByte(c)
|
|
||||||
|
|
||||||
case ',':
|
|
||||||
dst.WriteByte(c)
|
|
||||||
newline(dst, prefix, indent, depth)
|
|
||||||
|
|
||||||
case ':':
|
|
||||||
dst.WriteByte(c)
|
|
||||||
dst.WriteByte(' ')
|
|
||||||
|
|
||||||
case '}', ']':
|
|
||||||
if needIndent {
|
|
||||||
// suppress indent in empty object/array
|
|
||||||
needIndent = false
|
|
||||||
} else {
|
|
||||||
depth--
|
|
||||||
newline(dst, prefix, indent, depth)
|
|
||||||
}
|
|
||||||
dst.WriteByte(c)
|
|
||||||
|
|
||||||
default:
|
|
||||||
dst.WriteByte(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if scan.eof() == scanError {
|
|
||||||
dst.Truncate(origLen)
|
|
||||||
return scan.err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
610
vendor/github.com/evanphx/json-patch/v5/internal/json/scanner.go
generated
vendored
610
vendor/github.com/evanphx/json-patch/v5/internal/json/scanner.go
generated
vendored
@@ -1,610 +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
|
|
||||||
|
|
||||||
// JSON value parser state machine.
|
|
||||||
// Just about at the limit of what is reasonable to write by hand.
|
|
||||||
// Some parts are a bit tedious, but overall it nicely factors out the
|
|
||||||
// otherwise common code from the multiple scanning functions
|
|
||||||
// in this package (Compact, Indent, checkValid, etc).
|
|
||||||
//
|
|
||||||
// This file starts with two simple examples using the scanner
|
|
||||||
// before diving into the scanner itself.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Valid reports whether data is a valid JSON encoding.
|
|
||||||
func Valid(data []byte) bool {
|
|
||||||
scan := newScanner()
|
|
||||||
defer freeScanner(scan)
|
|
||||||
return checkValid(data, scan) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkValid verifies that data is valid JSON-encoded data.
|
|
||||||
// scan is passed in for use by checkValid to avoid an allocation.
|
|
||||||
// checkValid returns nil or a SyntaxError.
|
|
||||||
func checkValid(data []byte, scan *scanner) error {
|
|
||||||
scan.reset()
|
|
||||||
for _, c := range data {
|
|
||||||
scan.bytes++
|
|
||||||
if scan.step(scan, c) == scanError {
|
|
||||||
return scan.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if scan.eof() == scanError {
|
|
||||||
return scan.err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// A SyntaxError is a description of a JSON syntax error.
|
|
||||||
// Unmarshal will return a SyntaxError if the JSON can't be parsed.
|
|
||||||
type SyntaxError struct {
|
|
||||||
msg string // description of error
|
|
||||||
Offset int64 // error occurred after reading Offset bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SyntaxError) Error() string { return e.msg }
|
|
||||||
|
|
||||||
// A scanner is a JSON scanning state machine.
|
|
||||||
// Callers call scan.reset and then pass bytes in one at a time
|
|
||||||
// by calling scan.step(&scan, c) for each byte.
|
|
||||||
// The return value, referred to as an opcode, tells the
|
|
||||||
// caller about significant parsing events like beginning
|
|
||||||
// and ending literals, objects, and arrays, so that the
|
|
||||||
// caller can follow along if it wishes.
|
|
||||||
// The return value scanEnd indicates that a single top-level
|
|
||||||
// JSON value has been completed, *before* the byte that
|
|
||||||
// just got passed in. (The indication must be delayed in order
|
|
||||||
// to recognize the end of numbers: is 123 a whole value or
|
|
||||||
// the beginning of 12345e+6?).
|
|
||||||
type scanner struct {
|
|
||||||
// The step is a func to be called to execute the next transition.
|
|
||||||
// Also tried using an integer constant and a single func
|
|
||||||
// with a switch, but using the func directly was 10% faster
|
|
||||||
// on a 64-bit Mac Mini, and it's nicer to read.
|
|
||||||
step func(*scanner, byte) int
|
|
||||||
|
|
||||||
// Reached end of top-level value.
|
|
||||||
endTop bool
|
|
||||||
|
|
||||||
// Stack of what we're in the middle of - array values, object keys, object values.
|
|
||||||
parseState []int
|
|
||||||
|
|
||||||
// Error that happened, if any.
|
|
||||||
err error
|
|
||||||
|
|
||||||
// total bytes consumed, updated by decoder.Decode (and deliberately
|
|
||||||
// not set to zero by scan.reset)
|
|
||||||
bytes int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var scannerPool = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return &scanner{}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func newScanner() *scanner {
|
|
||||||
scan := scannerPool.Get().(*scanner)
|
|
||||||
// scan.reset by design doesn't set bytes to zero
|
|
||||||
scan.bytes = 0
|
|
||||||
scan.reset()
|
|
||||||
return scan
|
|
||||||
}
|
|
||||||
|
|
||||||
func freeScanner(scan *scanner) {
|
|
||||||
// Avoid hanging on to too much memory in extreme cases.
|
|
||||||
if len(scan.parseState) > 1024 {
|
|
||||||
scan.parseState = nil
|
|
||||||
}
|
|
||||||
scannerPool.Put(scan)
|
|
||||||
}
|
|
||||||
|
|
||||||
// These values are returned by the state transition functions
|
|
||||||
// assigned to scanner.state and the method scanner.eof.
|
|
||||||
// They give details about the current state of the scan that
|
|
||||||
// callers might be interested to know about.
|
|
||||||
// It is okay to ignore the return value of any particular
|
|
||||||
// call to scanner.state: if one call returns scanError,
|
|
||||||
// every subsequent call will return scanError too.
|
|
||||||
const (
|
|
||||||
// Continue.
|
|
||||||
scanContinue = iota // uninteresting byte
|
|
||||||
scanBeginLiteral // end implied by next result != scanContinue
|
|
||||||
scanBeginObject // begin object
|
|
||||||
scanObjectKey // just finished object key (string)
|
|
||||||
scanObjectValue // just finished non-last object value
|
|
||||||
scanEndObject // end object (implies scanObjectValue if possible)
|
|
||||||
scanBeginArray // begin array
|
|
||||||
scanArrayValue // just finished array value
|
|
||||||
scanEndArray // end array (implies scanArrayValue if possible)
|
|
||||||
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
|
||||||
|
|
||||||
// Stop.
|
|
||||||
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
|
||||||
scanError // hit an error, scanner.err.
|
|
||||||
)
|
|
||||||
|
|
||||||
// These values are stored in the parseState stack.
|
|
||||||
// They give the current state of a composite value
|
|
||||||
// being scanned. If the parser is inside a nested value
|
|
||||||
// the parseState describes the nested state, outermost at entry 0.
|
|
||||||
const (
|
|
||||||
parseObjectKey = iota // parsing object key (before colon)
|
|
||||||
parseObjectValue // parsing object value (after colon)
|
|
||||||
parseArrayValue // parsing array value
|
|
||||||
)
|
|
||||||
|
|
||||||
// This limits the max nesting depth to prevent stack overflow.
|
|
||||||
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
|
|
||||||
const maxNestingDepth = 10000
|
|
||||||
|
|
||||||
// reset prepares the scanner for use.
|
|
||||||
// It must be called before calling s.step.
|
|
||||||
func (s *scanner) reset() {
|
|
||||||
s.step = stateBeginValue
|
|
||||||
s.parseState = s.parseState[0:0]
|
|
||||||
s.err = nil
|
|
||||||
s.endTop = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// eof tells the scanner that the end of input has been reached.
|
|
||||||
// It returns a scan status just as s.step does.
|
|
||||||
func (s *scanner) eof() int {
|
|
||||||
if s.err != nil {
|
|
||||||
return scanError
|
|
||||||
}
|
|
||||||
if s.endTop {
|
|
||||||
return scanEnd
|
|
||||||
}
|
|
||||||
s.step(s, ' ')
|
|
||||||
if s.endTop {
|
|
||||||
return scanEnd
|
|
||||||
}
|
|
||||||
if s.err == nil {
|
|
||||||
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
|
||||||
}
|
|
||||||
return scanError
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushParseState pushes a new parse state p onto the parse stack.
|
|
||||||
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
|
|
||||||
func (s *scanner) pushParseState(c byte, newParseState int, successState int) int {
|
|
||||||
s.parseState = append(s.parseState, newParseState)
|
|
||||||
if len(s.parseState) <= maxNestingDepth {
|
|
||||||
return successState
|
|
||||||
}
|
|
||||||
return s.error(c, "exceeded max depth")
|
|
||||||
}
|
|
||||||
|
|
||||||
// popParseState pops a parse state (already obtained) off the stack
|
|
||||||
// and updates s.step accordingly.
|
|
||||||
func (s *scanner) popParseState() {
|
|
||||||
n := len(s.parseState) - 1
|
|
||||||
s.parseState = s.parseState[0:n]
|
|
||||||
if n == 0 {
|
|
||||||
s.step = stateEndTop
|
|
||||||
s.endTop = true
|
|
||||||
} else {
|
|
||||||
s.step = stateEndValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSpace(c byte) bool {
|
|
||||||
return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateBeginValueOrEmpty is the state after reading `[`.
|
|
||||||
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
|
||||||
if isSpace(c) {
|
|
||||||
return scanSkipSpace
|
|
||||||
}
|
|
||||||
if c == ']' {
|
|
||||||
return stateEndValue(s, c)
|
|
||||||
}
|
|
||||||
return stateBeginValue(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateBeginValue is the state at the beginning of the input.
|
|
||||||
func stateBeginValue(s *scanner, c byte) int {
|
|
||||||
if isSpace(c) {
|
|
||||||
return scanSkipSpace
|
|
||||||
}
|
|
||||||
switch c {
|
|
||||||
case '{':
|
|
||||||
s.step = stateBeginStringOrEmpty
|
|
||||||
return s.pushParseState(c, parseObjectKey, scanBeginObject)
|
|
||||||
case '[':
|
|
||||||
s.step = stateBeginValueOrEmpty
|
|
||||||
return s.pushParseState(c, parseArrayValue, scanBeginArray)
|
|
||||||
case '"':
|
|
||||||
s.step = stateInString
|
|
||||||
return scanBeginLiteral
|
|
||||||
case '-':
|
|
||||||
s.step = stateNeg
|
|
||||||
return scanBeginLiteral
|
|
||||||
case '0': // beginning of 0.123
|
|
||||||
s.step = state0
|
|
||||||
return scanBeginLiteral
|
|
||||||
case 't': // beginning of true
|
|
||||||
s.step = stateT
|
|
||||||
return scanBeginLiteral
|
|
||||||
case 'f': // beginning of false
|
|
||||||
s.step = stateF
|
|
||||||
return scanBeginLiteral
|
|
||||||
case 'n': // beginning of null
|
|
||||||
s.step = stateN
|
|
||||||
return scanBeginLiteral
|
|
||||||
}
|
|
||||||
if '1' <= c && c <= '9' { // beginning of 1234.5
|
|
||||||
s.step = state1
|
|
||||||
return scanBeginLiteral
|
|
||||||
}
|
|
||||||
return s.error(c, "looking for beginning of value")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateBeginStringOrEmpty is the state after reading `{`.
|
|
||||||
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
|
||||||
if isSpace(c) {
|
|
||||||
return scanSkipSpace
|
|
||||||
}
|
|
||||||
if c == '}' {
|
|
||||||
n := len(s.parseState)
|
|
||||||
s.parseState[n-1] = parseObjectValue
|
|
||||||
return stateEndValue(s, c)
|
|
||||||
}
|
|
||||||
return stateBeginString(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateBeginString is the state after reading `{"key": value,`.
|
|
||||||
func stateBeginString(s *scanner, c byte) int {
|
|
||||||
if isSpace(c) {
|
|
||||||
return scanSkipSpace
|
|
||||||
}
|
|
||||||
if c == '"' {
|
|
||||||
s.step = stateInString
|
|
||||||
return scanBeginLiteral
|
|
||||||
}
|
|
||||||
return s.error(c, "looking for beginning of object key string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateEndValue is the state after completing a value,
|
|
||||||
// such as after reading `{}` or `true` or `["x"`.
|
|
||||||
func stateEndValue(s *scanner, c byte) int {
|
|
||||||
n := len(s.parseState)
|
|
||||||
if n == 0 {
|
|
||||||
// Completed top-level before the current byte.
|
|
||||||
s.step = stateEndTop
|
|
||||||
s.endTop = true
|
|
||||||
return stateEndTop(s, c)
|
|
||||||
}
|
|
||||||
if isSpace(c) {
|
|
||||||
s.step = stateEndValue
|
|
||||||
return scanSkipSpace
|
|
||||||
}
|
|
||||||
ps := s.parseState[n-1]
|
|
||||||
switch ps {
|
|
||||||
case parseObjectKey:
|
|
||||||
if c == ':' {
|
|
||||||
s.parseState[n-1] = parseObjectValue
|
|
||||||
s.step = stateBeginValue
|
|
||||||
return scanObjectKey
|
|
||||||
}
|
|
||||||
return s.error(c, "after object key")
|
|
||||||
case parseObjectValue:
|
|
||||||
if c == ',' {
|
|
||||||
s.parseState[n-1] = parseObjectKey
|
|
||||||
s.step = stateBeginString
|
|
||||||
return scanObjectValue
|
|
||||||
}
|
|
||||||
if c == '}' {
|
|
||||||
s.popParseState()
|
|
||||||
return scanEndObject
|
|
||||||
}
|
|
||||||
return s.error(c, "after object key:value pair")
|
|
||||||
case parseArrayValue:
|
|
||||||
if c == ',' {
|
|
||||||
s.step = stateBeginValue
|
|
||||||
return scanArrayValue
|
|
||||||
}
|
|
||||||
if c == ']' {
|
|
||||||
s.popParseState()
|
|
||||||
return scanEndArray
|
|
||||||
}
|
|
||||||
return s.error(c, "after array element")
|
|
||||||
}
|
|
||||||
return s.error(c, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateEndTop is the state after finishing the top-level value,
|
|
||||||
// such as after reading `{}` or `[1,2,3]`.
|
|
||||||
// Only space characters should be seen now.
|
|
||||||
func stateEndTop(s *scanner, c byte) int {
|
|
||||||
if !isSpace(c) {
|
|
||||||
// Complain about non-space byte on next call.
|
|
||||||
s.error(c, "after top-level value")
|
|
||||||
}
|
|
||||||
return scanEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateInString is the state after reading `"`.
|
|
||||||
func stateInString(s *scanner, c byte) int {
|
|
||||||
if c == '"' {
|
|
||||||
s.step = stateEndValue
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
if c == '\\' {
|
|
||||||
s.step = stateInStringEsc
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
if c < 0x20 {
|
|
||||||
return s.error(c, "in string literal")
|
|
||||||
}
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
|
||||||
func stateInStringEsc(s *scanner, c byte) int {
|
|
||||||
switch c {
|
|
||||||
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
|
||||||
s.step = stateInString
|
|
||||||
return scanContinue
|
|
||||||
case 'u':
|
|
||||||
s.step = stateInStringEscU
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in string escape code")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
|
||||||
func stateInStringEscU(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
|
||||||
s.step = stateInStringEscU1
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
// numbers
|
|
||||||
return s.error(c, "in \\u hexadecimal character escape")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
|
||||||
func stateInStringEscU1(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
|
||||||
s.step = stateInStringEscU12
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
// numbers
|
|
||||||
return s.error(c, "in \\u hexadecimal character escape")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
|
||||||
func stateInStringEscU12(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
|
||||||
s.step = stateInStringEscU123
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
// numbers
|
|
||||||
return s.error(c, "in \\u hexadecimal character escape")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
|
||||||
func stateInStringEscU123(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
|
||||||
s.step = stateInString
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
// numbers
|
|
||||||
return s.error(c, "in \\u hexadecimal character escape")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateNeg is the state after reading `-` during a number.
|
|
||||||
func stateNeg(s *scanner, c byte) int {
|
|
||||||
if c == '0' {
|
|
||||||
s.step = state0
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
if '1' <= c && c <= '9' {
|
|
||||||
s.step = state1
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in numeric literal")
|
|
||||||
}
|
|
||||||
|
|
||||||
// state1 is the state after reading a non-zero integer during a number,
|
|
||||||
// such as after reading `1` or `100` but not `0`.
|
|
||||||
func state1(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' {
|
|
||||||
s.step = state1
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return state0(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// state0 is the state after reading `0` during a number.
|
|
||||||
func state0(s *scanner, c byte) int {
|
|
||||||
if c == '.' {
|
|
||||||
s.step = stateDot
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
if c == 'e' || c == 'E' {
|
|
||||||
s.step = stateE
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return stateEndValue(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateDot is the state after reading the integer and decimal point in a number,
|
|
||||||
// such as after reading `1.`.
|
|
||||||
func stateDot(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' {
|
|
||||||
s.step = stateDot0
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "after decimal point in numeric literal")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
|
||||||
// digits of a number, such as after reading `3.14`.
|
|
||||||
func stateDot0(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' {
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
if c == 'e' || c == 'E' {
|
|
||||||
s.step = stateE
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return stateEndValue(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateE is the state after reading the mantissa and e in a number,
|
|
||||||
// such as after reading `314e` or `0.314e`.
|
|
||||||
func stateE(s *scanner, c byte) int {
|
|
||||||
if c == '+' || c == '-' {
|
|
||||||
s.step = stateESign
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return stateESign(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
|
||||||
// such as after reading `314e-` or `0.314e+`.
|
|
||||||
func stateESign(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' {
|
|
||||||
s.step = stateE0
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in exponent of numeric literal")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateE0 is the state after reading the mantissa, e, optional sign,
|
|
||||||
// and at least one digit of the exponent in a number,
|
|
||||||
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
|
||||||
func stateE0(s *scanner, c byte) int {
|
|
||||||
if '0' <= c && c <= '9' {
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return stateEndValue(s, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateT is the state after reading `t`.
|
|
||||||
func stateT(s *scanner, c byte) int {
|
|
||||||
if c == 'r' {
|
|
||||||
s.step = stateTr
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal true (expecting 'r')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateTr is the state after reading `tr`.
|
|
||||||
func stateTr(s *scanner, c byte) int {
|
|
||||||
if c == 'u' {
|
|
||||||
s.step = stateTru
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal true (expecting 'u')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateTru is the state after reading `tru`.
|
|
||||||
func stateTru(s *scanner, c byte) int {
|
|
||||||
if c == 'e' {
|
|
||||||
s.step = stateEndValue
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal true (expecting 'e')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateF is the state after reading `f`.
|
|
||||||
func stateF(s *scanner, c byte) int {
|
|
||||||
if c == 'a' {
|
|
||||||
s.step = stateFa
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal false (expecting 'a')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateFa is the state after reading `fa`.
|
|
||||||
func stateFa(s *scanner, c byte) int {
|
|
||||||
if c == 'l' {
|
|
||||||
s.step = stateFal
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal false (expecting 'l')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateFal is the state after reading `fal`.
|
|
||||||
func stateFal(s *scanner, c byte) int {
|
|
||||||
if c == 's' {
|
|
||||||
s.step = stateFals
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal false (expecting 's')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateFals is the state after reading `fals`.
|
|
||||||
func stateFals(s *scanner, c byte) int {
|
|
||||||
if c == 'e' {
|
|
||||||
s.step = stateEndValue
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal false (expecting 'e')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateN is the state after reading `n`.
|
|
||||||
func stateN(s *scanner, c byte) int {
|
|
||||||
if c == 'u' {
|
|
||||||
s.step = stateNu
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal null (expecting 'u')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateNu is the state after reading `nu`.
|
|
||||||
func stateNu(s *scanner, c byte) int {
|
|
||||||
if c == 'l' {
|
|
||||||
s.step = stateNul
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal null (expecting 'l')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateNul is the state after reading `nul`.
|
|
||||||
func stateNul(s *scanner, c byte) int {
|
|
||||||
if c == 'l' {
|
|
||||||
s.step = stateEndValue
|
|
||||||
return scanContinue
|
|
||||||
}
|
|
||||||
return s.error(c, "in literal null (expecting 'l')")
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateError is the state after reaching a syntax error,
|
|
||||||
// such as after reading `[1}` or `5.1.2`.
|
|
||||||
func stateError(s *scanner, c byte) int {
|
|
||||||
return scanError
|
|
||||||
}
|
|
||||||
|
|
||||||
// error records an error and switches to the error state.
|
|
||||||
func (s *scanner) error(c byte, context string) int {
|
|
||||||
s.step = stateError
|
|
||||||
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
|
||||||
return scanError
|
|
||||||
}
|
|
||||||
|
|
||||||
// quoteChar formats c as a quoted character literal.
|
|
||||||
func quoteChar(c byte) string {
|
|
||||||
// special cases - different from quoted strings
|
|
||||||
if c == '\'' {
|
|
||||||
return `'\''`
|
|
||||||
}
|
|
||||||
if c == '"' {
|
|
||||||
return `'"'`
|
|
||||||
}
|
|
||||||
|
|
||||||
// use quoted string with different quotation marks
|
|
||||||
s := strconv.Quote(string(c))
|
|
||||||
return "'" + s[1:len(s)-1] + "'"
|
|
||||||
}
|
|
||||||
495
vendor/github.com/evanphx/json-patch/v5/internal/json/stream.go
generated
vendored
495
vendor/github.com/evanphx/json-patch/v5/internal/json/stream.go
generated
vendored
@@ -1,495 +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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Decoder reads and decodes JSON values from an input stream.
|
|
||||||
type Decoder struct {
|
|
||||||
r io.Reader
|
|
||||||
buf []byte
|
|
||||||
d decodeState
|
|
||||||
scanp int // start of unread data in buf
|
|
||||||
scanned int64 // amount of data already scanned
|
|
||||||
scan scanner
|
|
||||||
err error
|
|
||||||
|
|
||||||
tokenState int
|
|
||||||
tokenStack []int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder returns a new decoder that reads from r.
|
|
||||||
//
|
|
||||||
// The decoder introduces its own buffering and may
|
|
||||||
// read data from r beyond the JSON values requested.
|
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
|
||||||
return &Decoder{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
|
||||||
// Number instead of as a float64.
|
|
||||||
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
|
||||||
|
|
||||||
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
|
||||||
// is a struct and the input contains object keys which do not match any
|
|
||||||
// non-ignored, exported fields in the destination.
|
|
||||||
func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true }
|
|
||||||
|
|
||||||
// Decode reads the next JSON-encoded value from its
|
|
||||||
// input and stores it in the value pointed to by v.
|
|
||||||
//
|
|
||||||
// See the documentation for Unmarshal for details about
|
|
||||||
// the conversion of JSON into a Go value.
|
|
||||||
func (dec *Decoder) Decode(v any) error {
|
|
||||||
if dec.err != nil {
|
|
||||||
return dec.err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dec.tokenPrepareForDecode(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dec.tokenValueAllowed() {
|
|
||||||
return &SyntaxError{msg: "not at beginning of value", Offset: dec.InputOffset()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read whole value into buffer.
|
|
||||||
n, err := dec.readValue()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
|
||||||
dec.scanp += n
|
|
||||||
|
|
||||||
// Don't save err from unmarshal into dec.err:
|
|
||||||
// the connection is still usable since we read a complete JSON
|
|
||||||
// object from it before the error happened.
|
|
||||||
err = dec.d.unmarshal(v)
|
|
||||||
|
|
||||||
// fixup token streaming state
|
|
||||||
dec.tokenValueEnd()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffered returns a reader of the data remaining in the Decoder's
|
|
||||||
// buffer. The reader is valid until the next call to Decode.
|
|
||||||
func (dec *Decoder) Buffered() io.Reader {
|
|
||||||
return bytes.NewReader(dec.buf[dec.scanp:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// readValue reads a JSON value into dec.buf.
|
|
||||||
// It returns the length of the encoding.
|
|
||||||
func (dec *Decoder) readValue() (int, error) {
|
|
||||||
dec.scan.reset()
|
|
||||||
|
|
||||||
scanp := dec.scanp
|
|
||||||
var err error
|
|
||||||
Input:
|
|
||||||
// help the compiler see that scanp is never negative, so it can remove
|
|
||||||
// some bounds checks below.
|
|
||||||
for scanp >= 0 {
|
|
||||||
|
|
||||||
// Look in the buffer for a new value.
|
|
||||||
for ; scanp < len(dec.buf); scanp++ {
|
|
||||||
c := dec.buf[scanp]
|
|
||||||
dec.scan.bytes++
|
|
||||||
switch dec.scan.step(&dec.scan, c) {
|
|
||||||
case scanEnd:
|
|
||||||
// scanEnd is delayed one byte so we decrement
|
|
||||||
// the scanner bytes count by 1 to ensure that
|
|
||||||
// this value is correct in the next call of Decode.
|
|
||||||
dec.scan.bytes--
|
|
||||||
break Input
|
|
||||||
case scanEndObject, scanEndArray:
|
|
||||||
// scanEnd is delayed one byte.
|
|
||||||
// We might block trying to get that byte from src,
|
|
||||||
// so instead invent a space byte.
|
|
||||||
if stateEndValue(&dec.scan, ' ') == scanEnd {
|
|
||||||
scanp++
|
|
||||||
break Input
|
|
||||||
}
|
|
||||||
case scanError:
|
|
||||||
dec.err = dec.scan.err
|
|
||||||
return 0, dec.scan.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Did the last read have an error?
|
|
||||||
// Delayed until now to allow buffer scan.
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
|
||||||
break Input
|
|
||||||
}
|
|
||||||
if nonSpace(dec.buf) {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dec.err = err
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n := scanp - dec.scanp
|
|
||||||
err = dec.refill()
|
|
||||||
scanp = dec.scanp + n
|
|
||||||
}
|
|
||||||
return scanp - dec.scanp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) refill() error {
|
|
||||||
// Make room to read more into the buffer.
|
|
||||||
// First slide down data already consumed.
|
|
||||||
if dec.scanp > 0 {
|
|
||||||
dec.scanned += int64(dec.scanp)
|
|
||||||
n := copy(dec.buf, dec.buf[dec.scanp:])
|
|
||||||
dec.buf = dec.buf[:n]
|
|
||||||
dec.scanp = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grow buffer if not large enough.
|
|
||||||
const minRead = 512
|
|
||||||
if cap(dec.buf)-len(dec.buf) < minRead {
|
|
||||||
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
|
||||||
copy(newBuf, dec.buf)
|
|
||||||
dec.buf = newBuf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read. Delay error for next iteration (after scan).
|
|
||||||
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
|
||||||
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func nonSpace(b []byte) bool {
|
|
||||||
for _, c := range b {
|
|
||||||
if !isSpace(c) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Encoder writes JSON values to an output stream.
|
|
||||||
type Encoder struct {
|
|
||||||
w io.Writer
|
|
||||||
err error
|
|
||||||
escapeHTML bool
|
|
||||||
|
|
||||||
indentBuf *bytes.Buffer
|
|
||||||
indentPrefix string
|
|
||||||
indentValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder returns a new encoder that writes to w.
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{w: w, escapeHTML: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes the JSON encoding of v to the stream,
|
|
||||||
// followed by a newline character.
|
|
||||||
//
|
|
||||||
// See the documentation for Marshal for details about the
|
|
||||||
// conversion of Go values to JSON.
|
|
||||||
func (enc *Encoder) Encode(v any) error {
|
|
||||||
if enc.err != nil {
|
|
||||||
return enc.err
|
|
||||||
}
|
|
||||||
|
|
||||||
e := newEncodeState()
|
|
||||||
defer encodeStatePool.Put(e)
|
|
||||||
|
|
||||||
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate each value with a newline.
|
|
||||||
// This makes the output look a little nicer
|
|
||||||
// when debugging, and some kind of space
|
|
||||||
// is required if the encoded value was a number,
|
|
||||||
// so that the reader knows there aren't more
|
|
||||||
// digits coming.
|
|
||||||
e.WriteByte('\n')
|
|
||||||
|
|
||||||
b := e.Bytes()
|
|
||||||
if enc.indentPrefix != "" || enc.indentValue != "" {
|
|
||||||
if enc.indentBuf == nil {
|
|
||||||
enc.indentBuf = new(bytes.Buffer)
|
|
||||||
}
|
|
||||||
enc.indentBuf.Reset()
|
|
||||||
err = Indent(enc.indentBuf, b, enc.indentPrefix, enc.indentValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b = enc.indentBuf.Bytes()
|
|
||||||
}
|
|
||||||
if _, err = enc.w.Write(b); err != nil {
|
|
||||||
enc.err = err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetIndent instructs the encoder to format each subsequent encoded
|
|
||||||
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
|
|
||||||
// Calling SetIndent("", "") disables indentation.
|
|
||||||
func (enc *Encoder) SetIndent(prefix, indent string) {
|
|
||||||
enc.indentPrefix = prefix
|
|
||||||
enc.indentValue = indent
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEscapeHTML specifies whether problematic HTML characters
|
|
||||||
// should be escaped inside JSON quoted strings.
|
|
||||||
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
|
|
||||||
// to avoid certain safety problems that can arise when embedding JSON in HTML.
|
|
||||||
//
|
|
||||||
// In non-HTML settings where the escaping interferes with the readability
|
|
||||||
// of the output, SetEscapeHTML(false) disables this behavior.
|
|
||||||
func (enc *Encoder) SetEscapeHTML(on bool) {
|
|
||||||
enc.escapeHTML = on
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawMessage is a raw encoded JSON value.
|
|
||||||
// It implements Marshaler and Unmarshaler and can
|
|
||||||
// be used to delay JSON decoding or precompute a JSON encoding.
|
|
||||||
type RawMessage = json.RawMessage
|
|
||||||
|
|
||||||
// A Token holds a value of one of these types:
|
|
||||||
//
|
|
||||||
// Delim, for the four JSON delimiters [ ] { }
|
|
||||||
// bool, for JSON booleans
|
|
||||||
// float64, for JSON numbers
|
|
||||||
// Number, for JSON numbers
|
|
||||||
// string, for JSON string literals
|
|
||||||
// nil, for JSON null
|
|
||||||
type Token any
|
|
||||||
|
|
||||||
const (
|
|
||||||
tokenTopValue = iota
|
|
||||||
tokenArrayStart
|
|
||||||
tokenArrayValue
|
|
||||||
tokenArrayComma
|
|
||||||
tokenObjectStart
|
|
||||||
tokenObjectKey
|
|
||||||
tokenObjectColon
|
|
||||||
tokenObjectValue
|
|
||||||
tokenObjectComma
|
|
||||||
)
|
|
||||||
|
|
||||||
// advance tokenstate from a separator state to a value state
|
|
||||||
func (dec *Decoder) tokenPrepareForDecode() error {
|
|
||||||
// Note: Not calling peek before switch, to avoid
|
|
||||||
// putting peek into the standard Decode path.
|
|
||||||
// peek is only called when using the Token API.
|
|
||||||
switch dec.tokenState {
|
|
||||||
case tokenArrayComma:
|
|
||||||
c, err := dec.peek()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c != ',' {
|
|
||||||
return &SyntaxError{"expected comma after array element", dec.InputOffset()}
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = tokenArrayValue
|
|
||||||
case tokenObjectColon:
|
|
||||||
c, err := dec.peek()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c != ':' {
|
|
||||||
return &SyntaxError{"expected colon after object key", dec.InputOffset()}
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = tokenObjectValue
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) tokenValueAllowed() bool {
|
|
||||||
switch dec.tokenState {
|
|
||||||
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) tokenValueEnd() {
|
|
||||||
switch dec.tokenState {
|
|
||||||
case tokenArrayStart, tokenArrayValue:
|
|
||||||
dec.tokenState = tokenArrayComma
|
|
||||||
case tokenObjectValue:
|
|
||||||
dec.tokenState = tokenObjectComma
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
|
||||||
type Delim rune
|
|
||||||
|
|
||||||
func (d Delim) String() string {
|
|
||||||
return string(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token returns the next JSON token in the input stream.
|
|
||||||
// At the end of the input stream, Token returns nil, io.EOF.
|
|
||||||
//
|
|
||||||
// Token guarantees that the delimiters [ ] { } it returns are
|
|
||||||
// properly nested and matched: if Token encounters an unexpected
|
|
||||||
// delimiter in the input, it will return an error.
|
|
||||||
//
|
|
||||||
// The input stream consists of basic JSON values—bool, string,
|
|
||||||
// number, and null—along with delimiters [ ] { } of type Delim
|
|
||||||
// to mark the start and end of arrays and objects.
|
|
||||||
// Commas and colons are elided.
|
|
||||||
func (dec *Decoder) Token() (Token, error) {
|
|
||||||
for {
|
|
||||||
c, err := dec.peek()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch c {
|
|
||||||
case '[':
|
|
||||||
if !dec.tokenValueAllowed() {
|
|
||||||
return dec.tokenError(c)
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
|
||||||
dec.tokenState = tokenArrayStart
|
|
||||||
return Delim('['), nil
|
|
||||||
|
|
||||||
case ']':
|
|
||||||
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
|
||||||
return dec.tokenError(c)
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
|
||||||
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
|
||||||
dec.tokenValueEnd()
|
|
||||||
return Delim(']'), nil
|
|
||||||
|
|
||||||
case '{':
|
|
||||||
if !dec.tokenValueAllowed() {
|
|
||||||
return dec.tokenError(c)
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
|
||||||
dec.tokenState = tokenObjectStart
|
|
||||||
return Delim('{'), nil
|
|
||||||
|
|
||||||
case '}':
|
|
||||||
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
|
||||||
return dec.tokenError(c)
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
|
||||||
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
|
||||||
dec.tokenValueEnd()
|
|
||||||
return Delim('}'), nil
|
|
||||||
|
|
||||||
case ':':
|
|
||||||
if dec.tokenState != tokenObjectColon {
|
|
||||||
return dec.tokenError(c)
|
|
||||||
}
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = tokenObjectValue
|
|
||||||
continue
|
|
||||||
|
|
||||||
case ',':
|
|
||||||
if dec.tokenState == tokenArrayComma {
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = tokenArrayValue
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if dec.tokenState == tokenObjectComma {
|
|
||||||
dec.scanp++
|
|
||||||
dec.tokenState = tokenObjectKey
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return dec.tokenError(c)
|
|
||||||
|
|
||||||
case '"':
|
|
||||||
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
|
||||||
var x string
|
|
||||||
old := dec.tokenState
|
|
||||||
dec.tokenState = tokenTopValue
|
|
||||||
err := dec.Decode(&x)
|
|
||||||
dec.tokenState = old
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dec.tokenState = tokenObjectColon
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
default:
|
|
||||||
if !dec.tokenValueAllowed() {
|
|
||||||
return dec.tokenError(c)
|
|
||||||
}
|
|
||||||
var x any
|
|
||||||
if err := dec.Decode(&x); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
|
||||||
var context string
|
|
||||||
switch dec.tokenState {
|
|
||||||
case tokenTopValue:
|
|
||||||
context = " looking for beginning of value"
|
|
||||||
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
|
||||||
context = " looking for beginning of value"
|
|
||||||
case tokenArrayComma:
|
|
||||||
context = " after array element"
|
|
||||||
case tokenObjectKey:
|
|
||||||
context = " looking for beginning of object key string"
|
|
||||||
case tokenObjectColon:
|
|
||||||
context = " after object key"
|
|
||||||
case tokenObjectComma:
|
|
||||||
context = " after object key:value pair"
|
|
||||||
}
|
|
||||||
return nil, &SyntaxError{"invalid character " + quoteChar(c) + context, dec.InputOffset()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// More reports whether there is another element in the
|
|
||||||
// current array or object being parsed.
|
|
||||||
func (dec *Decoder) More() bool {
|
|
||||||
c, err := dec.peek()
|
|
||||||
return err == nil && c != ']' && c != '}'
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dec *Decoder) peek() (byte, error) {
|
|
||||||
var err error
|
|
||||||
for {
|
|
||||||
for i := dec.scanp; i < len(dec.buf); i++ {
|
|
||||||
c := dec.buf[i]
|
|
||||||
if isSpace(c) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dec.scanp = i
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
// buffer has been scanned, now report any error
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = dec.refill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputOffset returns the input stream byte offset of the current decoder position.
|
|
||||||
// The offset gives the location of the end of the most recently returned token
|
|
||||||
// and the beginning of the next token.
|
|
||||||
func (dec *Decoder) InputOffset() int64 {
|
|
||||||
return dec.scanned + int64(dec.scanp)
|
|
||||||
}
|
|
||||||
218
vendor/github.com/evanphx/json-patch/v5/internal/json/tables.go
generated
vendored
218
vendor/github.com/evanphx/json-patch/v5/internal/json/tables.go
generated
vendored
@@ -1,218 +0,0 @@
|
|||||||
// Copyright 2016 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
|
|
||||||
|
|
||||||
import "unicode/utf8"
|
|
||||||
|
|
||||||
// safeSet holds the value true if the ASCII character with the given array
|
|
||||||
// position can be represented inside a JSON string without any further
|
|
||||||
// escaping.
|
|
||||||
//
|
|
||||||
// All values are true except for the ASCII control characters (0-31), the
|
|
||||||
// double quote ("), and the backslash character ("\").
|
|
||||||
var safeSet = [utf8.RuneSelf]bool{
|
|
||||||
' ': true,
|
|
||||||
'!': true,
|
|
||||||
'"': false,
|
|
||||||
'#': true,
|
|
||||||
'$': true,
|
|
||||||
'%': true,
|
|
||||||
'&': true,
|
|
||||||
'\'': true,
|
|
||||||
'(': true,
|
|
||||||
')': true,
|
|
||||||
'*': true,
|
|
||||||
'+': true,
|
|
||||||
',': true,
|
|
||||||
'-': true,
|
|
||||||
'.': true,
|
|
||||||
'/': true,
|
|
||||||
'0': true,
|
|
||||||
'1': true,
|
|
||||||
'2': true,
|
|
||||||
'3': true,
|
|
||||||
'4': true,
|
|
||||||
'5': true,
|
|
||||||
'6': true,
|
|
||||||
'7': true,
|
|
||||||
'8': true,
|
|
||||||
'9': true,
|
|
||||||
':': true,
|
|
||||||
';': true,
|
|
||||||
'<': true,
|
|
||||||
'=': true,
|
|
||||||
'>': true,
|
|
||||||
'?': true,
|
|
||||||
'@': true,
|
|
||||||
'A': true,
|
|
||||||
'B': true,
|
|
||||||
'C': true,
|
|
||||||
'D': true,
|
|
||||||
'E': true,
|
|
||||||
'F': true,
|
|
||||||
'G': true,
|
|
||||||
'H': true,
|
|
||||||
'I': true,
|
|
||||||
'J': true,
|
|
||||||
'K': true,
|
|
||||||
'L': true,
|
|
||||||
'M': true,
|
|
||||||
'N': true,
|
|
||||||
'O': true,
|
|
||||||
'P': true,
|
|
||||||
'Q': true,
|
|
||||||
'R': true,
|
|
||||||
'S': true,
|
|
||||||
'T': true,
|
|
||||||
'U': true,
|
|
||||||
'V': true,
|
|
||||||
'W': true,
|
|
||||||
'X': true,
|
|
||||||
'Y': true,
|
|
||||||
'Z': true,
|
|
||||||
'[': true,
|
|
||||||
'\\': false,
|
|
||||||
']': true,
|
|
||||||
'^': true,
|
|
||||||
'_': true,
|
|
||||||
'`': true,
|
|
||||||
'a': true,
|
|
||||||
'b': true,
|
|
||||||
'c': true,
|
|
||||||
'd': true,
|
|
||||||
'e': true,
|
|
||||||
'f': true,
|
|
||||||
'g': true,
|
|
||||||
'h': true,
|
|
||||||
'i': true,
|
|
||||||
'j': true,
|
|
||||||
'k': true,
|
|
||||||
'l': true,
|
|
||||||
'm': true,
|
|
||||||
'n': true,
|
|
||||||
'o': true,
|
|
||||||
'p': true,
|
|
||||||
'q': true,
|
|
||||||
'r': true,
|
|
||||||
's': true,
|
|
||||||
't': true,
|
|
||||||
'u': true,
|
|
||||||
'v': true,
|
|
||||||
'w': true,
|
|
||||||
'x': true,
|
|
||||||
'y': true,
|
|
||||||
'z': true,
|
|
||||||
'{': true,
|
|
||||||
'|': true,
|
|
||||||
'}': true,
|
|
||||||
'~': true,
|
|
||||||
'\u007f': true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// htmlSafeSet holds the value true if the ASCII character with the given
|
|
||||||
// array position can be safely represented inside a JSON string, embedded
|
|
||||||
// inside of HTML <script> tags, without any additional escaping.
|
|
||||||
//
|
|
||||||
// All values are true except for the ASCII control characters (0-31), the
|
|
||||||
// double quote ("), the backslash character ("\"), HTML opening and closing
|
|
||||||
// tags ("<" and ">"), and the ampersand ("&").
|
|
||||||
var htmlSafeSet = [utf8.RuneSelf]bool{
|
|
||||||
' ': true,
|
|
||||||
'!': true,
|
|
||||||
'"': false,
|
|
||||||
'#': true,
|
|
||||||
'$': true,
|
|
||||||
'%': true,
|
|
||||||
'&': false,
|
|
||||||
'\'': true,
|
|
||||||
'(': true,
|
|
||||||
')': true,
|
|
||||||
'*': true,
|
|
||||||
'+': true,
|
|
||||||
',': true,
|
|
||||||
'-': true,
|
|
||||||
'.': true,
|
|
||||||
'/': true,
|
|
||||||
'0': true,
|
|
||||||
'1': true,
|
|
||||||
'2': true,
|
|
||||||
'3': true,
|
|
||||||
'4': true,
|
|
||||||
'5': true,
|
|
||||||
'6': true,
|
|
||||||
'7': true,
|
|
||||||
'8': true,
|
|
||||||
'9': true,
|
|
||||||
':': true,
|
|
||||||
';': true,
|
|
||||||
'<': false,
|
|
||||||
'=': true,
|
|
||||||
'>': false,
|
|
||||||
'?': true,
|
|
||||||
'@': true,
|
|
||||||
'A': true,
|
|
||||||
'B': true,
|
|
||||||
'C': true,
|
|
||||||
'D': true,
|
|
||||||
'E': true,
|
|
||||||
'F': true,
|
|
||||||
'G': true,
|
|
||||||
'H': true,
|
|
||||||
'I': true,
|
|
||||||
'J': true,
|
|
||||||
'K': true,
|
|
||||||
'L': true,
|
|
||||||
'M': true,
|
|
||||||
'N': true,
|
|
||||||
'O': true,
|
|
||||||
'P': true,
|
|
||||||
'Q': true,
|
|
||||||
'R': true,
|
|
||||||
'S': true,
|
|
||||||
'T': true,
|
|
||||||
'U': true,
|
|
||||||
'V': true,
|
|
||||||
'W': true,
|
|
||||||
'X': true,
|
|
||||||
'Y': true,
|
|
||||||
'Z': true,
|
|
||||||
'[': true,
|
|
||||||
'\\': false,
|
|
||||||
']': true,
|
|
||||||
'^': true,
|
|
||||||
'_': true,
|
|
||||||
'`': true,
|
|
||||||
'a': true,
|
|
||||||
'b': true,
|
|
||||||
'c': true,
|
|
||||||
'd': true,
|
|
||||||
'e': true,
|
|
||||||
'f': true,
|
|
||||||
'g': true,
|
|
||||||
'h': true,
|
|
||||||
'i': true,
|
|
||||||
'j': true,
|
|
||||||
'k': true,
|
|
||||||
'l': true,
|
|
||||||
'm': true,
|
|
||||||
'n': true,
|
|
||||||
'o': true,
|
|
||||||
'p': true,
|
|
||||||
'q': true,
|
|
||||||
'r': true,
|
|
||||||
's': true,
|
|
||||||
't': true,
|
|
||||||
'u': true,
|
|
||||||
'v': true,
|
|
||||||
'w': true,
|
|
||||||
'x': true,
|
|
||||||
'y': true,
|
|
||||||
'z': true,
|
|
||||||
'{': true,
|
|
||||||
'|': true,
|
|
||||||
'}': true,
|
|
||||||
'~': true,
|
|
||||||
'\u007f': true,
|
|
||||||
}
|
|
||||||
38
vendor/github.com/evanphx/json-patch/v5/internal/json/tags.go
generated
vendored
38
vendor/github.com/evanphx/json-patch/v5/internal/json/tags.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
// Copyright 2011 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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// tagOptions is the string following a comma in a struct field's "json"
|
|
||||||
// tag, or the empty string. It does not include the leading comma.
|
|
||||||
type tagOptions string
|
|
||||||
|
|
||||||
// parseTag splits a struct field's json tag into its name and
|
|
||||||
// comma-separated options.
|
|
||||||
func parseTag(tag string) (string, tagOptions) {
|
|
||||||
tag, opt, _ := strings.Cut(tag, ",")
|
|
||||||
return tag, tagOptions(opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains reports whether a comma-separated list of options
|
|
||||||
// contains a particular substr flag. substr must be surrounded by a
|
|
||||||
// string boundary or commas.
|
|
||||||
func (o tagOptions) Contains(optionName string) bool {
|
|
||||||
if len(o) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
s := string(o)
|
|
||||||
for s != "" {
|
|
||||||
var name string
|
|
||||||
name, s, _ = strings.Cut(s, ",")
|
|
||||||
if name == optionName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
444
vendor/github.com/evanphx/json-patch/v5/merge.go
generated
vendored
444
vendor/github.com/evanphx/json-patch/v5/merge.go
generated
vendored
@@ -1,444 +0,0 @@
|
|||||||
package jsonpatch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/evanphx/json-patch/v5/internal/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func merge(cur, patch *lazyNode, mergeMerge bool, options *ApplyOptions) *lazyNode {
|
|
||||||
curDoc, err := cur.intoDoc(options)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
pruneNulls(patch, options)
|
|
||||||
return patch
|
|
||||||
}
|
|
||||||
|
|
||||||
patchDoc, err := patch.intoDoc(options)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return patch
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeDocs(curDoc, patchDoc, mergeMerge, options)
|
|
||||||
|
|
||||||
return cur
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeDocs(doc, patch *partialDoc, mergeMerge bool, options *ApplyOptions) {
|
|
||||||
for k, v := range patch.obj {
|
|
||||||
if v == nil {
|
|
||||||
if mergeMerge {
|
|
||||||
idx := -1
|
|
||||||
for i, key := range doc.keys {
|
|
||||||
if key == k {
|
|
||||||
idx = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if idx == -1 {
|
|
||||||
doc.keys = append(doc.keys, k)
|
|
||||||
}
|
|
||||||
doc.obj[k] = nil
|
|
||||||
} else {
|
|
||||||
_ = doc.remove(k, options)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cur, ok := doc.obj[k]
|
|
||||||
|
|
||||||
if !ok || cur == nil {
|
|
||||||
if !mergeMerge {
|
|
||||||
pruneNulls(v, options)
|
|
||||||
}
|
|
||||||
_ = doc.set(k, v, options)
|
|
||||||
} else {
|
|
||||||
_ = doc.set(k, merge(cur, v, mergeMerge, options), options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pruneNulls(n *lazyNode, options *ApplyOptions) {
|
|
||||||
sub, err := n.intoDoc(options)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
pruneDocNulls(sub, options)
|
|
||||||
} else {
|
|
||||||
ary, err := n.intoAry()
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
pruneAryNulls(ary, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pruneDocNulls(doc *partialDoc, options *ApplyOptions) *partialDoc {
|
|
||||||
for k, v := range doc.obj {
|
|
||||||
if v == nil {
|
|
||||||
_ = doc.remove(k, &ApplyOptions{})
|
|
||||||
} else {
|
|
||||||
pruneNulls(v, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
func pruneAryNulls(ary *partialArray, options *ApplyOptions) *partialArray {
|
|
||||||
newAry := []*lazyNode{}
|
|
||||||
|
|
||||||
for _, v := range ary.nodes {
|
|
||||||
if v != nil {
|
|
||||||
pruneNulls(v, options)
|
|
||||||
}
|
|
||||||
newAry = append(newAry, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
ary.nodes = newAry
|
|
||||||
|
|
||||||
return ary
|
|
||||||
}
|
|
||||||
|
|
||||||
var errBadJSONDoc = fmt.Errorf("Invalid JSON Document")
|
|
||||||
var errBadJSONPatch = fmt.Errorf("Invalid JSON Patch")
|
|
||||||
var errBadMergeTypes = fmt.Errorf("Mismatched JSON Documents")
|
|
||||||
|
|
||||||
// MergeMergePatches merges two merge patches together, such that
|
|
||||||
// applying this resulting merged merge patch to a document yields the same
|
|
||||||
// as merging each merge patch to the document in succession.
|
|
||||||
func MergeMergePatches(patch1Data, patch2Data []byte) ([]byte, error) {
|
|
||||||
return doMergePatch(patch1Data, patch2Data, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePatch merges the patchData into the docData.
|
|
||||||
func MergePatch(docData, patchData []byte) ([]byte, error) {
|
|
||||||
return doMergePatch(docData, patchData, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
|
|
||||||
if !json.Valid(docData) {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
if !json.Valid(patchData) {
|
|
||||||
return nil, errBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
options := NewApplyOptions()
|
|
||||||
|
|
||||||
doc := &partialDoc{
|
|
||||||
opts: options,
|
|
||||||
}
|
|
||||||
|
|
||||||
docErr := doc.UnmarshalJSON(docData)
|
|
||||||
|
|
||||||
patch := &partialDoc{
|
|
||||||
opts: options,
|
|
||||||
}
|
|
||||||
|
|
||||||
patchErr := patch.UnmarshalJSON(patchData)
|
|
||||||
|
|
||||||
if isSyntaxError(docErr) {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
if isSyntaxError(patchErr) {
|
|
||||||
return patchData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if docErr == nil && doc.obj == nil {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
if patchErr == nil && patch.obj == nil {
|
|
||||||
return patchData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if docErr != nil || patchErr != nil {
|
|
||||||
// Not an error, just not a doc, so we turn straight into the patch
|
|
||||||
if patchErr == nil {
|
|
||||||
if mergeMerge {
|
|
||||||
doc = patch
|
|
||||||
} else {
|
|
||||||
doc = pruneDocNulls(patch, options)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
patchAry := &partialArray{}
|
|
||||||
patchErr = unmarshal(patchData, &patchAry.nodes)
|
|
||||||
|
|
||||||
if patchErr != nil {
|
|
||||||
// Not an array either, a literal is the result directly.
|
|
||||||
if json.Valid(patchData) {
|
|
||||||
return patchData, nil
|
|
||||||
}
|
|
||||||
return nil, errBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
pruneAryNulls(patchAry, options)
|
|
||||||
|
|
||||||
out, patchErr := json.Marshal(patchAry.nodes)
|
|
||||||
|
|
||||||
if patchErr != nil {
|
|
||||||
return nil, errBadJSONPatch
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mergeDocs(doc, patch, mergeMerge, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSyntaxError(err error) bool {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := err.(*json.SyntaxError); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := err.(*syntaxError); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// resemblesJSONArray indicates whether the byte-slice "appears" to be
|
|
||||||
// a JSON array or not.
|
|
||||||
// False-positives are possible, as this function does not check the internal
|
|
||||||
// structure of the array. It only checks that the outer syntax is present and
|
|
||||||
// correct.
|
|
||||||
func resemblesJSONArray(input []byte) bool {
|
|
||||||
input = bytes.TrimSpace(input)
|
|
||||||
|
|
||||||
hasPrefix := bytes.HasPrefix(input, []byte("["))
|
|
||||||
hasSuffix := bytes.HasSuffix(input, []byte("]"))
|
|
||||||
|
|
||||||
return hasPrefix && hasSuffix
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMergePatch will return a merge patch document capable of converting
|
|
||||||
// the original document(s) to the modified document(s).
|
|
||||||
// The parameters can be bytes of either two JSON Documents, or two arrays of
|
|
||||||
// JSON documents.
|
|
||||||
// The merge patch returned follows the specification defined at http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-07
|
|
||||||
func CreateMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) {
|
|
||||||
originalResemblesArray := resemblesJSONArray(originalJSON)
|
|
||||||
modifiedResemblesArray := resemblesJSONArray(modifiedJSON)
|
|
||||||
|
|
||||||
// Do both byte-slices seem like JSON arrays?
|
|
||||||
if originalResemblesArray && modifiedResemblesArray {
|
|
||||||
return createArrayMergePatch(originalJSON, modifiedJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Are both byte-slices are not arrays? Then they are likely JSON objects...
|
|
||||||
if !originalResemblesArray && !modifiedResemblesArray {
|
|
||||||
return createObjectMergePatch(originalJSON, modifiedJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// None of the above? Then return an error because of mismatched types.
|
|
||||||
return nil, errBadMergeTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
// createObjectMergePatch will return a merge-patch document capable of
|
|
||||||
// converting the original document to the modified document.
|
|
||||||
func createObjectMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) {
|
|
||||||
originalDoc := map[string]interface{}{}
|
|
||||||
modifiedDoc := map[string]interface{}{}
|
|
||||||
|
|
||||||
err := unmarshal(originalJSON, &originalDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unmarshal(modifiedJSON, &modifiedDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
dest, err := getDiff(originalDoc, modifiedDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshal(data []byte, into interface{}) error {
|
|
||||||
return json.UnmarshalValid(data, into)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createArrayMergePatch will return an array of merge-patch documents capable
|
|
||||||
// of converting the original document to the modified document for each
|
|
||||||
// pair of JSON documents provided in the arrays.
|
|
||||||
// Arrays of mismatched sizes will result in an error.
|
|
||||||
func createArrayMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) {
|
|
||||||
originalDocs := []json.RawMessage{}
|
|
||||||
modifiedDocs := []json.RawMessage{}
|
|
||||||
|
|
||||||
err := unmarshal(originalJSON, &originalDocs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unmarshal(modifiedJSON, &modifiedDocs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
total := len(originalDocs)
|
|
||||||
if len(modifiedDocs) != total {
|
|
||||||
return nil, errBadJSONDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
result := []json.RawMessage{}
|
|
||||||
for i := 0; i < len(originalDocs); i++ {
|
|
||||||
original := originalDocs[i]
|
|
||||||
modified := modifiedDocs[i]
|
|
||||||
|
|
||||||
patch, err := createObjectMergePatch(original, modified)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, json.RawMessage(patch))
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the array matches (must be json types).
|
|
||||||
// As is idiomatic for go, an empty array is not the same as a nil array.
|
|
||||||
func matchesArray(a, b []interface{}) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (a == nil && b != nil) || (a != nil && b == nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if !matchesValue(a[i], b[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the values matches (must be json types)
|
|
||||||
// The types of the values must match, otherwise it will always return false
|
|
||||||
// If two map[string]interface{} are given, all elements must match.
|
|
||||||
func matchesValue(av, bv interface{}) bool {
|
|
||||||
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch at := av.(type) {
|
|
||||||
case string:
|
|
||||||
bt := bv.(string)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case json.Number:
|
|
||||||
bt := bv.(json.Number)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
bt := bv.(float64)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case bool:
|
|
||||||
bt := bv.(bool)
|
|
||||||
if bt == at {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
// Both nil, fine.
|
|
||||||
return true
|
|
||||||
case map[string]interface{}:
|
|
||||||
bt := bv.(map[string]interface{})
|
|
||||||
if len(bt) != len(at) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for key := range bt {
|
|
||||||
av, aOK := at[key]
|
|
||||||
bv, bOK := bt[key]
|
|
||||||
if aOK != bOK {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !matchesValue(av, bv) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case []interface{}:
|
|
||||||
bt := bv.([]interface{})
|
|
||||||
return matchesArray(at, bt)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDiff returns the (recursive) difference between a and b as a map[string]interface{}.
|
|
||||||
func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) {
|
|
||||||
into := map[string]interface{}{}
|
|
||||||
for key, bv := range b {
|
|
||||||
av, ok := a[key]
|
|
||||||
// value was added
|
|
||||||
if !ok {
|
|
||||||
into[key] = bv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If types have changed, replace completely
|
|
||||||
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
|
|
||||||
into[key] = bv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Types are the same, compare values
|
|
||||||
switch at := av.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
bt := bv.(map[string]interface{})
|
|
||||||
dst := make(map[string]interface{}, len(bt))
|
|
||||||
dst, err := getDiff(at, bt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(dst) > 0 {
|
|
||||||
into[key] = dst
|
|
||||||
}
|
|
||||||
case string, float64, bool, json.Number:
|
|
||||||
if !matchesValue(av, bv) {
|
|
||||||
into[key] = bv
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
bt := bv.([]interface{})
|
|
||||||
if !matchesArray(at, bt) {
|
|
||||||
into[key] = bv
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
switch bv.(type) {
|
|
||||||
case nil:
|
|
||||||
// Both nil, fine.
|
|
||||||
default:
|
|
||||||
into[key] = bv
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("Unknown type:%T in key %s", av, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now add all deleted values as nil
|
|
||||||
for key := range a {
|
|
||||||
_, found := b[key]
|
|
||||||
if !found {
|
|
||||||
into[key] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return into, nil
|
|
||||||
}
|
|
||||||
1305
vendor/github.com/evanphx/json-patch/v5/patch.go
generated
vendored
1305
vendor/github.com/evanphx/json-patch/v5/patch.go
generated
vendored
File diff suppressed because it is too large
Load Diff
13
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
13
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
freebsd_task:
|
|
||||||
name: 'FreeBSD'
|
|
||||||
freebsd_instance:
|
|
||||||
image_family: freebsd-13-2
|
|
||||||
install_script:
|
|
||||||
- pkg update -f
|
|
||||||
- pkg install -y go
|
|
||||||
test_script:
|
|
||||||
# run tests as user "cirrus" instead of root
|
|
||||||
- pw useradd cirrus -m
|
|
||||||
- chown -R cirrus:cirrus .
|
|
||||||
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
|
||||||
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
|
||||||
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*.go]
|
|
||||||
indent_style = tab
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.{yml,yaml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
@@ -1 +0,0 @@
|
|||||||
go.sum linguist-generated
|
|
||||||
7
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
7
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
# go test -c output
|
|
||||||
*.test
|
|
||||||
*.test.exe
|
|
||||||
|
|
||||||
# Output of go build ./cmd/fsnotify
|
|
||||||
/fsnotify
|
|
||||||
/fsnotify.exe
|
|
||||||
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
@@ -1,2 +0,0 @@
|
|||||||
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
|
||||||
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
|
||||||
541
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
541
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
@@ -1,541 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
Unreleased
|
|
||||||
----------
|
|
||||||
Nothing yet.
|
|
||||||
|
|
||||||
1.7.0 - 2023-10-22
|
|
||||||
------------------
|
|
||||||
This version of fsnotify needs Go 1.17.
|
|
||||||
|
|
||||||
### Additions
|
|
||||||
|
|
||||||
- illumos: add FEN backend to support illumos and Solaris. ([#371])
|
|
||||||
|
|
||||||
- all: add `NewBufferedWatcher()` to use a buffered channel, which can be useful
|
|
||||||
in cases where you can't control the kernel buffer and receive a large number
|
|
||||||
of events in bursts. ([#550], [#572])
|
|
||||||
|
|
||||||
- all: add `AddWith()`, which is identical to `Add()` but allows passing
|
|
||||||
options. ([#521])
|
|
||||||
|
|
||||||
- windows: allow setting the ReadDirectoryChangesW() buffer size with
|
|
||||||
`fsnotify.WithBufferSize()`; the default of 64K is the highest value that
|
|
||||||
works on all platforms and is enough for most purposes, but in some cases a
|
|
||||||
highest buffer is needed. ([#521])
|
|
||||||
|
|
||||||
### Changes and fixes
|
|
||||||
|
|
||||||
- inotify: remove watcher if a watched path is renamed ([#518])
|
|
||||||
|
|
||||||
After a rename the reported name wasn't updated, or even an empty string.
|
|
||||||
Inotify doesn't provide any good facilities to update it, so just remove the
|
|
||||||
watcher. This is already how it worked on kqueue and FEN.
|
|
||||||
|
|
||||||
On Windows this does work, and remains working.
|
|
||||||
|
|
||||||
- windows: don't listen for file attribute changes ([#520])
|
|
||||||
|
|
||||||
File attribute changes are sent as `FILE_ACTION_MODIFIED` by the Windows API,
|
|
||||||
with no way to see if they're a file write or attribute change, so would show
|
|
||||||
up as a fsnotify.Write event. This is never useful, and could result in many
|
|
||||||
spurious Write events.
|
|
||||||
|
|
||||||
- windows: return `ErrEventOverflow` if the buffer is full ([#525])
|
|
||||||
|
|
||||||
Before it would merely return "short read", making it hard to detect this
|
|
||||||
error.
|
|
||||||
|
|
||||||
- kqueue: make sure events for all files are delivered properly when removing a
|
|
||||||
watched directory ([#526])
|
|
||||||
|
|
||||||
Previously they would get sent with `""` (empty string) or `"."` as the path
|
|
||||||
name.
|
|
||||||
|
|
||||||
- kqueue: don't emit spurious Create events for symbolic links ([#524])
|
|
||||||
|
|
||||||
The link would get resolved but kqueue would "forget" it already saw the link
|
|
||||||
itself, resulting on a Create for every Write event for the directory.
|
|
||||||
|
|
||||||
- all: return `ErrClosed` on `Add()` when the watcher is closed ([#516])
|
|
||||||
|
|
||||||
- other: add `Watcher.Errors` and `Watcher.Events` to the no-op `Watcher` in
|
|
||||||
`backend_other.go`, making it easier to use on unsupported platforms such as
|
|
||||||
WASM, AIX, etc. ([#528])
|
|
||||||
|
|
||||||
- other: use the `backend_other.go` no-op if the `appengine` build tag is set;
|
|
||||||
Google AppEngine forbids usage of the unsafe package so the inotify backend
|
|
||||||
won't compile there.
|
|
||||||
|
|
||||||
[#371]: https://github.com/fsnotify/fsnotify/pull/371
|
|
||||||
[#516]: https://github.com/fsnotify/fsnotify/pull/516
|
|
||||||
[#518]: https://github.com/fsnotify/fsnotify/pull/518
|
|
||||||
[#520]: https://github.com/fsnotify/fsnotify/pull/520
|
|
||||||
[#521]: https://github.com/fsnotify/fsnotify/pull/521
|
|
||||||
[#524]: https://github.com/fsnotify/fsnotify/pull/524
|
|
||||||
[#525]: https://github.com/fsnotify/fsnotify/pull/525
|
|
||||||
[#526]: https://github.com/fsnotify/fsnotify/pull/526
|
|
||||||
[#528]: https://github.com/fsnotify/fsnotify/pull/528
|
|
||||||
[#537]: https://github.com/fsnotify/fsnotify/pull/537
|
|
||||||
[#550]: https://github.com/fsnotify/fsnotify/pull/550
|
|
||||||
[#572]: https://github.com/fsnotify/fsnotify/pull/572
|
|
||||||
|
|
||||||
1.6.0 - 2022-10-13
|
|
||||||
------------------
|
|
||||||
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
|
||||||
but not documented). It also increases the minimum Linux version to 2.6.32.
|
|
||||||
|
|
||||||
### Additions
|
|
||||||
|
|
||||||
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
|
||||||
|
|
||||||
This makes checking events a lot easier; for example:
|
|
||||||
|
|
||||||
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Becomes:
|
|
||||||
|
|
||||||
if event.Has(Write) && !event.Has(Remove) {
|
|
||||||
}
|
|
||||||
|
|
||||||
- all: add cmd/fsnotify ([#463])
|
|
||||||
|
|
||||||
A command-line utility for testing and some examples.
|
|
||||||
|
|
||||||
### Changes and fixes
|
|
||||||
|
|
||||||
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
|
||||||
|
|
||||||
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
|
||||||
still exists before emitting events.
|
|
||||||
|
|
||||||
This was inconsistent with other platforms and resulted in inconsistent event
|
|
||||||
reporting (e.g. when a file is quickly removed and re-created), and generally
|
|
||||||
a source of confusion. It was added in 2013 to fix a memory leak that no
|
|
||||||
longer exists.
|
|
||||||
|
|
||||||
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
|
||||||
not watched ([#460])
|
|
||||||
|
|
||||||
- inotify: replace epoll() with non-blocking inotify ([#434])
|
|
||||||
|
|
||||||
Non-blocking inotify was not generally available at the time this library was
|
|
||||||
written in 2014, but now it is. As a result, the minimum Linux version is
|
|
||||||
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
|
||||||
|
|
||||||
- kqueue: don't check for events every 100ms ([#480])
|
|
||||||
|
|
||||||
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
|
||||||
it waits until there is something to do.
|
|
||||||
|
|
||||||
- macos: retry opening files on EINTR ([#475])
|
|
||||||
|
|
||||||
- kqueue: skip unreadable files ([#479])
|
|
||||||
|
|
||||||
kqueue requires a file descriptor for every file in a directory; this would
|
|
||||||
fail if a file was unreadable by the current user. Now these files are simply
|
|
||||||
skipped.
|
|
||||||
|
|
||||||
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
|
||||||
|
|
||||||
- windows: increase buffer size from 4K to 64K ([#485])
|
|
||||||
|
|
||||||
- windows: close file handle on Remove() ([#288])
|
|
||||||
|
|
||||||
- kqueue: put pathname in the error if watching a file fails ([#471])
|
|
||||||
|
|
||||||
- inotify, windows: calling Close() more than once could race ([#465])
|
|
||||||
|
|
||||||
- kqueue: improve Close() performance ([#233])
|
|
||||||
|
|
||||||
- all: various documentation additions and clarifications.
|
|
||||||
|
|
||||||
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
|
||||||
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
|
||||||
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
|
||||||
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
|
||||||
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
|
||||||
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
|
||||||
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
|
||||||
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
|
||||||
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
|
||||||
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
|
||||||
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
|
||||||
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
|
||||||
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
|
||||||
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
|
||||||
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
|
||||||
|
|
||||||
## [1.5.4] - 2022-04-25
|
|
||||||
|
|
||||||
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
|
||||||
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
|
||||||
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
|
||||||
|
|
||||||
## [1.5.3] - 2022-04-22
|
|
||||||
|
|
||||||
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
|
||||||
|
|
||||||
## [1.5.2] - 2022-04-21
|
|
||||||
|
|
||||||
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
|
||||||
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
|
||||||
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
|
||||||
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
|
||||||
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
|
||||||
|
|
||||||
## [1.5.1] - 2021-08-24
|
|
||||||
|
|
||||||
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
|
||||||
|
|
||||||
## [1.5.0] - 2021-08-20
|
|
||||||
|
|
||||||
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
|
||||||
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
|
||||||
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
|
||||||
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
|
||||||
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
|
||||||
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
|
||||||
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
|
||||||
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
|
||||||
|
|
||||||
## [1.4.9] - 2020-03-11
|
|
||||||
|
|
||||||
* Move example usage to the readme #329. This may resolve #328.
|
|
||||||
|
|
||||||
## [1.4.8] - 2020-03-10
|
|
||||||
|
|
||||||
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
|
||||||
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
|
||||||
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
|
||||||
* CI: Less verbosity (@nathany #267)
|
|
||||||
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
|
||||||
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
|
||||||
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
|
||||||
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
|
||||||
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
|
||||||
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
|
||||||
* Linux: open files with close-on-exec (@linxiulei #273)
|
|
||||||
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
|
||||||
* Project: Add go.mod (@nathany #309)
|
|
||||||
* Project: Revise editor config (@nathany #309)
|
|
||||||
* Project: Update copyright for 2019 (@nathany #309)
|
|
||||||
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
|
||||||
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
|
||||||
|
|
||||||
## [1.4.7] - 2018-01-09
|
|
||||||
|
|
||||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
|
||||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
|
||||||
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
|
||||||
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
|
||||||
* Docs: Moved FAQ into the README (thanks @vahe)
|
|
||||||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
|
||||||
* Docs: replace references to OS X with macOS
|
|
||||||
|
|
||||||
## [1.4.2] - 2016-10-10
|
|
||||||
|
|
||||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
|
||||||
|
|
||||||
## [1.4.1] - 2016-10-04
|
|
||||||
|
|
||||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
|
||||||
|
|
||||||
## [1.4.0] - 2016-10-01
|
|
||||||
|
|
||||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
|
||||||
|
|
||||||
## [1.3.1] - 2016-06-28
|
|
||||||
|
|
||||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
|
||||||
|
|
||||||
## [1.3.0] - 2016-04-19
|
|
||||||
|
|
||||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
|
||||||
|
|
||||||
## [1.2.10] - 2016-03-02
|
|
||||||
|
|
||||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
|
||||||
|
|
||||||
## [1.2.9] - 2016-01-13
|
|
||||||
|
|
||||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
|
||||||
|
|
||||||
## [1.2.8] - 2015-12-17
|
|
||||||
|
|
||||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
|
||||||
* inotify: fix race in test
|
|
||||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
|
||||||
|
|
||||||
## [1.2.5] - 2015-10-17
|
|
||||||
|
|
||||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
|
||||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
|
||||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
|
||||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
|
||||||
|
|
||||||
## [1.2.1] - 2015-10-14
|
|
||||||
|
|
||||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
|
||||||
|
|
||||||
## [1.2.0] - 2015-02-08
|
|
||||||
|
|
||||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
|
||||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
|
||||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
|
||||||
|
|
||||||
## [1.1.1] - 2015-02-05
|
|
||||||
|
|
||||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
|
||||||
|
|
||||||
## [1.1.0] - 2014-12-12
|
|
||||||
|
|
||||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
|
||||||
* add low-level functions
|
|
||||||
* only need to store flags on directories
|
|
||||||
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
|
||||||
* done can be an unbuffered channel
|
|
||||||
* remove calls to os.NewSyscallError
|
|
||||||
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
|
||||||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
|
||||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
|
||||||
|
|
||||||
## [1.0.4] - 2014-09-07
|
|
||||||
|
|
||||||
* kqueue: add dragonfly to the build tags.
|
|
||||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
|
||||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
|
||||||
|
|
||||||
## [1.0.3] - 2014-08-19
|
|
||||||
|
|
||||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
|
||||||
|
|
||||||
## [1.0.2] - 2014-08-17
|
|
||||||
|
|
||||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
|
||||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
|
||||||
|
|
||||||
## [1.0.0] - 2014-08-15
|
|
||||||
|
|
||||||
* [API] Remove AddWatch on Windows, use Add.
|
|
||||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
|
||||||
* Minor updates based on feedback from golint.
|
|
||||||
|
|
||||||
## dev / 2014-07-09
|
|
||||||
|
|
||||||
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
|
||||||
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
|
||||||
|
|
||||||
## dev / 2014-07-04
|
|
||||||
|
|
||||||
* kqueue: fix incorrect mutex used in Close()
|
|
||||||
* Update example to demonstrate usage of Op.
|
|
||||||
|
|
||||||
## dev / 2014-06-28
|
|
||||||
|
|
||||||
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
|
||||||
* Fix for String() method on Event (thanks Alex Brainman)
|
|
||||||
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
|
||||||
|
|
||||||
## dev / 2014-06-21
|
|
||||||
|
|
||||||
* Events channel of type Event rather than *Event.
|
|
||||||
* [internal] use syscall constants directly for inotify and kqueue.
|
|
||||||
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
|
||||||
|
|
||||||
## dev / 2014-06-19
|
|
||||||
|
|
||||||
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
|
||||||
* [internal] remove cookie from Event struct (unused).
|
|
||||||
* [internal] Event struct has the same definition across every OS.
|
|
||||||
* [internal] remove internal watch and removeWatch methods.
|
|
||||||
|
|
||||||
## dev / 2014-06-12
|
|
||||||
|
|
||||||
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
|
||||||
* [API] Pluralized channel names: Events and Errors.
|
|
||||||
* [API] Renamed FileEvent struct to Event.
|
|
||||||
* [API] Op constants replace methods like IsCreate().
|
|
||||||
|
|
||||||
## dev / 2014-06-12
|
|
||||||
|
|
||||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
|
||||||
|
|
||||||
## dev / 2014-05-23
|
|
||||||
|
|
||||||
* [API] Remove current implementation of WatchFlags.
|
|
||||||
* current implementation doesn't take advantage of OS for efficiency
|
|
||||||
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
|
||||||
* no tests for the current implementation
|
|
||||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
|
||||||
|
|
||||||
## [0.9.3] - 2014-12-31
|
|
||||||
|
|
||||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
|
||||||
|
|
||||||
## [0.9.2] - 2014-08-17
|
|
||||||
|
|
||||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
|
||||||
|
|
||||||
## [0.9.1] - 2014-06-12
|
|
||||||
|
|
||||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
|
||||||
|
|
||||||
## [0.9.0] - 2014-01-17
|
|
||||||
|
|
||||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
|
||||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
|
||||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
|
||||||
|
|
||||||
## [0.8.12] - 2013-11-13
|
|
||||||
|
|
||||||
* [API] Remove FD_SET and friends from Linux adapter
|
|
||||||
|
|
||||||
## [0.8.11] - 2013-11-02
|
|
||||||
|
|
||||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
|
||||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
|
||||||
|
|
||||||
## [0.8.10] - 2013-10-19
|
|
||||||
|
|
||||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
|
||||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
|
||||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
|
||||||
|
|
||||||
## [0.8.9] - 2013-09-08
|
|
||||||
|
|
||||||
* [Doc] Contributing (thanks @nathany)
|
|
||||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
|
||||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
|
||||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
|
||||||
|
|
||||||
## [0.8.8] - 2013-06-17
|
|
||||||
|
|
||||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
|
||||||
|
|
||||||
## [0.8.7] - 2013-06-03
|
|
||||||
|
|
||||||
* [API] Make syscall flags internal
|
|
||||||
* [Fix] inotify: ignore event changes
|
|
||||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
|
||||||
* [Fix] tests on Windows
|
|
||||||
* lower case error messages
|
|
||||||
|
|
||||||
## [0.8.6] - 2013-05-23
|
|
||||||
|
|
||||||
* kqueue: Use EVT_ONLY flag on Darwin
|
|
||||||
* [Doc] Update README with full example
|
|
||||||
|
|
||||||
## [0.8.5] - 2013-05-09
|
|
||||||
|
|
||||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
|
||||||
|
|
||||||
## [0.8.4] - 2013-04-07
|
|
||||||
|
|
||||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
|
||||||
|
|
||||||
## [0.8.3] - 2013-03-13
|
|
||||||
|
|
||||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
|
||||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
|
||||||
|
|
||||||
## [0.8.2] - 2013-02-07
|
|
||||||
|
|
||||||
* [Doc] add Authors
|
|
||||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
|
||||||
|
|
||||||
## [0.8.1] - 2013-01-09
|
|
||||||
|
|
||||||
* [Fix] Windows path separators
|
|
||||||
* [Doc] BSD License
|
|
||||||
|
|
||||||
## [0.8.0] - 2012-11-09
|
|
||||||
|
|
||||||
* kqueue: directory watching improvements (thanks @vmirage)
|
|
||||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
|
||||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
|
||||||
|
|
||||||
## [0.7.4] - 2012-10-09
|
|
||||||
|
|
||||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
|
||||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
|
||||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
|
||||||
* [Fix] kqueue: modify after recreation of file
|
|
||||||
|
|
||||||
## [0.7.3] - 2012-09-27
|
|
||||||
|
|
||||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
|
||||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
|
||||||
|
|
||||||
## [0.7.2] - 2012-09-01
|
|
||||||
|
|
||||||
* kqueue: events for created directories
|
|
||||||
|
|
||||||
## [0.7.1] - 2012-07-14
|
|
||||||
|
|
||||||
* [Fix] for renaming files
|
|
||||||
|
|
||||||
## [0.7.0] - 2012-07-02
|
|
||||||
|
|
||||||
* [Feature] FSNotify flags
|
|
||||||
* [Fix] inotify: Added file name back to event path
|
|
||||||
|
|
||||||
## [0.6.0] - 2012-06-06
|
|
||||||
|
|
||||||
* kqueue: watch files after directory created (thanks @tmc)
|
|
||||||
|
|
||||||
## [0.5.1] - 2012-05-22
|
|
||||||
|
|
||||||
* [Fix] inotify: remove all watches before Close()
|
|
||||||
|
|
||||||
## [0.5.0] - 2012-05-03
|
|
||||||
|
|
||||||
* [API] kqueue: return errors during watch instead of sending over channel
|
|
||||||
* kqueue: match symlink behavior on Linux
|
|
||||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
|
||||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
|
||||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
|
||||||
|
|
||||||
## [0.4.0] - 2012-03-30
|
|
||||||
|
|
||||||
* Go 1 released: build with go tool
|
|
||||||
* [Feature] Windows support using winfsnotify
|
|
||||||
* Windows does not have attribute change notifications
|
|
||||||
* Roll attribute notifications into IsModify
|
|
||||||
|
|
||||||
## [0.3.0] - 2012-02-19
|
|
||||||
|
|
||||||
* kqueue: add files when watch directory
|
|
||||||
|
|
||||||
## [0.2.0] - 2011-12-30
|
|
||||||
|
|
||||||
* update to latest Go weekly code
|
|
||||||
|
|
||||||
## [0.1.0] - 2011-10-19
|
|
||||||
|
|
||||||
* kqueue: add watch on file creation to match inotify
|
|
||||||
* kqueue: create file event
|
|
||||||
* inotify: ignore `IN_IGNORED` events
|
|
||||||
* event String()
|
|
||||||
* linux: common FileEvent functions
|
|
||||||
* initial commit
|
|
||||||
|
|
||||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
|
||||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
|
||||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
|
||||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
|
||||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
|
||||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
|
||||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
|
||||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
|
||||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
|
||||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
|
||||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
|
||||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
|
||||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
|
||||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
|
||||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
|
||||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
|
||||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
|
||||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
|
||||||
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
Thank you for your interest in contributing to fsnotify! We try to review and
|
|
||||||
merge PRs in a reasonable timeframe, but please be aware that:
|
|
||||||
|
|
||||||
- To avoid "wasted" work, please discus changes on the issue tracker first. You
|
|
||||||
can just send PRs, but they may end up being rejected for one reason or the
|
|
||||||
other.
|
|
||||||
|
|
||||||
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
|
||||||
all supported platforms.
|
|
||||||
|
|
||||||
- Changes will need to be compatible; old code should still compile, and the
|
|
||||||
runtime behaviour can't change in ways that are likely to lead to problems for
|
|
||||||
users.
|
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
|
||||||
platforms. Testing different platforms locally can be done with something like
|
|
||||||
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
|
||||||
|
|
||||||
Use the `-short` flag to make the "stress test" run faster.
|
|
||||||
|
|
||||||
|
|
||||||
[goon]: https://github.com/arp242/goon
|
|
||||||
[Vagrant]: https://www.vagrantup.com/
|
|
||||||
[integration_test.go]: /integration_test.go
|
|
||||||
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
Copyright © 2012 The Go Authors. All rights reserved.
|
|
||||||
Copyright © fsnotify Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer in the documentation and/or
|
|
||||||
other materials provided with the distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its contributors may be used
|
|
||||||
to endorse or promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user