func valueToJSON(val ref.Val) string {
	v, err := val.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
	if err != nil {
		glog.Exit(err)
	}
	marshaller := protojson.MarshalOptions{Indent: "    "}
	bytes, err := marshaller.Marshal(v.(proto.Message))
	if err != nil {
		glog.Exit(err)
	}
	return string(bytes)
}
valueToJSON converts the CEL type to a protobuf JSON representation and
marshals the result to a string.
package main
import (
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"time"
	"github.com/google/cel-go/cel"
	_ "github.com/google/cel-go/checker/decls"
	"github.com/google/cel-go/common/types"
	"github.com/google/cel-go/common/types/ref"
	"github.com/google/cel-go/common/types/traits"
	_ "github.com/google/cel-go/interpreter/functions"
	"github.com/golang/glog"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/encoding/prototext"
	"google.golang.org/protobuf/proto"
	exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
	rpcpb "google.golang.org/genproto/googleapis/rpc/context/attribute_context"
	structpb "google.golang.org/protobuf/types/known/structpb"
	tpb "google.golang.org/protobuf/types/known/timestamppb"
)
func main() {
	exercise1()
	exercise2()
	exercise3()
	exercise4()
	exercise5()
	exercise6()
	exercise7()
	exercise8()
}
func exercise1() {
	fmt.Println("=== Exercise 1: Hello World ===\n")
	fmt.Println()
}
func exercise2() {
	fmt.Println("=== Exercise 2: Variables ===\n")
	fmt.Println()
}
func exercise3() {
	fmt.Println("=== Exercise 3: Logical AND/OR ===\n")
	fmt.Println()
}
func exercise4() {
	fmt.Println("=== Exercise 4: Customization ===\n")
	fmt.Println()
}
func exercise5() {
	fmt.Println("=== Exercise 5: Building JSON ===\n")
	fmt.Println()
}
func exercise6() {
	fmt.Println("=== Exercise 6: Building Protos ===\n")
	fmt.Println()
}
func exercise7() {
	fmt.Println("=== Exercise 7: Macros ===\n")
	fmt.Println()
}
func exercise8() {
	fmt.Println("=== Exercise 8: Tuning ===\n")
	fmt.Println()
}
func compile(env *cel.Env, expr string, exprType *exprpb.Type) *cel.Ast {
	ast, iss := env.Compile(expr)
	if iss.Err() != nil {
		glog.Exit(iss.Err())
	}
	if !proto.Equal(ast.ResultType(), exprType) {
		glog.Exitf(
			"Got %v, wanted %v result type", ast.ResultType(), exprType)
	}
	fmt.Printf("%s\n\n", strings.ReplaceAll(expr, "\t", " "))
	return ast
}
func eval(prg cel.Program,
	vars interface{}) (out ref.Val, det *cel.EvalDetails, err error) {
	varMap, isMap := vars.(map[string]interface{})
	fmt.Println("------ input ------")
	if !isMap {
		fmt.Printf("(%T)\n", vars)
	} else {
		for k, v := range varMap {
			switch val := v.(type) {
			case proto.Message:
				bytes, err := prototext.Marshal(val)
				if err != nil {
					glog.Exitf("failed to marshal proto to text: %v", val)
				}
				fmt.Printf("%s = %s", k, string(bytes))
			case map[string]interface{}:
				b, _ := json.MarshalIndent(v, "", "  ")
				fmt.Printf("%s = %v\n", k, string(b))
			case uint64:
				fmt.Printf("%s = %vu\n", k, v)
			default:
				fmt.Printf("%s = %v\n", k, v)
			}
		}
	}
	fmt.Println()
	out, det, err = prg.Eval(vars)
	report(out, det, err)
	fmt.Println()
	return
}
func report(result ref.Val, details *cel.EvalDetails, err error) {
	fmt.Println("------ result ------")
	if err != nil {
		fmt.Printf("error: %s\n", err)
	} else {
		fmt.Printf("value: %v (%T)\n", result, result)
	}
	if details != nil {
		fmt.Printf("\n------ eval states ------\n")
		state := details.State()
		stateIDs := state.IDs()
		ids := make([]int, len(stateIDs), len(stateIDs))
		for i, id := range stateIDs {
			ids[i] = int(id)
		}
		sort.Ints(ids)
		for _, id := range ids {
			v, found := state.Value(int64(id))
			if !found {
				continue
			}
			fmt.Printf("%d: %v (%T)\n", id, v, v)
		}
	}
}
func mapContainsKeyValue(args ...ref.Val) ref.Val {
	if len(args) != 3 {
		return types.NewErr("no such overload")
	}
	obj := args[0]
	m, isMap := obj.(traits.Mapper)
	if !isMap {
		return types.ValOrErr(obj, "no such overload")
	}
	key := args[1]
	v, found := m.Find(key)
	if !found {
		if v != nil {
			return types.ValOrErr(v, "unsupported key type")
		}
		return types.False
	}
	return v.Equal(args[2])
}
func auth(user string, claims map[string]string) *rpcpb.AttributeContext_Auth {
	claimFields := make(map[string]*structpb.Value)
	for k, v := range claims {
		claimFields[k] = structpb.NewStringValue(v)
	}
	return &rpcpb.AttributeContext_Auth{
		Principal: user,
		Claims:    &structpb.Struct{Fields: claimFields},
	}
}
func request(auth *rpcpb.AttributeContext_Auth, t time.Time) map[string]interface{} {
	req := &rpcpb.AttributeContext_Request{
		Auth: auth,
		Time: &tpb.Timestamp{Seconds: t.Unix()},
	}
	return map[string]interface{}{"request": req}
}