Skip to content
61 changes: 61 additions & 0 deletions experimental/optional/optional.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
*
* Copyright 2026 gRPC 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

// Package optional adds generic optional types.
//
// All APIs in this package are experimental.
package optional

// Option represents an optional value of type T.
type Option[T any] struct {
val T
isSet bool
}

// New creates a new Option that does not have a value set. This can also be
// done implicitly using a zero-value declaration: `var opt optional.Option[T]“
func New[T any]() Option[T] {
return Option[T]{}
}

// NewValue creates a new Option with the provided value.
func NewValue[T any](value T) Option[T] {
return Option[T]{
val: value,
isSet: true,
}
}

// Value returns the underlying value and a boolean indicating if the value is
// set. If the value is not set, it returns the zero value of T and false.
func (o Option[T]) Value() (T, bool) {
return o.val, o.isSet
}

// WithValue returns a new Option containing the provided value.
func (o Option[T]) WithValue(value T) Option[T] {
return Option[T]{
val: value,
isSet: true,
}
}

// Clear returns an empty Option.
func (o Option[T]) Clear() Option[T] {
return Option[T]{}
}
161 changes: 161 additions & 0 deletions experimental/optional/optional_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
*
* Copyright 2026 gRPC 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package optional_test

import (
"testing"

"google.golang.org/grpc/experimental/optional"
"google.golang.org/grpc/internal/grpctest"
)

type s struct {
grpctest.Tester
}

func Test(t *testing.T) {
grpctest.RunSubTests(t, s{})
}

type testStruct struct {
Name string
Age int
}

// TestOption_Int tests the scenario of using integer optional values and
// verifies that default value, constructors, and mutation methods work as
// expected for primitive integers.
func (s) TestOption_Int(t *testing.T) {
var opt optional.Option[int]
// Test unset value.
if v, set := opt.Value(); set || v != 0 {
t.Fatalf("Zero-value Option[int] = (%v, %v); want (0, false)", v, set)
}

// Test that New() function also returns an unset optional value.
optNew := optional.New[int]()
if v, set := optNew.Value(); set || v != 0 {
t.Fatalf("New[int]() = (%v, %v); want (0, false)", v, set)
}

optVal := optional.NewValue(42)
if v, set := optVal.Value(); !set || v != 42 {
t.Fatalf("NewValue(42) = (%v, %v); want (42, true)", v, set)
}

opt = opt.WithValue(100)
if v, set := opt.Value(); !set || v != 100 {
t.Fatalf("WithValue(100) = (%v, %v); want (100, true)", v, set)
}

opt = opt.Clear()
if v, set := opt.Value(); set || v != 0 {
t.Fatalf("Clear() = (%v, %v); want (0, false)", v, set)
}
}

// TestOption_String tests the scenario of using string optional values and
// verifies that default value, constructors, and mutation methods work as
// expected for text strings.
func (s) TestOption_String(t *testing.T) {
var opt optional.Option[string]
// Test unset value.
if v, set := opt.Value(); set || v != "" {
t.Fatalf("Zero-value Option[string] = (%q, %v); want (%q, false)", v, set, "")
}

// Test that New() function also returns an unset optional value.
optNew := optional.New[string]()
if v, set := optNew.Value(); set || v != "" {
t.Fatalf("New Option[string] = (%q, %v); want (%q, false)", v, set, "")
}

wantString := "test-string"
optVal := optional.NewValue(wantString)
if v, set := optVal.Value(); !set || v != wantString {
t.Fatalf("NewValue(%q) = (%q, %v); want (%q, true)", wantString, v, set, wantString)
}

wantStringNew := "world"
opt = opt.WithValue(wantStringNew)
if v, set := opt.Value(); !set || v != wantStringNew {
t.Fatalf("WithValue(%q) = (%q, %v); want (%q, true)", wantStringNew, v, set, wantStringNew)
}

opt = opt.Clear()
if v, set := opt.Value(); set || v != "" {
t.Fatalf("Clear() = (%q, %v); want (%q, false)", v, set, "")
}
}

// TestOption_Struct tests the scenario of using a custom struct type inside an
// option type and verifies that custom struct field values are preserved,
// modified, and cleared correctly.
func (s) TestOption_Struct(t *testing.T) {
val1 := testStruct{Name: "Alice", Age: 30}
val2 := testStruct{Name: "Bob", Age: 40}

var opt optional.Option[testStruct]
if v, set := opt.Value(); set || v != (testStruct{}) {
t.Fatalf("Zero-value Option[struct] = (%v, %v); want (empty, false)", v, set)
}

optVal := optional.NewValue(val1)
if v, set := optVal.Value(); !set || v != val1 {
t.Fatalf("NewValue(val1) = (%v, %v); want (%v, true)", v, set, val1)
}

opt = opt.WithValue(val2)
if v, set := opt.Value(); !set || v != val2 {
t.Fatalf("WithValue(val2) = (%v, %v); want (%v, true)", v, set, val2)
}

opt = opt.Clear()
if v, set := opt.Value(); set || v != (testStruct{}) {
t.Fatalf("Clear() = (%v, %v); want (empty, false)", v, set)
}
}

