@@ -155,7 +155,7 @@ func (k *Keeper) Close() error {
 	if prev {
 		return errClosed
 	}
-	return k.k.Close()
+	return wrapError(k, k.k.Close())
 }
 
 // ErrorAs converts i to provider-specific types. See
// Copyright 2019 The Go Cloud Development Kit Authors
//
// 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
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package secrets provides an easy and portable way to encrypt and decrypt
// messages.
//
// Subpackages contain distinct implementations of secrets for various
// providers, including Cloud and on-prem solutions. For example, "localsecrets"
// supports encryption/decryption using a locally provided key. Your application
// should import one of these provider-specific subpackages and use its exported
// function(s) to create a *Keeper; do not use the NewKeeper function in this
// package. For example:
//
//  keeper := localsecrets.NewKeeper(myKey)
//  encrypted, err := keeper.Encrypt(ctx.Background(), []byte("text"))
//  ...
//
// Then, write your application code using the *Keeper type. You can easily
// reconfigure your initialization code to choose a different provider.
// You can develop your application locally using localsecrets, or deploy it to
// multiple Cloud providers. You may find http://github.com/google/wire useful
// for managing your initialization code.
//
// Alternatively, you can construct a *Keeper via a URL and OpenKeeper.
// See https://godoc.org/gocloud.dev#hdr-URLs for more information.
//
//
// OpenCensus Integration
//
// OpenCensus supports tracing and metric collection for multiple languages and
// backend providers. See https://opencensus.io.
//
// This API collects OpenCensus traces and metrics for the following methods:
//  - Encrypt
//  - Decrypt
// All trace and metric names begin with the package import path.
// The traces add the method name.
// For example, "gocloud.dev/secrets/Encrypt".
// The metrics are "completed_calls", a count of completed method calls by provider,
// method and status (error code); and "latency", a distribution of method latency
// by provider and method.
// For example, "gocloud.dev/secrets/latency".
//
// To enable trace collection in your application, see "Configure Exporter" at
// https://opencensus.io/quickstart/go/tracing.
// To enable metric collection in your application, see "Exporting stats" at
// https://opencensus.io/quickstart/go/metrics.
package secrets // import "gocloud.dev/secrets"

import (
	"context"
	"net/url"
	"sync"

	"gocloud.dev/internal/gcerr"
	"gocloud.dev/internal/oc"
	"gocloud.dev/internal/openurl"
	"gocloud.dev/secrets/driver"
)

// Keeper does encryption and decryption. To create a Keeper, use constructors
// found in provider-specific subpackages.
type Keeper struct {
	k      driver.Keeper
	tracer *oc.Tracer

	// mu protects the closed variable.
	// Read locks are kept to allow holding a read lock for long-running calls,
	// and thereby prevent closing until a call finishes.
	mu     sync.RWMutex
	closed bool
}

// NewKeeper is intended for use by provider implementations.
var NewKeeper = newKeeper

// newKeeper creates a Keeper.
func newKeeper(k driver.Keeper) *Keeper {
	return &Keeper{
		k: k,
		tracer: &oc.Tracer{
			Package:        pkgName,
			Provider:       oc.ProviderName(k),
			LatencyMeasure: latencyMeasure,
		},
	}
}

const pkgName = "gocloud.dev/secrets"

var (
	latencyMeasure = oc.LatencyMeasure(pkgName)

	// OpenCensusViews are predefined views for OpenCensus metrics.
	// The views include counts and latency distributions for API method calls.
	// See the example at https://godoc.org/go.opencensus.io/stats/view for usage.
	OpenCensusViews = oc.Views(pkgName, latencyMeasure)
)

// Encrypt encrypts the plaintext and returns the cipher message.
func (k *Keeper) Encrypt(ctx context.Context, plaintext []byte) (ciphertext []byte, err error) {
	ctx = k.tracer.Start(ctx, "Encrypt")
	defer func() { k.tracer.End(ctx, err) }()

	k.mu.RLock()
	defer k.mu.RUnlock()
	if k.closed {
		return nil, errClosed
	}

	b, err := k.k.Encrypt(ctx, plaintext)
	if err != nil {
		return nil, wrapError(k, err)
	}
	return b, nil
}

