github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/validationfile/blocks/assertions.go (about)

     1  package blocks
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
     9  	yamlv3 "gopkg.in/yaml.v3"
    10  
    11  	"github.com/authzed/spicedb/pkg/spiceerrors"
    12  	"github.com/authzed/spicedb/pkg/tuple"
    13  )
    14  
    15  // Assertions represents assertions defined in the validation file.
    16  type Assertions struct {
    17  	// AssertTrue is the set of relationships to assert true.
    18  	AssertTrue []Assertion `yaml:"assertTrue"`
    19  
    20  	// AssertCaveated is the set of relationships to assert that are caveated.
    21  	AssertCaveated []Assertion `yaml:"assertCaveated"`
    22  
    23  	// AssertFalse is the set of relationships to assert false.
    24  	AssertFalse []Assertion `yaml:"assertFalse"`
    25  
    26  	// SourcePosition is the position of the assertions in the file.
    27  	SourcePosition spiceerrors.SourcePosition
    28  }
    29  
    30  // Assertion is a parsed assertion.
    31  type Assertion struct {
    32  	// RelationshipWithContextString is the string form of the assertion, including optional context.
    33  	// Forms:
    34  	// `document:firstdoc#view@user:tom`
    35  	// `document:seconddoc#view@user:sarah with {"some":"contexthere"}`
    36  	RelationshipWithContextString string
    37  
    38  	// Relationship is the parsed relationship on which the assertion is being
    39  	// run.
    40  	Relationship *v1.Relationship
    41  
    42  	// CaveatContext is the caveat context for the assertion, if any.
    43  	CaveatContext map[string]any
    44  
    45  	// SourcePosition is the position of the assertion in the file.
    46  	SourcePosition spiceerrors.SourcePosition
    47  }
    48  
    49  type internalAssertions struct {
    50  	// AssertTrue is the set of relationships to assert true.
    51  	AssertTrue []Assertion `yaml:"assertTrue"`
    52  
    53  	// AssertCaveated is the set of relationships to assert that are caveated.
    54  	AssertCaveated []Assertion `yaml:"assertCaveated"`
    55  
    56  	// AssertFalse is the set of relationships to assert false.
    57  	AssertFalse []Assertion `yaml:"assertFalse"`
    58  }
    59  
    60  // UnmarshalYAML is a custom unmarshaller.
    61  func (a *Assertions) UnmarshalYAML(node *yamlv3.Node) error {
    62  	ia := internalAssertions{}
    63  	if err := node.Decode(&ia); err != nil {
    64  		return convertYamlError(err)
    65  	}
    66  
    67  	a.AssertTrue = ia.AssertTrue
    68  	a.AssertFalse = ia.AssertFalse
    69  	a.AssertCaveated = ia.AssertCaveated
    70  	a.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
    71  	return nil
    72  }
    73  
    74  // UnmarshalYAML is a custom unmarshaller.
    75  func (a *Assertion) UnmarshalYAML(node *yamlv3.Node) error {
    76  	relationshipWithContextString := ""
    77  
    78  	if err := node.Decode(&relationshipWithContextString); err != nil {
    79  		return convertYamlError(err)
    80  	}
    81  
    82  	trimmed := strings.TrimSpace(relationshipWithContextString)
    83  
    84  	// Check for caveat context.
    85  	parts := strings.SplitN(trimmed, " with ", 2)
    86  	if len(parts) == 0 {
    87  		return spiceerrors.NewErrorWithSource(
    88  			fmt.Errorf("error parsing assertion `%s`", trimmed),
    89  			trimmed,
    90  			uint64(node.Line),
    91  			uint64(node.Column),
    92  		)
    93  	}
    94  
    95  	tpl := tuple.Parse(strings.TrimSpace(parts[0]))
    96  	if tpl == nil {
    97  		return spiceerrors.NewErrorWithSource(
    98  			fmt.Errorf("error parsing relationship in assertion `%s`", trimmed),
    99  			trimmed,
   100  			uint64(node.Line),
   101  			uint64(node.Column),
   102  		)
   103  	}
   104  
   105  	a.Relationship = tuple.MustToRelationship(tpl)
   106  
   107  	if len(parts) == 2 {
   108  		caveatContextMap := make(map[string]any, 0)
   109  		err := json.Unmarshal([]byte(parts[1]), &caveatContextMap)
   110  		if err != nil {
   111  			return spiceerrors.NewErrorWithSource(
   112  				fmt.Errorf("error parsing caveat context in assertion `%s`: %w", trimmed, err),
   113  				trimmed,
   114  				uint64(node.Line),
   115  				uint64(node.Column),
   116  			)
   117  		}
   118  
   119  		a.CaveatContext = caveatContextMap
   120  	}
   121  
   122  	a.RelationshipWithContextString = relationshipWithContextString
   123  	a.SourcePosition = spiceerrors.SourcePosition{LineNumber: node.Line, ColumnPosition: node.Column}
   124  	return nil
   125  }
   126  
   127  // ParseAssertionsBlock parses the given contents as an assertions block.
   128  func ParseAssertionsBlock(contents []byte) (*Assertions, error) {
   129  	a := internalAssertions{}
   130  	if err := yamlv3.Unmarshal(contents, &a); err != nil {
   131  		return nil, convertYamlError(err)
   132  	}
   133  	return &Assertions{
   134  		AssertTrue:     a.AssertTrue,
   135  		AssertCaveated: a.AssertCaveated,
   136  		AssertFalse:    a.AssertFalse,
   137  	}, nil
   138  }