// TestOption_Pointer tests the scenario of using a pointer type inside an
// option type and verifies that nil status, address preservation, and
// underlying value dereferencing work as expected.
func (s) TestOption_Pointer(t *testing.T) {
val1 := 42
val2 := 100

var opt optional.Option[*int]
if v, set := opt.Value(); set || v != nil {
t.Fatalf("Zero-value Option[*int] = (%v, %v); want (nil, false)", v, set)
}

optVal := optional.NewValue(&val1)
if v, set := optVal.Value(); !set || v != &val1 || *v != val1 {
t.Fatalf("NewValue(%v) = (%v, %v); want (%v, true)", &val1, v, set, &val1)
}

opt = opt.WithValue(&val2)
if v, set := opt.Value(); !set || v != &val2 || *v != val2 {
t.Fatalf("WithValue(%v) = (%v, %v); want (%v, true)", &val2, v, set, &val2)
}

opt = opt.Clear()
if v, set := opt.Value(); set || v != nil {
t.Fatalf("Clear() = (%v, %v); want (nil, false)", v, set)
}
}
116 changes: 116 additions & 0 deletions internal/xds/httpfilter/extconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
*
* Copyright 2021 gRPC 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

// Package httpfilter contains interface definitions for xDS-based HTTP filters
// and a registry for filter builders.
package httpfilter

import (
"encoding/json"
"fmt"
"regexp"
"time"

"google.golang.org/grpc/internal/xds/matcher"
"google.golang.org/grpc/metadata"

v3mutationpb "github.com/envoyproxy/go-control-plane/envoy/config/common/mutation_rules/v3"
v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
)

// HeaderMutationRules specifies the rules for what modifications an external
// processing server may make to headers sent on the data plane RPC.
type HeaderMutationRules struct {
// AllowExpr specifies a regular expression that matches the headers that can
// be mutated.
AllowExpr *regexp.Regexp
// DisallowExpr specifies a regular expression that matches the headers that
// cannot be mutated. This overrides the above allowExpr if a header matches
// both.
DisallowExpr *regexp.Regexp
// DisallowAll specifies that no header mutations are allowed. This overrides
// all other settings.
DisallowAll bool
// DisallowIsError specifies whether to return an error if a header mutation
// is disallowed. If true, the data plane RPC will be failed with a grpc
// status code of Unknown.
DisallowIsError bool
}

// ServerConfig contains the configuration for an external server.
type ServerConfig struct {
// TargetURI is the name of the external server.
TargetURI string
// ChannelCredentials specifies the transport credentials to use to connect to
// the external server. Must not be nil.
ChannelCredentials json.RawMessage
// CallCredentials specifies the per-RPC credentials to use when making calls
// to the external server.
CallCredentials []json.RawMessage
// Timeout is the RPC Timeout for the call to the external server. If unset,
// the Timeout depends on the usage of this external server. For example,
// cases like ext_authz and ext_proc, where there is a 1:1 mapping between the
// data plane RPC and the external server call, the Timeout will be capped by
// the Timeout on the data plane RPC. For cases like RLQS where there is a
// side channel to the external server, an unset Timeout will result in no
// Timeout being applied to the external server call.
Timeout time.Duration
// InitialMetadata is the additional metadata to include in all RPCs sent to
// the external server.
InitialMetadata metadata.MD
}

// ConvertStringMatchers converts a slice of protobuf StringMatcher messages to
// a slice of matcher.StringMatcher.
func ConvertStringMatchers(patterns []*v3matcherpb.StringMatcher) ([]matcher.StringMatcher, error) {
matchers := make([]matcher.StringMatcher, 0, len(patterns))
for _, p := range patterns {
sm, err := matcher.StringMatcherFromProto(p)
if err != nil {
return nil, err
}
matchers = append(matchers, sm)
}
return matchers, nil
}

// HeaderMutationRulesFromProto converts a protobuf HeaderMutationRules message
// to a headerMutationRules struct.
func HeaderMutationRulesFromProto(mr *v3mutationpb.HeaderMutationRules) (HeaderMutationRules, error) {
var rules HeaderMutationRules
if mr == nil {
return rules, nil
}
if allowExpr := mr.GetAllowExpression(); allowExpr != nil {
re, err := regexp.Compile(allowExpr.GetRegex())
if err != nil {
return rules, fmt.Errorf("extproc: %v", err)
}
rules.AllowExpr = re
}
if disallowExpr := mr.GetDisallowExpression(); disallowExpr != nil {
re, err := regexp.Compile(disallowExpr.GetRegex())
if err != nil {
return rules, fmt.Errorf("extproc: %v", err)
}
rules.DisallowExpr = re
}
rules.DisallowAll = mr.GetDisallowAll().GetValue()
rules.DisallowIsError = mr.GetDisallowIsError().GetValue()
return rules, nil
}
Loading
Loading