You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
543 lines
20 KiB
543 lines
20 KiB
//
|
|
// DISCLAIMER
|
|
//
|
|
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
//
|
|
// Author Ewout Prangsma
|
|
// Author Tomasz Mielech <tomasz@arangodb.com>
|
|
//
|
|
|
|
package driver
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/arangodb/go-driver/util"
|
|
)
|
|
|
|
// ContextKey is an internal type used for holding values in a `context.Context`
|
|
// do not use!.
|
|
type ContextKey string
|
|
|
|
const (
|
|
keyRevision ContextKey = "arangodb-revision"
|
|
keyRevisions ContextKey = "arangodb-revisions"
|
|
keyReturnNew ContextKey = "arangodb-returnNew"
|
|
keyReturnOld ContextKey = "arangodb-returnOld"
|
|
keySilent ContextKey = "arangodb-silent"
|
|
keyWaitForSync ContextKey = "arangodb-waitForSync"
|
|
keyDetails ContextKey = "arangodb-details"
|
|
keyKeepNull ContextKey = "arangodb-keepNull"
|
|
keyMergeObjects ContextKey = "arangodb-mergeObjects"
|
|
keyRawResponse ContextKey = "arangodb-rawResponse"
|
|
keyImportDetails ContextKey = "arangodb-importDetails"
|
|
keyResponse ContextKey = "arangodb-response"
|
|
keyEndpoint ContextKey = "arangodb-endpoint"
|
|
keyIsRestore ContextKey = "arangodb-isRestore"
|
|
keyIsSystem ContextKey = "arangodb-isSystem"
|
|
keyIgnoreRevs ContextKey = "arangodb-ignoreRevs"
|
|
keyEnforceReplicationFactor ContextKey = "arangodb-enforceReplicationFactor"
|
|
keyConfigured ContextKey = "arangodb-configured"
|
|
keyFollowLeaderRedirect ContextKey = "arangodb-followLeaderRedirect"
|
|
keyDBServerID ContextKey = "arangodb-dbserverID"
|
|
keyBatchID ContextKey = "arangodb-batchID"
|
|
keyJobIDResponse ContextKey = "arangodb-jobIDResponse"
|
|
keyAllowDirtyReads ContextKey = "arangodb-allowDirtyReads"
|
|
keyTransactionID ContextKey = "arangodb-transactionID"
|
|
keyOverwriteMode ContextKey = "arangodb-overwriteMode"
|
|
keyOverwrite ContextKey = "arangodb-overwrite"
|
|
keyUseQueueTimeout ContextKey = "arangodb-use-queue-timeout"
|
|
keyMaxQueueTime ContextKey = "arangodb-max-queue-time-seconds"
|
|
)
|
|
|
|
type OverwriteMode string
|
|
|
|
const (
|
|
OverwriteModeIgnore OverwriteMode = "ignore"
|
|
OverwriteModeReplace OverwriteMode = "replace"
|
|
OverwriteModeUpdate OverwriteMode = "update"
|
|
OverwriteModeConflict OverwriteMode = "conflict"
|
|
)
|
|
|
|
// WithRevision is used to configure a context to make document
|
|
// functions specify an explicit revision of the document using an `If-Match` condition.
|
|
func WithRevision(parent context.Context, revision string) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyRevision, revision)
|
|
}
|
|
|
|
// WithRevisions is used to configure a context to make multi-document
|
|
// functions specify explicit revisions of the documents.
|
|
func WithRevisions(parent context.Context, revisions []string) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyRevisions, revisions)
|
|
}
|
|
|
|
// WithReturnNew is used to configure a context to make create, update & replace document
|
|
// functions return the new document into the given result.
|
|
func WithReturnNew(parent context.Context, result interface{}) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyReturnNew, result)
|
|
}
|
|
|
|
// WithReturnOld is used to configure a context to make update & replace document
|
|
// functions return the old document into the given result.
|
|
func WithReturnOld(parent context.Context, result interface{}) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyReturnOld, result)
|
|
}
|
|
|
|
// WithDetails is used to configure a context to make Client.Version return additional details.
|
|
// You can pass a single (optional) boolean. If that is set to false, you explicitly ask to not provide details.
|
|
func WithDetails(parent context.Context, value ...bool) context.Context {
|
|
v := true
|
|
if len(value) == 1 {
|
|
v = value[0]
|
|
}
|
|
return context.WithValue(contextOrBackground(parent), keyDetails, v)
|
|
}
|
|
|
|
// WithEndpoint is used to configure a context that forces a request to be executed on a specific endpoint.
|
|
// If you specify an endpoint like this, failover is disabled.
|
|
// If you specify an unknown endpoint, an InvalidArgumentError is returned from requests.
|
|
func WithEndpoint(parent context.Context, endpoint string) context.Context {
|
|
endpoint = util.FixupEndpointURLScheme(endpoint)
|
|
return context.WithValue(contextOrBackground(parent), keyEndpoint, endpoint)
|
|
}
|
|
|
|
// WithKeepNull is used to configure a context to make update functions keep null fields (value==true)
|
|
// or remove fields with null values (value==false).
|
|
func WithKeepNull(parent context.Context, value bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyKeepNull, value)
|
|
}
|
|
|
|
// WithMergeObjects is used to configure a context to make update functions merge objects present in both
|
|
// the existing document and the patch document (value==true) or overwrite objects in the existing document
|
|
// with objects found in the patch document (value==false)
|
|
func WithMergeObjects(parent context.Context, value bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyMergeObjects, value)
|
|
}
|
|
|
|
// WithSilent is used to configure a context to make functions return an empty result (silent==true),
|
|
// instead of a metadata result (silent==false, default).
|
|
// You can pass a single (optional) boolean. If that is set to false, you explicitly ask to return metadata result.
|
|
func WithSilent(parent context.Context, value ...bool) context.Context {
|
|
v := true
|
|
if len(value) == 1 {
|
|
v = value[0]
|
|
}
|
|
return context.WithValue(contextOrBackground(parent), keySilent, v)
|
|
}
|
|
|
|
// WithWaitForSync is used to configure a context to make modification
|
|
// functions wait until the data has been synced to disk (or not).
|
|
// You can pass a single (optional) boolean. If that is set to false, you explicitly do not wait for
|
|
// data to be synced to disk.
|
|
func WithWaitForSync(parent context.Context, value ...bool) context.Context {
|
|
v := true
|
|
if len(value) == 1 {
|
|
v = value[0]
|
|
}
|
|
return context.WithValue(contextOrBackground(parent), keyWaitForSync, v)
|
|
}
|
|
|
|
// WithAllowDirtyReads is used in an active failover deployment to allow reads from the follower.
|
|
// You can pass a reference to a boolean that will set according to whether a potentially dirty read
|
|
// happened or not. nil is allowed.
|
|
// This is valid for document reads, aql queries, gharial vertex and edge reads.
|
|
// Since 3.10 This feature is available in the Enterprise Edition for cluster deployments as well
|
|
func WithAllowDirtyReads(parent context.Context, wasDirtyRead *bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyAllowDirtyReads, wasDirtyRead)
|
|
}
|
|
|
|
// WithArangoQueueTimeout is used to enable Queue timeout on the server side.
|
|
// If WithArangoQueueTime is used then its value takes precedence in other case value of ctx.Deadline will be taken
|
|
func WithArangoQueueTimeout(parent context.Context, useQueueTimeout bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyUseQueueTimeout, useQueueTimeout)
|
|
}
|
|
|
|
// WithArangoQueueTime defines max queue timeout on the server side.
|
|
func WithArangoQueueTime(parent context.Context, duration time.Duration) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyMaxQueueTime, duration)
|
|
}
|
|
|
|
// WithRawResponse is used to configure a context that will make all functions store the raw response into a
|
|
// buffer.
|
|
func WithRawResponse(parent context.Context, value *[]byte) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyRawResponse, value)
|
|
}
|
|
|
|
// WithResponse is used to configure a context that will make all functions store the response into the given value.
|
|
func WithResponse(parent context.Context, value *Response) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyResponse, value)
|
|
}
|
|
|
|
// WithImportDetails is used to configure a context that will make import document requests return
|
|
// details about documents that could not be imported.
|
|
func WithImportDetails(parent context.Context, value *[]string) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyImportDetails, value)
|
|
}
|
|
|
|
// WithIsRestore is used to configure a context to make insert functions use the "isRestore=<value>"
|
|
// setting.
|
|
// Note: This function is intended for internal (replication) use. It is NOT intended to
|
|
// be used by normal client. This CAN screw up your database.
|
|
func WithIsRestore(parent context.Context, value bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyIsRestore, value)
|
|
}
|
|
|
|
// WithIsSystem is used to configure a context to make insert functions use the "isSystem=<value>"
|
|
// setting.
|
|
func WithIsSystem(parent context.Context, value bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyIsSystem, value)
|
|
}
|
|
|
|
// WithIgnoreRevisions is used to configure a context to make modification
|
|
// functions ignore revisions in the update.
|
|
// Do not use in combination with WithRevision or WithRevisions.
|
|
func WithIgnoreRevisions(parent context.Context, value ...bool) context.Context {
|
|
v := true
|
|
if len(value) == 1 {
|
|
v = value[0]
|
|
}
|
|
return context.WithValue(contextOrBackground(parent), keyIgnoreRevs, v)
|
|
}
|
|
|
|
// WithEnforceReplicationFactor is used to configure a context to make adding collections
|
|
// fail if the replication factor is too high (default or true) or
|
|
// silently accept (false).
|
|
func WithEnforceReplicationFactor(parent context.Context, value bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyEnforceReplicationFactor, value)
|
|
}
|
|
|
|
// WithConfigured is used to configure a context to return the configured value of
|
|
// a user grant instead of the effective grant.
|
|
func WithConfigured(parent context.Context, value ...bool) context.Context {
|
|
v := true
|
|
if len(value) == 1 {
|
|
v = value[0]
|
|
}
|
|
return context.WithValue(contextOrBackground(parent), keyConfigured, v)
|
|
}
|
|
|
|
// WithFollowLeaderRedirect is used to configure a context to return turn on/off
|
|
// following redirection responses from the server when the request is answered by a follower.
|
|
// Default behavior is "on".
|
|
func WithFollowLeaderRedirect(parent context.Context, value bool) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyFollowLeaderRedirect, value)
|
|
}
|
|
|
|
// WithDBServerID is used to configure a context that includes an ID of a specific DBServer.
|
|
func WithDBServerID(parent context.Context, id string) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyDBServerID, id)
|
|
}
|
|
|
|
// WithBatchID is used to configure a context that includes an ID of a Batch.
|
|
// This is used in replication functions.
|
|
func WithBatchID(parent context.Context, id string) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyBatchID, id)
|
|
}
|
|
|
|
// WithJobIDResponse is used to configure a context that includes a reference to a JobID
|
|
// that is filled on a error-free response.
|
|
// This is used in cluster functions.
|
|
func WithJobIDResponse(parent context.Context, jobID *string) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyJobIDResponse, jobID)
|
|
}
|
|
|
|
// WithTransactionID is used to bind a request to a specific transaction
|
|
func WithTransactionID(parent context.Context, tid TransactionID) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyTransactionID, tid)
|
|
}
|
|
|
|
// WithOverwriteMode is used to configure a context to instruct how a document should be overwritten.
|
|
func WithOverwriteMode(parent context.Context, mode OverwriteMode) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyOverwriteMode, mode)
|
|
}
|
|
|
|
// WithOverwrite is used to configure a context to instruct if a document should be overwritten.
|
|
func WithOverwrite(parent context.Context) context.Context {
|
|
return context.WithValue(contextOrBackground(parent), keyOverwrite, true)
|
|
}
|
|
|
|
type contextSettings struct {
|
|
Silent bool
|
|
WaitForSync bool
|
|
ReturnOld interface{}
|
|
ReturnNew interface{}
|
|
Revision string
|
|
Revisions []string
|
|
ImportDetails *[]string
|
|
IsRestore bool
|
|
IsSystem bool
|
|
AllowDirtyReads bool
|
|
DirtyReadFlag *bool
|
|
IgnoreRevs *bool
|
|
EnforceReplicationFactor *bool
|
|
Configured *bool
|
|
FollowLeaderRedirect *bool
|
|
DBServerID string
|
|
BatchID string
|
|
JobIDResponse *string
|
|
OverwriteMode OverwriteMode
|
|
Overwrite bool
|
|
QueueTimeout bool
|
|
MaxQueueTime time.Duration
|
|
}
|
|
|
|
// loadContextResponseValue loads generic values from the response and puts it into variables specified
|
|
// via context values.
|
|
func loadContextResponseValues(cs contextSettings, resp Response) {
|
|
// Parse potential dirty read
|
|
if cs.DirtyReadFlag != nil {
|
|
if dirtyRead := resp.Header("X-Arango-Potential-Dirty-Read"); dirtyRead != "" {
|
|
*cs.DirtyReadFlag = true // The documentation does not say anything about the actual value (dirtyRead == "true")
|
|
} else {
|
|
*cs.DirtyReadFlag = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// setDirtyReadFlagIfRequired is a helper function that sets the bool reference for allowDirtyReads to the
|
|
// specified value, if required and reference is not nil.
|
|
func setDirtyReadFlagIfRequired(ctx context.Context, wasDirty bool) {
|
|
if v := ctx.Value(keyAllowDirtyReads); v != nil {
|
|
if ref, ok := v.(*bool); ok && ref != nil {
|
|
*ref = wasDirty
|
|
}
|
|
}
|
|
}
|
|
|
|
// applyContextSettings returns the settings configured in the context in the given request.
|
|
// It then returns information about the applied settings that may be needed later in API implementation functions.
|
|
func applyContextSettings(ctx context.Context, req Request) contextSettings {
|
|
result := contextSettings{}
|
|
if ctx == nil {
|
|
return result
|
|
}
|
|
// Details
|
|
if v := ctx.Value(keyDetails); v != nil {
|
|
if details, ok := v.(bool); ok {
|
|
req.SetQuery("details", strconv.FormatBool(details))
|
|
}
|
|
}
|
|
// KeepNull
|
|
if v := ctx.Value(keyKeepNull); v != nil {
|
|
if keepNull, ok := v.(bool); ok {
|
|
req.SetQuery("keepNull", strconv.FormatBool(keepNull))
|
|
}
|
|
}
|
|
// MergeObjects
|
|
if v := ctx.Value(keyMergeObjects); v != nil {
|
|
if mergeObjects, ok := v.(bool); ok {
|
|
req.SetQuery("mergeObjects", strconv.FormatBool(mergeObjects))
|
|
}
|
|
}
|
|
// Silent
|
|
if v := ctx.Value(keySilent); v != nil {
|
|
if silent, ok := v.(bool); ok {
|
|
req.SetQuery("silent", strconv.FormatBool(silent))
|
|
result.Silent = silent
|
|
}
|
|
}
|
|
// WaitForSync
|
|
if v := ctx.Value(keyWaitForSync); v != nil {
|
|
if waitForSync, ok := v.(bool); ok {
|
|
req.SetQuery("waitForSync", strconv.FormatBool(waitForSync))
|
|
result.WaitForSync = waitForSync
|
|
}
|
|
}
|
|
// AllowDirtyReads
|
|
if v := ctx.Value(keyAllowDirtyReads); v != nil {
|
|
req.SetHeader("x-arango-allow-dirty-read", "true")
|
|
result.AllowDirtyReads = true
|
|
if dirtyReadFlag, ok := v.(*bool); ok {
|
|
result.DirtyReadFlag = dirtyReadFlag
|
|
}
|
|
}
|
|
|
|
// Enable Queue timeout
|
|
if v := ctx.Value(keyUseQueueTimeout); v != nil {
|
|
if useQueueTimeout, ok := v.(bool); ok && useQueueTimeout {
|
|
result.QueueTimeout = useQueueTimeout
|
|
if v := ctx.Value(keyMaxQueueTime); v != nil {
|
|
if timeout, ok := v.(time.Duration); ok {
|
|
result.MaxQueueTime = timeout
|
|
req.SetHeader("x-arango-queue-time-seconds", fmt.Sprint(timeout.Seconds()))
|
|
}
|
|
} else if deadline, ok := ctx.Deadline(); ok {
|
|
timeout := deadline.Sub(time.Now())
|
|
req.SetHeader("x-arango-queue-time-seconds", fmt.Sprint(timeout.Seconds()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TransactionID
|
|
if v := ctx.Value(keyTransactionID); v != nil {
|
|
req.SetHeader("x-arango-trx-id", string(v.(TransactionID)))
|
|
}
|
|
// ReturnOld
|
|
if v := ctx.Value(keyReturnOld); v != nil {
|
|
req.SetQuery("returnOld", "true")
|
|
result.ReturnOld = v
|
|
}
|
|
// ReturnNew
|
|
if v := ctx.Value(keyReturnNew); v != nil {
|
|
req.SetQuery("returnNew", "true")
|
|
result.ReturnNew = v
|
|
}
|
|
// If-Match
|
|
if v := ctx.Value(keyRevision); v != nil {
|
|
if rev, ok := v.(string); ok {
|
|
req.SetHeader("If-Match", rev)
|
|
result.Revision = rev
|
|
}
|
|
}
|
|
// Revisions
|
|
if v := ctx.Value(keyRevisions); v != nil {
|
|
if revs, ok := v.([]string); ok {
|
|
req.SetQuery("ignoreRevs", "false")
|
|
result.Revisions = revs
|
|
}
|
|
}
|
|
// ImportDetails
|
|
if v := ctx.Value(keyImportDetails); v != nil {
|
|
if details, ok := v.(*[]string); ok {
|
|
req.SetQuery("details", "true")
|
|
result.ImportDetails = details
|
|
}
|
|
}
|
|
// IsRestore
|
|
if v := ctx.Value(keyIsRestore); v != nil {
|
|
if isRestore, ok := v.(bool); ok {
|
|
req.SetQuery("isRestore", strconv.FormatBool(isRestore))
|
|
result.IsRestore = isRestore
|
|
}
|
|
}
|
|
// IsSystem
|
|
if v := ctx.Value(keyIsSystem); v != nil {
|
|
if isSystem, ok := v.(bool); ok {
|
|
req.SetQuery("isSystem", strconv.FormatBool(isSystem))
|
|
result.IsSystem = isSystem
|
|
}
|
|
}
|
|
// IgnoreRevs
|
|
if v := ctx.Value(keyIgnoreRevs); v != nil {
|
|
if ignoreRevs, ok := v.(bool); ok {
|
|
req.SetQuery("ignoreRevs", strconv.FormatBool(ignoreRevs))
|
|
result.IgnoreRevs = &ignoreRevs
|
|
}
|
|
}
|
|
// EnforeReplicationFactor
|
|
if v := ctx.Value(keyEnforceReplicationFactor); v != nil {
|
|
if enforceReplicationFactor, ok := v.(bool); ok {
|
|
req.SetQuery("enforceReplicationFactor", strconv.FormatBool(enforceReplicationFactor))
|
|
result.EnforceReplicationFactor = &enforceReplicationFactor
|
|
}
|
|
}
|
|
// Configured
|
|
if v := ctx.Value(keyConfigured); v != nil {
|
|
if configured, ok := v.(bool); ok {
|
|
req.SetQuery("configured", strconv.FormatBool(configured))
|
|
result.Configured = &configured
|
|
}
|
|
}
|
|
// FollowLeaderRedirect
|
|
if v := ctx.Value(keyFollowLeaderRedirect); v != nil {
|
|
if followLeaderRedirect, ok := v.(bool); ok {
|
|
result.FollowLeaderRedirect = &followLeaderRedirect
|
|
}
|
|
}
|
|
// DBServerID
|
|
if v := ctx.Value(keyDBServerID); v != nil {
|
|
if id, ok := v.(string); ok {
|
|
req.SetQuery("DBserver", id)
|
|
result.DBServerID = id
|
|
}
|
|
}
|
|
// BatchID
|
|
if v := ctx.Value(keyBatchID); v != nil {
|
|
if id, ok := v.(string); ok {
|
|
req.SetQuery("batchId", id)
|
|
result.BatchID = id
|
|
}
|
|
}
|
|
// JobIDResponse
|
|
if v := ctx.Value(keyJobIDResponse); v != nil {
|
|
if idRef, ok := v.(*string); ok {
|
|
result.JobIDResponse = idRef
|
|
}
|
|
}
|
|
// OverwriteMode
|
|
if v := ctx.Value(keyOverwriteMode); v != nil {
|
|
if mode, ok := v.(OverwriteMode); ok {
|
|
req.SetQuery("overwriteMode", string(mode))
|
|
result.OverwriteMode = mode
|
|
}
|
|
}
|
|
|
|
if v := ctx.Value(keyOverwrite); v != nil {
|
|
if overwrite, ok := v.(bool); ok && overwrite {
|
|
req.SetQuery("overwrite", "true")
|
|
result.Overwrite = true
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// contextOrBackground returns the given context if it is not nil.
|
|
// Returns context.Background() otherwise.
|
|
func contextOrBackground(ctx context.Context) context.Context {
|
|
if ctx != nil {
|
|
return ctx
|
|
}
|
|
return context.Background()
|
|
}
|
|
|
|
// withDocumentAt returns a context derived from the given parent context to be used in multi-document options
|
|
// that needs a client side "loop" implementation.
|
|
// It handle:
|
|
// - WithRevisions
|
|
// - WithReturnNew
|
|
// - WithReturnOld
|
|
func withDocumentAt(ctx context.Context, index int) (context.Context, error) {
|
|
if ctx == nil {
|
|
return nil, nil
|
|
}
|
|
// Revisions
|
|
if v := ctx.Value(keyRevisions); v != nil {
|
|
if revs, ok := v.([]string); ok {
|
|
if index >= len(revs) {
|
|
return nil, WithStack(InvalidArgumentError{Message: "Index out of range: revisions"})
|
|
}
|
|
ctx = WithRevision(ctx, revs[index])
|
|
}
|
|
}
|
|
// ReturnOld
|
|
if v := ctx.Value(keyReturnOld); v != nil {
|
|
val := reflect.ValueOf(v)
|
|
ctx = WithReturnOld(ctx, val.Index(index).Addr().Interface())
|
|
}
|
|
// ReturnNew
|
|
if v := ctx.Value(keyReturnNew); v != nil {
|
|
val := reflect.ValueOf(v)
|
|
ctx = WithReturnNew(ctx, val.Index(index).Addr().Interface())
|
|
}
|
|
|
|
return ctx, nil
|
|
}
|
|
|