// Decrypt decrypts the ciphertext and returns the plaintext.
func (k *Keeper) Decrypt(ctx context.Context, ciphertext []byte) (plaintext []byte, err error) {
	ctx = k.tracer.Start(ctx, "Decrypt")
	defer func() { k.tracer.End(ctx, err) }()

	k.mu.RLock()
	defer k.mu.RUnlock()
	if k.closed {
		return nil, errClosed
	}

	b, err := k.k.Decrypt(ctx, ciphertext)
	if err != nil {
		return nil, wrapError(k, err)
	}
	return b, nil
}

var errClosed = gcerr.Newf(gcerr.FailedPrecondition, nil, "secrets: Keeper has been closed")

// Close releases any resources used for the Keeper.
func (k *Keeper) Close() error {
	k.mu.Lock()
	prev := k.closed
	k.closed = true
	k.mu.Unlock()
	if prev {
		return errClosed
	}
	return k.k.Close()
}

// ErrorAs converts i to provider-specific types. See
// https://godoc.org/gocloud.dev#hdr-As for background information and the
// provider-specific package documentation for the specific types supported for
// that provider.
//
// ErrorAs panics if i is nil or not a pointer.
// ErrorAs returns false if err == nil.
func (k *Keeper) ErrorAs(err error, i interface{}) bool {
	return gcerr.ErrorAs(err, i, k.k.ErrorAs)
}

func wrapError(k *Keeper, err error) error {
	if gcerr.DoNotWrap(err) {
		return err
	}
	return gcerr.New(k.k.ErrorCode(err), err, 2, "secrets")
}

// KeeperURLOpener represents types that can open Keepers based on a URL.
// The opener must not modify the URL argument. OpenKeeperURL must be safe to
// call from multiple goroutines.
//
// This interface is generally implemented by types in driver packages.
type KeeperURLOpener interface {
	OpenKeeperURL(ctx context.Context, u *url.URL) (*Keeper, error)
}

// URLMux is a URL opener multiplexer. It matches the scheme of the URLs
// against a set of registered schemes and calls the opener that matches the
// URL's scheme.
// See https://godoc.org/gocloud.dev#hdr-URLs for more information.
//
// The zero value is a multiplexer with no registered schemes.
type URLMux struct {
	schemes openurl.SchemeMap
}

// KeeperSchemes returns a sorted slice of the registered Keeper schemes.
func (mux *URLMux) KeeperSchemes() []string { return mux.schemes.Schemes() }

// ValidKeeperScheme returns true iff scheme has been registered for Keepers.
func (mux *URLMux) ValidKeeperScheme(scheme string) bool { return mux.schemes.ValidScheme(scheme) }

// RegisterKeeper registers the opener with the given scheme. If an opener
// already exists for the scheme, RegisterKeeper panics.
func (mux *URLMux) RegisterKeeper(scheme string, opener KeeperURLOpener) {
	mux.schemes.Register("secrets", "Keeper", scheme, opener)
}

// OpenKeeper calls OpenKeeperURL with the URL parsed from urlstr.
// OpenKeeper is safe to call from multiple goroutines.
func (mux *URLMux) OpenKeeper(ctx context.Context, urlstr string) (*Keeper, error) {
	opener, u, err := mux.schemes.FromString("Keeper", urlstr)
	if err != nil {
		return nil, err
	}
	return opener.(KeeperURLOpener).OpenKeeperURL(ctx, u)
}

// OpenKeeperURL dispatches the URL to the opener that is registered with the
// URL's scheme. OpenKeeperURL is safe to call from multiple goroutines.
func (mux *URLMux) OpenKeeperURL(ctx context.Context, u *url.URL) (*Keeper, error) {
	opener, err := mux.schemes.FromURL("Keeper", u)
	if err != nil {
		return nil, err
	}
	return opener.(KeeperURLOpener).OpenKeeperURL(ctx, u)
}

var defaultURLMux = new(URLMux)

// DefaultURLMux returns the URLMux used by OpenKeeper.
//
// Driver packages can use this to register their KeeperURLOpener on the mux.
func DefaultURLMux() *URLMux {
	return defaultURLMux
}

// OpenKeeper opens the Keeper identified by the URL given.
// See the URLOpener documentation in provider-specific subpackages for
// details on supported URL formats, and https://godoc.org/gocloud.dev#hdr-URLs
// for more information.
func OpenKeeper(ctx context.Context, urlstr string) (*Keeper, error) {
	return defaultURLMux.OpenKeeper(ctx, urlstr)
}

wrapError needs to check `err != nil`