Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Comment thread
easwars marked this conversation as resolved.
Outdated
//
// All APIs in this package are experimental.
package optional

// Option represents an optional value of type T.
Comment thread
easwars marked this conversation as resolved.
Outdated
type Option[T any] struct {
Comment thread
easwars marked this conversation as resolved.
Outdated
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
}
Comment thread
easwars marked this conversation as resolved.
Outdated

// 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) {
Comment thread
easwars marked this conversation as resolved.
Outdated
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)
}
}
1 change: 1 addition & 0 deletions internal/envconfig/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ var (
// the client side. For more details, see:
// https://github.com/grpc/proposal/blob/master/A93-xds-ext-proc.md
XDSClientExtProcEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_EXT_PROC_ON_CLIENT", false)

// GCPAuthenticationFilterEnabled enables the xDS GCP Authentication
// filter. For more details, see:
// https://github.com/grpc/proposal/blob/master/A83-xds-gcp-authn-filter.md
Expand Down
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.
Comment thread
easwars marked this conversation as resolved.
Outdated
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 {
Comment thread
easwars marked this conversation as resolved.
Outdated
// 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
Comment thread
easwars marked this conversation as resolved.
Outdated
// 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.
Comment thread
easwars marked this conversation as resolved.
Outdated
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("httpfilter: %v", err)
}
rules.AllowExpr = re
}
if disallowExpr := mr.GetDisallowExpression(); disallowExpr != nil {
re, err := regexp.Compile(disallowExpr.GetRegex())
if err != nil {
return rules, fmt.Errorf("httpfilter: %v", err)
}
rules.DisallowExpr = re
}
rules.DisallowAll = mr.GetDisallowAll().GetValue()
rules.DisallowIsError = mr.GetDisallowIsError().GetValue()
Comment thread
eshitachandwani marked this conversation as resolved.
return rules, nil
}
Loading
Loading