[v4] improve metadata documentation (#216)
* add usage docs for context types and metadata, improve comments * changes after review
This commit is contained in:
294
metadata/context.go
Normal file
294
metadata/context.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// In the metadata package, context and metadata are treated as immutable.
|
||||
// Deep copies of metadata are made to keep things safe and correct.
|
||||
// If a user takes a map and changes it across threads, it's their responsibility.
|
||||
//
|
||||
// 1. Incoming Context
|
||||
//
|
||||
// This context is provided by an external system and populated by the server or broker of the micro framework.
|
||||
// It should not be modified. The idea is to extract all necessary data from it,
|
||||
// validate the data, and transfer it into the current context.
|
||||
// After that, only the current context should be used throughout the code.
|
||||
//
|
||||
// 2. Current Context
|
||||
//
|
||||
// This is the context used during the execution flow.
|
||||
// You can add any needed metadata to it and pass it through your code.
|
||||
//
|
||||
// 3. Outgoing Context
|
||||
//
|
||||
// This context is for sending data to external systems.
|
||||
// You can add what you need before sending it out.
|
||||
// But it’s usually better to build and prepare this context right before making the external call,
|
||||
// instead of changing it in many places.
|
||||
//
|
||||
// Execution Flow:
|
||||
//
|
||||
// [External System]
|
||||
// ↓
|
||||
// [Incoming Context]
|
||||
// ↓
|
||||
// [Extract & Validate Metadata from Incoming Context]
|
||||
// ↓
|
||||
// [Prepare Current Context]
|
||||
// ↓
|
||||
// [Enrich Current Context]
|
||||
// ↓
|
||||
// [Business Logic]
|
||||
// ↓
|
||||
// [Prepare Outgoing Context]
|
||||
// ↓
|
||||
// [External System Call]
|
||||
|
||||
type (
|
||||
metadataCurrentKey struct{}
|
||||
metadataIncomingKey struct{}
|
||||
metadataOutgoingKey struct{}
|
||||
|
||||
rawMetadata struct {
|
||||
md Metadata
|
||||
added [][]string
|
||||
}
|
||||
)
|
||||
|
||||
// NewContext creates a new context with the provided Metadata attached.
|
||||
// The Metadata must not be modified after calling this function.
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewIncomingContext creates a new context with the provided incoming Metadata attached.
|
||||
// The Metadata must not be modified after calling this function.
|
||||
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewOutgoingContext creates a new context with the provided outgoing Metadata attached.
|
||||
// The Metadata must not be modified after calling this function.
|
||||
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// AppendContext returns a new context with the provided key-value pairs (kv)
|
||||
// merged with any existing metadata in the context. For a description of kv,
|
||||
// please refer to the Pairs documentation.
|
||||
func AppendContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// AppendOutgoingContext returns a new context with the provided key-value pairs (kv)
|
||||
// merged with any existing metadata in the context. For a description of kv,
|
||||
// please refer to the Pairs documentation.
|
||||
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// FromContext retrieves a deep copy of the metadata from the context and returns it
|
||||
// with a boolean indicating if it was found.
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustContext retrieves a deep copy of the metadata from the context and panics
|
||||
// if the metadata is not found.
|
||||
func MustContext(ctx context.Context) Metadata {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromIncomingContext retrieves a deep copy of the metadata from the context and returns it
|
||||
// with a boolean indicating if it was found.
|
||||
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromIncomingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustIncomingContext retrieves a deep copy of the metadata from the context and panics
|
||||
// if the metadata is not found.
|
||||
func MustIncomingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromOutgoingContext retrieves a deep copy of the metadata from the context and returns it
|
||||
// with a boolean indicating if it was found.
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
// MustOutgoingContext retrieves a deep copy of the metadata from the context and panics
|
||||
// if the metadata is not found.
|
||||
func MustOutgoingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// ValueFromCurrentContext retrieves a deep copy of the metadata for the given key
|
||||
// from the context, performing a case-insensitive search if needed. Returns nil if not found.
|
||||
func ValueFromCurrentContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case-insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueFromIncomingContext retrieves a deep copy of the metadata for the given key
|
||||
// from the context, performing a case-insensitive search if needed. Returns nil if not found.
|
||||
func ValueFromIncomingContext(ctx context.Context, key string) []string {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := raw.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range raw.md {
|
||||
// Case-insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueFromOutgoingContext retrieves a deep copy of the metadata for the given key
|
||||
// from the context, performing a case-insensitive search if needed. Returns nil if not found.
|
||||
func ValueFromOutgoingContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case-insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -2,18 +2,18 @@
|
||||
package metadata
|
||||
|
||||
var (
|
||||
// HeaderTopic is the header name that contains topic name
|
||||
// HeaderTopic is the header name that contains topic name.
|
||||
HeaderTopic = "Micro-Topic"
|
||||
// HeaderContentType specifies content type of message
|
||||
// HeaderContentType specifies content type of message.
|
||||
HeaderContentType = "Content-Type"
|
||||
// HeaderEndpoint specifies endpoint in service
|
||||
// HeaderEndpoint specifies endpoint in service.
|
||||
HeaderEndpoint = "Micro-Endpoint"
|
||||
// HeaderService specifies service
|
||||
// HeaderService specifies service.
|
||||
HeaderService = "Micro-Service"
|
||||
// HeaderTimeout specifies timeout of operation
|
||||
// HeaderTimeout specifies timeout of operation.
|
||||
HeaderTimeout = "Micro-Timeout"
|
||||
// HeaderAuthorization specifies Authorization header
|
||||
// HeaderAuthorization specifies Authorization header.
|
||||
HeaderAuthorization = "Authorization"
|
||||
// HeaderXRequestID specifies request id
|
||||
// HeaderXRequestID specifies request id.
|
||||
HeaderXRequestID = "X-Request-Id"
|
||||
)
|
||||
|
7
metadata/helpers.go
Normal file
7
metadata/helpers.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package metadata
|
||||
|
||||
func copyOf(v []string) []string {
|
||||
vals := make([]string, len(v))
|
||||
copy(vals, v)
|
||||
return vals
|
||||
}
|
37
metadata/iterator.go
Normal file
37
metadata/iterator.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package metadata
|
||||
|
||||
import "sort"
|
||||
|
||||
type Iterator struct {
|
||||
md Metadata
|
||||
keys []string
|
||||
cur int
|
||||
cnt int
|
||||
}
|
||||
|
||||
// Next advances the iterator to the next element.
|
||||
func (iter *Iterator) Next(k *string, v *[]string) bool {
|
||||
if iter.cur+1 > iter.cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
if k != nil && v != nil {
|
||||
*k = iter.keys[iter.cur]
|
||||
vv := iter.md[*k]
|
||||
*v = make([]string, len(vv))
|
||||
copy(*v, vv)
|
||||
iter.cur++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Iterator returns an iterator for iterating over metadata in sorted order.
|
||||
func (md Metadata) Iterator() *Iterator {
|
||||
iter := &Iterator{md: md, cnt: len(md)}
|
||||
iter.keys = make([]string, 0, iter.cnt)
|
||||
for k := range md {
|
||||
iter.keys = append(iter.keys, k)
|
||||
}
|
||||
sort.Strings(iter.keys)
|
||||
return iter
|
||||
}
|
@@ -1,21 +1,18 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// defaultMetadataSize used when need to init new Metadata
|
||||
// defaultMetadataSize is used when initializing new Metadata.
|
||||
var defaultMetadataSize = 2
|
||||
|
||||
// Metadata is a mapping from metadata keys to values. Users should use the following
|
||||
// two convenience functions New and Pairs to generate Metadata.
|
||||
// Metadata maps keys to values. Use the New, NewWithMetadata and Pairs functions to create it.
|
||||
type Metadata map[string][]string
|
||||
|
||||
// New creates an zero Metadata.
|
||||
// New creates a zero-value Metadata with the specified size.
|
||||
func New(l int) Metadata {
|
||||
if l == 0 {
|
||||
l = defaultMetadataSize
|
||||
@@ -24,7 +21,7 @@ func New(l int) Metadata {
|
||||
return md
|
||||
}
|
||||
|
||||
// NewWithMetadata creates an Metadata from a given key-value map.
|
||||
// NewWithMetadata creates a Metadata from the provided key-value map.
|
||||
func NewWithMetadata(m map[string]string) Metadata {
|
||||
md := make(Metadata, len(m))
|
||||
for key, val := range m {
|
||||
@@ -33,8 +30,7 @@ func NewWithMetadata(m map[string]string) Metadata {
|
||||
return md
|
||||
}
|
||||
|
||||
// Pairs returns an Metadata formed by the mapping of key, value ...
|
||||
// Pairs panics if len(kv) is odd.
|
||||
// Pairs returns a Metadata formed from the key-value mapping. It panics if the length of kv is odd.
|
||||
func Pairs(kv ...string) Metadata {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
|
||||
@@ -46,12 +42,19 @@ func Pairs(kv ...string) Metadata {
|
||||
return md
|
||||
}
|
||||
|
||||
// Len returns the number of items in Metadata.
|
||||
func (md Metadata) Len() int {
|
||||
return len(md)
|
||||
// Join combines multiple Metadatas into a single Metadata.
|
||||
// The order of values for each key is determined by the order in which the Metadatas are provided to Join.
|
||||
func Join(mds ...Metadata) Metadata {
|
||||
out := Metadata{}
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Copy returns a copy of Metadata.
|
||||
// Copy returns a deep copy of Metadata.
|
||||
func Copy(src Metadata) Metadata {
|
||||
out := make(Metadata, len(src))
|
||||
for k, v := range src {
|
||||
@@ -60,7 +63,7 @@ func Copy(src Metadata) Metadata {
|
||||
return out
|
||||
}
|
||||
|
||||
// Copy returns a copy of Metadata.
|
||||
// Copy returns a deep copy of Metadata.
|
||||
func (md Metadata) Copy() Metadata {
|
||||
out := make(Metadata, len(md))
|
||||
for k, v := range md {
|
||||
@@ -69,7 +72,19 @@ func (md Metadata) Copy() Metadata {
|
||||
return out
|
||||
}
|
||||
|
||||
// AsMap returns a copy of Metadata with map[string]string.
|
||||
// CopyTo performs a deep copy of Metadata to the out.
|
||||
func (md Metadata) CopyTo(out Metadata) {
|
||||
for k, v := range md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of items in Metadata.
|
||||
func (md Metadata) Len() int {
|
||||
return len(md)
|
||||
}
|
||||
|
||||
// AsMap returns a deep copy of Metadata as a map[string]string
|
||||
func (md Metadata) AsMap() map[string]string {
|
||||
out := make(map[string]string, len(md))
|
||||
for k, v := range md {
|
||||
@@ -78,8 +93,7 @@ func (md Metadata) AsMap() map[string]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// AsHTTP1 returns a copy of Metadata
|
||||
// with CanonicalMIMEHeaderKey.
|
||||
// AsHTTP1 returns a deep copy of Metadata with keys converted to canonical MIME header key format.
|
||||
func (md Metadata) AsHTTP1() map[string][]string {
|
||||
out := make(map[string][]string, len(md))
|
||||
for k, v := range md {
|
||||
@@ -88,8 +102,7 @@ func (md Metadata) AsHTTP1() map[string][]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// AsHTTP1 returns a copy of Metadata
|
||||
// with strings.ToLower.
|
||||
// AsHTTP2 returns a deep copy of Metadata with keys converted to lowercase.
|
||||
func (md Metadata) AsHTTP2() map[string][]string {
|
||||
out := make(map[string][]string, len(md))
|
||||
for k, v := range md {
|
||||
@@ -98,14 +111,10 @@ func (md Metadata) AsHTTP2() map[string][]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// CopyTo copies Metadata to out.
|
||||
func (md Metadata) CopyTo(out Metadata) {
|
||||
for k, v := range md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Get obtains the values for a given key.
|
||||
// Get retrieves the values for a given key, checking the key in three formats:
|
||||
// - exact case,
|
||||
// - lower case,
|
||||
// - canonical MIME header key format.
|
||||
func (md Metadata) Get(k string) []string {
|
||||
v, ok := md[k]
|
||||
if !ok {
|
||||
@@ -117,13 +126,12 @@ func (md Metadata) Get(k string) []string {
|
||||
return v
|
||||
}
|
||||
|
||||
// GetJoined obtains the values for a given key
|
||||
// with joined values with "," symbol
|
||||
// GetJoined retrieves the values for a given key and joins them into a single string, separated by commas.
|
||||
func (md Metadata) GetJoined(k string) string {
|
||||
return strings.Join(md.Get(k), ",")
|
||||
}
|
||||
|
||||
// Set sets the value of a given key with a slice of values.
|
||||
// Set assigns the values to the given key.
|
||||
func (md Metadata) Set(key string, vals ...string) {
|
||||
if len(vals) == 0 {
|
||||
return
|
||||
@@ -131,8 +139,7 @@ func (md Metadata) Set(key string, vals ...string) {
|
||||
md[key] = vals
|
||||
}
|
||||
|
||||
// Append adds the values to key k, not overwriting what was already stored at
|
||||
// that key.
|
||||
// Append adds values to the existing values for the given key.
|
||||
func (md Metadata) Append(key string, vals ...string) {
|
||||
if len(vals) == 0 {
|
||||
return
|
||||
@@ -140,7 +147,10 @@ func (md Metadata) Append(key string, vals ...string) {
|
||||
md[key] = append(md[key], vals...)
|
||||
}
|
||||
|
||||
// Del removes the values for a given keys k.
|
||||
// Del removes the values for the given keys k. It checks and removes the keys in the following formats:
|
||||
// - exact case,
|
||||
// - lower case,
|
||||
// - canonical MIME header key format.
|
||||
func (md Metadata) Del(k ...string) {
|
||||
for i := range k {
|
||||
delete(md, k[i])
|
||||
@@ -148,303 +158,3 @@ func (md Metadata) Del(k ...string) {
|
||||
delete(md, textproto.CanonicalMIMEHeaderKey(k[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// Join joins any number of Metadatas into a single Metadata.
|
||||
//
|
||||
// The order of values for each key is determined by the order in which the Metadatas
|
||||
// containing those values are presented to Join.
|
||||
func Join(mds ...Metadata) Metadata {
|
||||
out := Metadata{}
|
||||
for _, Metadata := range mds {
|
||||
for k, v := range Metadata {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type (
|
||||
metadataIncomingKey struct{}
|
||||
metadataOutgoingKey struct{}
|
||||
metadataCurrentKey struct{}
|
||||
)
|
||||
|
||||
// NewContext creates a new context with Metadata attached. Metadata must
|
||||
// not be modified after calling this function.
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewIncomingContext creates a new context with incoming Metadata attached. Metadata must
|
||||
// not be modified after calling this function.
|
||||
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewOutgoingContext creates a new context with outgoing Metadata attached. If used
|
||||
// in conjunction with AppendOutgoingContext, NewOutgoingContext will
|
||||
// overwrite any previously-appended metadata. Metadata must not be modified after
|
||||
// calling this function.
|
||||
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// AppendContext returns a new context with the provided kv merged
|
||||
// with any existing metadata in the context. Please refer to the documentation
|
||||
// of Pairs for a description of kv.
|
||||
func AppendContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// AppendOutgoingContext returns a new context with the provided kv merged
|
||||
// with any existing metadata in the context. Please refer to the documentation
|
||||
// of Pairs for a description of kv.
|
||||
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// FromContext returns the metadata in ctx if it exists.
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustContext returns the metadata in ctx.
|
||||
func MustContext(ctx context.Context) Metadata {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromIncomingContext returns the incoming metadata in ctx if it exists.
|
||||
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromIncomingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustIncomingContext returns the incoming metadata in ctx.
|
||||
func MustIncomingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// ValueFromIncomingContext returns the metadata value corresponding to the metadata
|
||||
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
|
||||
// manner.
|
||||
func ValueFromIncomingContext(ctx context.Context, key string) []string {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := raw.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range raw.md {
|
||||
// Case insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueFromCurrentContext returns the metadata value corresponding to the metadata
|
||||
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
|
||||
// manner.
|
||||
func ValueFromCurrentContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustOutgoingContext returns the outgoing metadata in ctx.
|
||||
func MustOutgoingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// ValueFromOutgoingContext returns the metadata value corresponding to the metadata
|
||||
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
|
||||
// manner.
|
||||
func ValueFromOutgoingContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyOf(v []string) []string {
|
||||
vals := make([]string, len(v))
|
||||
copy(vals, v)
|
||||
return vals
|
||||
}
|
||||
|
||||
// FromOutgoingContext returns the outgoing metadata in ctx if it exists.
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
type rawMetadata struct {
|
||||
md Metadata
|
||||
added [][]string
|
||||
}
|
||||
|
||||
// Iterator used to iterate over metadata with order
|
||||
type Iterator struct {
|
||||
md Metadata
|
||||
keys []string
|
||||
cur int
|
||||
cnt int
|
||||
}
|
||||
|
||||
// Next advance iterator to next element
|
||||
func (iter *Iterator) Next(k *string, v *[]string) bool {
|
||||
if iter.cur+1 > iter.cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
if k != nil && v != nil {
|
||||
*k = iter.keys[iter.cur]
|
||||
vv := iter.md[*k]
|
||||
*v = make([]string, len(vv))
|
||||
copy(*v, vv)
|
||||
iter.cur++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Iterator returns the itarator for metadata in sorted order
|
||||
func (md Metadata) Iterator() *Iterator {
|
||||
iter := &Iterator{md: md, cnt: len(md)}
|
||||
iter.keys = make([]string, 0, iter.cnt)
|
||||
for k := range md {
|
||||
iter.keys = append(iter.keys, k)
|
||||
}
|
||||
sort.Strings(iter.keys)
|
||||
return iter
|
||||
}
|
||||
|
Reference in New Issue
Block a user