func (pr *PrincipalRule) DenyActionOnCondition(action string, m match) *PrincipalRule {
	cond := &policyv1.Condition{Condition: &policyv1.Condition_Match{Match: m.build()}}
	return pr.addAction(action, effectv1.Effect_EFFECT_DENY, cond)
}
DenyActionOnCondition sets the action as denied if the condition is fulfilled.
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{}
}
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
}
func (rb *ResourceBatch) Err() error {
	return rb.err
}
func (rb *ResourceBatch) Validate() error {
	if rb.err != nil {
		return rb.err
	}
	if len(rb.batch) == 0 {
		return errors.New("empty batch")
	}
	var errList error
	for _, entry := range rb.batch {
		if err := entry.Validate(); err != nil {
			errList = multierr.Append(errList, err)
		}
	}
	return errList
}
type CheckResourceBatchResponse struct {
	*responsev1.CheckResourceBatchResponse
	once sync.Once
	idx  map[string][]int
}
func (crbr *CheckResourceBatchResponse) buildIdx() {
	crbr.once.Do(func() {
		crbr.idx = make(map[string][]int, len(crbr.Results))
		for i, r := range crbr.Results {
			v := crbr.idx[r.ResourceId]
			crbr.idx[r.ResourceId] = append(v, i)
		}
	})
}
func (crbr *CheckResourceBatchResponse) IsAllowed(resourceID, action string) bool {
	crbr.buildIdx()
	indexes, ok := crbr.idx[resourceID]
	if !ok {
		return false
	}
	for _, i := range indexes {
		r := crbr.Results[i]
		if r == nil {
			continue
		}
		if effect, ok := r.Actions[action]; ok {
			return effect == effectv1.Effect_EFFECT_ALLOW
		}
	}
	return false
}
func (crbr *CheckResourceBatchResponse) String() string {
	return protojson.Format(crbr.CheckResourceBatchResponse)
}
func toStructPB(v interface{}) (*structpb.Value, error) {
	val, err := structpb.NewValue(v)
	if err == nil {
		return val, nil
	}
	vv := reflect.ValueOf(v)
	switch vv.Kind() {
	case reflect.Array, reflect.Slice:
		arr := make([]interface{}, vv.Len())
		for i := 0; i < vv.Len(); i++ {
			el := vv.Index(i)
			arr[i] = el.Interface()
		}
		return structpb.NewValue(arr)
	case reflect.Map:
		if vv.Type().Key().Kind() == reflect.String {
			m := make(map[string]interface{})
			iter := vv.MapRange()
			for iter.Next() {
				m[iter.Key().String()] = iter.Value().Interface()
			}
			return structpb.NewValue(m)
		}
	default:
		return nil, err
	}
	return nil, err
}
type PolicySet struct {
	policies []*policyv1.Policy
	err      error
}
func NewPolicySet() *PolicySet {
	return &PolicySet{}
}
func (ps *PolicySet) AddPolicyFromFile(file string) *PolicySet {
	f, err := os.Open(file)
	if err != nil {
		ps.err = multierr.Append(ps.err, fmt.Errorf("failed to add policy from file '%s': %w", file, err))
		return ps
	}
	defer f.Close()
	return ps.AddPolicyFromReader(f)
}
func (ps *PolicySet) AddPolicyFromReader(r io.Reader) *PolicySet {
	p, err := policy.ReadPolicy(r)
	if err != nil {
		ps.err = multierr.Append(ps.err, fmt.Errorf("failed to add policy from reader: %w", err))
		return ps
	}
	ps.policies = append(ps.policies, p)
	return nil
}
func (ps *PolicySet) AddResourcePolicies(policies ...*ResourcePolicy) *PolicySet {
	for _, p := range policies {
		if p == nil {
			continue
		}
		if err := ps.add(p); err != nil {
			ps.err = multierr.Append(ps.err, fmt.Errorf("failed to add resource policy [%s:%s]: %w", p.p.Resource, p.p.Version, err))
		}
	}
	return ps
}
func (ps *PolicySet) AddPrincipalPolicies(policies ...*PrincipalPolicy) *PolicySet {
	for _, p := range policies {
		if p == nil {
			continue
		}
		if err := ps.add(p); err != nil {
			ps.err = multierr.Append(ps.err, fmt.Errorf("failed to add principal policy [%s:%s]: %w", p.pp.Principal, p.pp.Version, err))
		}
	}
	return ps
}
func (ps *PolicySet) AddDerivedRoles(policies ...*DerivedRoles) *PolicySet {
	for _, p := range policies {
		if p == nil {
			continue
		}
		if err := ps.add(p); err != nil {
			ps.err = multierr.Append(ps.err, fmt.Errorf("failed to add derived roles [%s]: %w", p.dr.Name, err))
		}
	}
	return ps
}
func (ps *PolicySet) add(b interface {
	build() (*policyv1.Policy, error)
}) error {
	p, err := b.build()
	if err != nil {
		return err
	}
	ps.policies = append(ps.policies, p)
	return nil
}
func (ps *PolicySet) Err() error {
	return ps.err
}
func (ps *PolicySet) Validate() error {
	if ps.err != nil {
		return ps.err
	}
	if len(ps.policies) == 0 {
		return errors.New("empty policy set")
	}
	return nil
}
type ResourcePolicy struct {
	p   *policyv1.ResourcePolicy
	err error
}
func NewResourcePolicy(resource, version string) *ResourcePolicy {
	return &ResourcePolicy{
		p: &policyv1.ResourcePolicy{
			Resource: resource,
			Version:  version,
		},
	}
}
func (rp *ResourcePolicy) WithDerivedRolesImports(imp ...string) *ResourcePolicy {
	rp.p.ImportDerivedRoles = append(rp.p.ImportDerivedRoles, imp...)
	return rp
}
func (rp *ResourcePolicy) AddResourceRules(rules ...*ResourceRule) *ResourcePolicy {
	for _, r := range rules {
		if r == nil {
			continue
		}
		if err := r.Validate(); err != nil {
			rp.err = multierr.Append(rp.err, fmt.Errorf("invalid rule: %w", err))
			continue
		}
		rp.p.Rules = append(rp.p.Rules, r.rule)
	}
	return rp
}
func (rp *ResourcePolicy) Err() error {
	return rp.err
}
func (rp *ResourcePolicy) Validate() error {
	if rp.err != nil {
		return rp.err
	}
	_, err := rp.build()
	return err
}
func (rp *ResourcePolicy) build() (*policyv1.Policy, error) {
	if err := rp.Validate(); err != nil {
		return nil, err
	}
	return &policyv1.Policy{
		ApiVersion: apiVersion,
		PolicyType: &policyv1.Policy_ResourcePolicy{
			ResourcePolicy: rp.p,
		},
	}, nil
}
type ResourceRule struct {
	rule *policyv1.ResourceRule
}
func NewAllowResourceRule(actions ...string) *ResourceRule {
	return &ResourceRule{
		rule: &policyv1.ResourceRule{
			Actions: actions,
			Effect:  effectv1.Effect_EFFECT_ALLOW,
		},
	}
}
func NewDenyResourceRule(actions ...string) *ResourceRule {
	return &ResourceRule{
		rule: &policyv1.ResourceRule{
			Actions: actions,
			Effect:  effectv1.Effect_EFFECT_DENY,
		},
	}
}
func (rr *ResourceRule) WithRoles(roles ...string) *ResourceRule {
	rr.rule.Roles = append(rr.rule.Roles, roles...)
	return rr
}
func (rr *ResourceRule) WithDerivedRoles(roles ...string) *ResourceRule {
	rr.rule.DerivedRoles = append(rr.rule.DerivedRoles, roles...)
	return rr
}
func (rr *ResourceRule) WithCondition(m match) *ResourceRule {
	rr.rule.Condition = &policyv1.Condition{
		Condition: &policyv1.Condition_Match{
			Match: m.build(),
		},
	}
	return rr
}
func (rr *ResourceRule) Err() error {
	return nil
}
func (rr *ResourceRule) Validate() error {
	return rr.rule.Validate()
}
type PrincipalPolicy struct {
	pp  *policyv1.PrincipalPolicy
	err error
}
func NewPrincipalPolicy(principal, version string) *PrincipalPolicy {
	return &PrincipalPolicy{
		pp: &policyv1.PrincipalPolicy{
			Principal: principal,
			Version:   version,
		},
	}
}
func (pp *PrincipalPolicy) AddPrincipalRules(rules ...*PrincipalRule) *PrincipalPolicy {
	for _, r := range rules {
		if r == nil {
			continue
		}
		if err := r.Validate(); err != nil {
			pp.err = multierr.Append(pp.err, fmt.Errorf("invalid rule: %w", err))
			continue
		}
		pp.pp.Rules = append(pp.pp.Rules, r.rule)
	}
	return pp
}
func (pp *PrincipalPolicy) Err() error {
	return pp.err
}
func (pp *PrincipalPolicy) Validate() error {
	if pp.err != nil {
		return pp.err
	}
	_, err := pp.build()
	return err
}
func (pp *PrincipalPolicy) build() (*policyv1.Policy, error) {
	p := &policyv1.Policy{
		ApiVersion: apiVersion,
		PolicyType: &policyv1.Policy_PrincipalPolicy{
			PrincipalPolicy: pp.pp,
		},
	}
	return p, policy.Validate(p)
}
type PrincipalRule struct {
	rule *policyv1.PrincipalRule
}
func NewPrincipalRule(resource string) *PrincipalRule {
	return &PrincipalRule{
		rule: &policyv1.PrincipalRule{
			Resource: resource,
		},
	}
}
func (pr *PrincipalRule) AllowAction(action string) *PrincipalRule {
	return pr.addAction(action, effectv1.Effect_EFFECT_ALLOW, nil)
}
func (pr *PrincipalRule) DenyAction(action string) *PrincipalRule {
	return pr.addAction(action, effectv1.Effect_EFFECT_DENY, nil)
}
func (pr *PrincipalRule) AllowActionOnCondition(action string, m match) *PrincipalRule {
	cond := &policyv1.Condition{Condition: &policyv1.Condition_Match{Match: m.build()}}
	return pr.addAction(action, effectv1.Effect_EFFECT_ALLOW, cond)
}