func (rb *ResourceBatch) Add(resource *Resource, actions ...string) *ResourceBatch {
	if resource == nil || len(actions) == 0 {
		return rb
	}
	entry := &requestv1.CheckResourceBatchRequest_BatchEntry{
		Actions:  actions,
		Resource: resource.r,
	}
	if err := entry.Validate(); err != nil {
		rb.err = multierr.Append(rb.err, fmt.Errorf("invalid resource '%s': %w", resource.r.Id, err))
		return rb
	}
	rb.batch = append(rb.batch, entry)
	return rb
}
Add a new resource to the batch.
package client
import (
	"errors"
	"fmt"
	"io"
	"os"
	"reflect"
	"sync"
	"time"
	"go.uber.org/multierr"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/types/known/structpb"
	auditv1 "github.com/cerbos/cerbos/api/genpb/cerbos/audit/v1"
	effectv1 "github.com/cerbos/cerbos/api/genpb/cerbos/effect/v1"
	enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
	policyv1 "github.com/cerbos/cerbos/api/genpb/cerbos/policy/v1"
	requestv1 "github.com/cerbos/cerbos/api/genpb/cerbos/request/v1"
	responsev1 "github.com/cerbos/cerbos/api/genpb/cerbos/response/v1"
	"github.com/cerbos/cerbos/internal/policy"
)
const apiVersion = "api.cerbos.dev/v1"
type Principal struct {
	p   *enginev1.Principal
	err error
}
func NewPrincipal(id string, roles ...string) *Principal {
	return &Principal{
		p: &enginev1.Principal{
			Id:    id,
			Roles: roles,
		},
	}
}
func (p *Principal) WithPolicyVersion(policyVersion string) *Principal {
	p.p.PolicyVersion = policyVersion
	return p
}
func (p *Principal) WithRoles(roles ...string) *Principal {
	p.p.Roles = append(p.p.Roles, roles...)
	return p
}
func (p *Principal) WithAttributes(attr map[string]interface{}) *Principal {
	if p.p.Attr == nil {
		p.p.Attr = make(map[string]*structpb.Value, len(attr))
	}
	for k, v := range attr {
		pbVal, err := toStructPB(v)
		if err != nil {
			p.err = multierr.Append(p.err, fmt.Errorf("invalid attribute value for '%s': %w", k, err))
			continue
		}
		p.p.Attr[k] = pbVal
	}
	return p
}
func (p *Principal) WithAttr(key string, value interface{}) *Principal {
	if p.p.Attr == nil {
		p.p.Attr = make(map[string]*structpb.Value)
	}
	pbVal, err := toStructPB(value)
	if err != nil {
		p.err = multierr.Append(p.err, fmt.Errorf("invalid attribute value for '%s': %w", key, err))
		return p
	}
	p.p.Attr[key] = pbVal
	return p
}
func (p *Principal) Err() error {
	return p.err
}
func (p *Principal) Validate() error {
	if p.err != nil {
		return p.err
	}
	return p.p.Validate()
}
type Resource struct {
	r   *enginev1.Resource
	err error
}
func NewResource(kind, id string) *Resource {
	return &Resource{
		r: &enginev1.Resource{Kind: kind, Id: id},
	}
}
func (r *Resource) WithPolicyVersion(policyVersion string) *Resource {
	r.r.PolicyVersion = policyVersion
	return r
}
func (r *Resource) WithAttributes(attr map[string]interface{}) *Resource {
	if r.r.Attr == nil {
		r.r.Attr = make(map[string]*structpb.Value, len(attr))
	}
	for k, v := range attr {
		pbVal, err := toStructPB(v)
		if err != nil {
			r.err = multierr.Append(r.err, fmt.Errorf("invalid attribute value for '%s': %w", k, err))
			continue
		}
		r.r.Attr[k] = pbVal
	}
	return r
}
func (r *Resource) WithAttr(key string, value interface{}) *Resource {
	if r.r.Attr == nil {
		r.r.Attr = make(map[string]*structpb.Value)
	}
	pbVal, err := toStructPB(value)
	if err != nil {
		r.err = multierr.Append(r.err, fmt.Errorf("invalid attribute value for '%s': %w", key, err))
		return r
	}
	r.r.Attr[key] = pbVal
	return r
}
func (r *Resource) Err() error {
	return r.err
}
func (r *Resource) Validate() error {
	if r.err != nil {
		return r.err
	}
	return r.r.Validate()
}
type ResourceSet struct {
	rs  *requestv1.ResourceSet
	err error
}
func NewResourceSet(kind string) *ResourceSet {
	return &ResourceSet{
		rs: &requestv1.ResourceSet{Kind: kind},
	}
}
func (rs *ResourceSet) WithPolicyVersion(policyVersion string) *ResourceSet {
	rs.rs.PolicyVersion = policyVersion
	return rs
}
func (rs *ResourceSet) AddResourceInstance(id string, attr map[string]interface{}) *ResourceSet {
	if rs.rs.Instances == nil {
		rs.rs.Instances = make(map[string]*requestv1.AttributesMap)
	}
	pbAttr := make(map[string]*structpb.Value, len(attr))
	for k, v := range attr {
		pbVal, err := structpb.NewValue(v)
		if err != nil {
			rs.err = multierr.Append(rs.err, fmt.Errorf("invalid attribute value for '%s': %w", k, err))
			continue
		}
		pbAttr[k] = pbVal
	}
	rs.rs.Instances[id] = &requestv1.AttributesMap{Attr: pbAttr}
	return rs
}
func (rs *ResourceSet) Err() error {
	return rs.err
}
func (rs *ResourceSet) Validate() error {
	if rs.err != nil {
		return rs.err
	}
	return rs.rs.Validate()
}
type CheckResourceSetResponse struct {
	*responsev1.CheckResourceSetResponse
}
func (crsr *CheckResourceSetResponse) IsAllowed(resourceID, action string) bool {
	res, ok := crsr.ResourceInstances[resourceID]
	if !ok {
		return false
	}
	effect, ok := res.Actions[action]
	if !ok {
		return false
	}
	return effect == effectv1.Effect_EFFECT_ALLOW
}
func (crsr *CheckResourceSetResponse) String() string {
	return protojson.Format(crsr.CheckResourceSetResponse)
}
type ResourceBatch struct {
	batch []*requestv1.CheckResourceBatchRequest_BatchEntry
	err   error
}
func NewResourceBatch() *ResourceBatch {
	return &ResourceBatch{}
}