github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/schemadsl/compiler/compiler_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"os"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	"google.golang.org/protobuf/reflect/protoreflect"
     9  
    10  	"github.com/authzed/spicedb/pkg/caveats"
    11  	caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
    12  	"github.com/authzed/spicedb/pkg/namespace"
    13  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    14  	"github.com/authzed/spicedb/pkg/schemadsl/input"
    15  	"github.com/authzed/spicedb/pkg/testutil"
    16  )
    17  
    18  var (
    19  	withTenantPrefix = ObjectTypePrefix("sometenant")
    20  	nilPrefix        = func(cfg *config) { cfg.objectTypePrefix = nil }
    21  )
    22  
    23  func TestCompile(t *testing.T) {
    24  	type compileTest struct {
    25  		name          string
    26  		objectPrefix  ObjectPrefixOption
    27  		input         string
    28  		expectedError string
    29  		expectedProto []SchemaDefinition
    30  	}
    31  
    32  	tests := []compileTest{
    33  		{
    34  			"empty",
    35  			withTenantPrefix,
    36  			"",
    37  			"",
    38  			[]SchemaDefinition{},
    39  		},
    40  		{
    41  			"parse error",
    42  			withTenantPrefix,
    43  			"foo",
    44  			"parse error in `parse error`, line 1, column 1: Unexpected token at root level: TokenTypeIdentifier",
    45  			[]SchemaDefinition{},
    46  		},
    47  		{
    48  			"nested parse error",
    49  			withTenantPrefix,
    50  			`definition foo {
    51  				relation something: rela | relb + relc	
    52  			}`,
    53  			"parse error in `nested parse error`, line 2, column 37: Expected end of statement or definition, found: TokenTypePlus",
    54  			[]SchemaDefinition{},
    55  		},
    56  		{
    57  			"allows bypassing prefix requirement",
    58  			AllowUnprefixedObjectType(),
    59  			`definition def {}`,
    60  			"",
    61  			[]SchemaDefinition{
    62  				namespace.Namespace("def"),
    63  			},
    64  		},
    65  		{
    66  			"empty def",
    67  			withTenantPrefix,
    68  			`definition def {}`,
    69  			"",
    70  			[]SchemaDefinition{
    71  				namespace.Namespace("sometenant/def"),
    72  			},
    73  		},
    74  		{
    75  			"simple def",
    76  			withTenantPrefix,
    77  			`definition simple {
    78  				relation foo: bar;
    79  			}`,
    80  			"",
    81  			[]SchemaDefinition{
    82  				namespace.Namespace("sometenant/simple",
    83  					namespace.MustRelation("foo", nil,
    84  						namespace.AllowedRelation("sometenant/bar", "..."),
    85  					),
    86  				),
    87  			},
    88  		},
    89  		{
    90  			"explicit relation",
    91  			withTenantPrefix,
    92  			`definition simple {
    93  				relation foos: bars#mehs;
    94  			}`,
    95  			"",
    96  			[]SchemaDefinition{
    97  				namespace.Namespace("sometenant/simple",
    98  					namespace.MustRelation("foos", nil,
    99  						namespace.AllowedRelation("sometenant/bars", "mehs"),
   100  					),
   101  				),
   102  			},
   103  		},
   104  		{
   105  			"wildcard relation",
   106  			withTenantPrefix,
   107  			`definition simple {
   108  				relation foos: bars:*
   109  			}`,
   110  			"",
   111  			[]SchemaDefinition{
   112  				namespace.Namespace("sometenant/simple",
   113  					namespace.MustRelation("foos", nil,
   114  						namespace.AllowedPublicNamespace("sometenant/bars"),
   115  					),
   116  				),
   117  			},
   118  		},
   119  		{
   120  			"cross tenant relation",
   121  			withTenantPrefix,
   122  			`definition simple {
   123  				relation foos: anothertenant/bars#mehs;
   124  			}`,
   125  			"",
   126  			[]SchemaDefinition{
   127  				namespace.Namespace("sometenant/simple",
   128  					namespace.MustRelation("foos", nil,
   129  						namespace.AllowedRelation("anothertenant/bars", "mehs"),
   130  					),
   131  				),
   132  			},
   133  		},
   134  		{
   135  			"multiple relations",
   136  			withTenantPrefix,
   137  			`definition simple {
   138  				relation foos: bars#mehs;
   139  				relation hello: there | world;
   140  			}`,
   141  			"",
   142  			[]SchemaDefinition{
   143  				namespace.Namespace("sometenant/simple",
   144  					namespace.MustRelation("foos", nil,
   145  						namespace.AllowedRelation("sometenant/bars", "mehs"),
   146  					),
   147  					namespace.MustRelation("hello", nil,
   148  						namespace.AllowedRelation("sometenant/there", "..."),
   149  						namespace.AllowedRelation("sometenant/world", "..."),
   150  					),
   151  				),
   152  			},
   153  		},
   154  		{
   155  			"relation with required caveat",
   156  			withTenantPrefix,
   157  			`definition simple {
   158  				relation viewer: user with somecaveat
   159  			}`,
   160  			"",
   161  			[]SchemaDefinition{
   162  				namespace.Namespace("sometenant/simple",
   163  					namespace.MustRelation("viewer", nil,
   164  						namespace.AllowedRelationWithCaveat("sometenant/user", "...",
   165  							namespace.AllowedCaveat("somecaveat")),
   166  					),
   167  				),
   168  			},
   169  		},
   170  		{
   171  			"relation with optional caveat",
   172  			withTenantPrefix,
   173  			`definition simple {
   174  				relation viewer: user with somecaveat | user
   175  			}`,
   176  			"",
   177  			[]SchemaDefinition{
   178  				namespace.Namespace("sometenant/simple",
   179  					namespace.MustRelation("viewer", nil,
   180  						namespace.AllowedRelationWithCaveat("sometenant/user", "...",
   181  							namespace.AllowedCaveat("somecaveat")),
   182  						namespace.AllowedRelation("sometenant/user", "..."),
   183  					),
   184  				),
   185  			},
   186  		},
   187  		{
   188  			"relation with multiple caveats",
   189  			withTenantPrefix,
   190  			`definition simple {
   191  				relation viewer: user with somecaveat | user | team#member with anothercaveat
   192  			}`,
   193  			"",
   194  			[]SchemaDefinition{
   195  				namespace.Namespace("sometenant/simple",
   196  					namespace.MustRelation("viewer", nil,
   197  						namespace.AllowedRelationWithCaveat("sometenant/user", "...",
   198  							namespace.AllowedCaveat("somecaveat")),
   199  						namespace.AllowedRelation("sometenant/user", "..."),
   200  						namespace.AllowedRelationWithCaveat("sometenant/team", "member",
   201  							namespace.AllowedCaveat("anothercaveat")),
   202  					),
   203  				),
   204  			},
   205  		},
   206  		{
   207  			"simple permission",
   208  			withTenantPrefix,
   209  			`definition simple {
   210  				permission foos = bars;
   211  			}`,
   212  			"",
   213  			[]SchemaDefinition{
   214  				namespace.Namespace("sometenant/simple",
   215  					namespace.MustRelation("foos",
   216  						namespace.Union(
   217  							namespace.ComputedUserset("bars"),
   218  						),
   219  					),
   220  				),
   221  			},
   222  		},
   223  		{
   224  			"union permission",
   225  			withTenantPrefix,
   226  			`definition simple {
   227  				permission foos = bars + bazs;
   228  			}`,
   229  			"",
   230  			[]SchemaDefinition{
   231  				namespace.Namespace("sometenant/simple",
   232  					namespace.MustRelation("foos",
   233  						namespace.Union(
   234  							namespace.ComputedUserset("bars"),
   235  							namespace.ComputedUserset("bazs"),
   236  						),
   237  					),
   238  				),
   239  			},
   240  		},
   241  		{
   242  			"intersection permission",
   243  			withTenantPrefix,
   244  			`definition simple {
   245  				permission foos = bars & bazs;
   246  			}`,
   247  			"",
   248  			[]SchemaDefinition{
   249  				namespace.Namespace("sometenant/simple",
   250  					namespace.MustRelation("foos",
   251  						namespace.Intersection(
   252  							namespace.ComputedUserset("bars"),
   253  							namespace.ComputedUserset("bazs"),
   254  						),
   255  					),
   256  				),
   257  			},
   258  		},
   259  		{
   260  			"exclusion permission",
   261  			withTenantPrefix,
   262  			`definition simple {
   263  				permission foos = bars - bazs;
   264  			}`,
   265  			"",
   266  			[]SchemaDefinition{
   267  				namespace.Namespace("sometenant/simple",
   268  					namespace.MustRelation("foos",
   269  						namespace.Exclusion(
   270  							namespace.ComputedUserset("bars"),
   271  							namespace.ComputedUserset("bazs"),
   272  						),
   273  					),
   274  				),
   275  			},
   276  		},
   277  		{
   278  			"multi-union permission",
   279  			withTenantPrefix,
   280  			`definition simple {
   281  				permission foos = bars + bazs + mehs;
   282  			}`,
   283  			"",
   284  			[]SchemaDefinition{
   285  				namespace.Namespace("sometenant/simple",
   286  					namespace.MustRelation("foos",
   287  						namespace.Union(
   288  							namespace.ComputedUserset("bars"),
   289  							namespace.ComputedUserset("bazs"),
   290  							namespace.ComputedUserset("mehs"),
   291  						),
   292  					),
   293  				),
   294  			},
   295  		},
   296  		{
   297  			"complex permission",
   298  			withTenantPrefix,
   299  			`definition complex {
   300  				permission foos = bars + bazs - mehs;
   301  			}`,
   302  			"",
   303  			[]SchemaDefinition{
   304  				namespace.Namespace("sometenant/complex",
   305  					namespace.MustRelation("foos",
   306  						namespace.Exclusion(
   307  							namespace.Rewrite(
   308  								namespace.Union(
   309  									namespace.ComputedUserset("bars"),
   310  									namespace.ComputedUserset("bazs"),
   311  								),
   312  							),
   313  							namespace.ComputedUserset("mehs"),
   314  						),
   315  					),
   316  				),
   317  			},
   318  		},
   319  		{
   320  			"complex parens permission",
   321  			withTenantPrefix,
   322  			`definition complex {
   323  				permission foos = bars + (bazs - mehs);
   324  			}`,
   325  			"",
   326  			[]SchemaDefinition{
   327  				namespace.Namespace("sometenant/complex",
   328  					namespace.MustRelation("foos",
   329  						namespace.Union(
   330  							namespace.ComputedUserset("bars"),
   331  							namespace.Rewrite(
   332  								namespace.Exclusion(
   333  									namespace.ComputedUserset("bazs"),
   334  									namespace.ComputedUserset("mehs"),
   335  								),
   336  							),
   337  						),
   338  					),
   339  				),
   340  			},
   341  		},
   342  		{
   343  			"arrow permission",
   344  			withTenantPrefix,
   345  			`definition arrowed {
   346  				permission foos = bars->bazs
   347  			}`,
   348  			"",
   349  			[]SchemaDefinition{
   350  				namespace.Namespace("sometenant/arrowed",
   351  					namespace.MustRelation("foos",
   352  						namespace.Union(
   353  							namespace.TupleToUserset("bars", "bazs"),
   354  						),
   355  					),
   356  				),
   357  			},
   358  		},
   359  
   360  		{
   361  			"multiarrow permission",
   362  			withTenantPrefix,
   363  			`definition arrowed {
   364  				relation somerel: something;
   365  				permission foos = somerel->brel->crel
   366  			}`,
   367  			"parse error in `multiarrow permission`, line 3, column 23: Nested arrows not yet supported",
   368  			[]SchemaDefinition{},
   369  		},
   370  
   371  		/*
   372  			 * TODO: uncomment once supported and remove the test above
   373  			{
   374  				"multiarrow permission",
   375  				`definition arrowed {
   376  					relation somerel: something;
   377  					permission foo = somerel->brel->crel
   378  				}`,
   379  				"",
   380  				[]SchemaDefinition{
   381  					namespace.Namespace("sometenant/arrowed",
   382  						namespace.MustRelation("foo",
   383  							namespace.Union(
   384  								namespace.TupleToUserset("bar", "baz"),
   385  							),
   386  						),
   387  					),
   388  				},
   389  			},
   390  		*/
   391  
   392  		{
   393  			"expression permission",
   394  			withTenantPrefix,
   395  			`definition expressioned {
   396  				permission foos = ((arel->brel) + nil) - drel
   397  			}`,
   398  			"",
   399  			[]SchemaDefinition{
   400  				namespace.Namespace("sometenant/expressioned",
   401  					namespace.MustRelation("foos",
   402  						namespace.Exclusion(
   403  							namespace.Rewrite(
   404  								namespace.Union(
   405  									namespace.TupleToUserset("arel", "brel"),
   406  									namespace.Nil(),
   407  								),
   408  							),
   409  							namespace.ComputedUserset("drel"),
   410  						),
   411  					),
   412  				),
   413  			},
   414  		},
   415  		{
   416  			"multiple permission",
   417  			withTenantPrefix,
   418  			`definition multiple {
   419  				permission first = bars + bazs
   420  				permission second = bars - bazs
   421  				permission third = bars & bazs
   422  				permission fourth = bars->bazs
   423  			}`,
   424  			"",
   425  			[]SchemaDefinition{
   426  				namespace.Namespace("sometenant/multiple",
   427  					namespace.MustRelation("first",
   428  						namespace.Union(
   429  							namespace.ComputedUserset("bars"),
   430  							namespace.ComputedUserset("bazs"),
   431  						),
   432  					),
   433  					namespace.MustRelation("second",
   434  						namespace.Exclusion(
   435  							namespace.ComputedUserset("bars"),
   436  							namespace.ComputedUserset("bazs"),
   437  						),
   438  					),
   439  					namespace.MustRelation("third",
   440  						namespace.Intersection(
   441  							namespace.ComputedUserset("bars"),
   442  							namespace.ComputedUserset("bazs"),
   443  						),
   444  					),
   445  					namespace.MustRelation("fourth",
   446  						namespace.Union(
   447  							namespace.TupleToUserset("bars", "bazs"),
   448  						),
   449  					),
   450  				),
   451  			},
   452  		},
   453  		{
   454  			"permission with nil",
   455  			withTenantPrefix,
   456  			`definition simple {
   457  				permission foos = aaaa + nil + bbbb;
   458  			}`,
   459  			"",
   460  			[]SchemaDefinition{
   461  				namespace.Namespace("sometenant/simple",
   462  					namespace.MustRelation("foos",
   463  						namespace.Union(
   464  							namespace.ComputedUserset("aaaa"),
   465  							namespace.Nil(),
   466  							namespace.ComputedUserset("bbbb"),
   467  						),
   468  					),
   469  				),
   470  			},
   471  		},
   472  		{
   473  			"no implicit tenant with unspecified tenant",
   474  			nilPrefix,
   475  			`definition foos {}`,
   476  			"parse error in `no implicit tenant with unspecified tenant`, line 1, column 1: found reference `foos` without prefix",
   477  			[]SchemaDefinition{},
   478  		},
   479  		{
   480  			"no implicit tenant with specified tenant",
   481  			nilPrefix,
   482  			`definition some_tenant/foos {}`,
   483  			"",
   484  			[]SchemaDefinition{
   485  				namespace.Namespace("some_tenant/foos"),
   486  			},
   487  		},
   488  		{
   489  			"no implicit tenant with unspecified tenant on type ref",
   490  			nilPrefix,
   491  			`definition some_tenant/foo {
   492  				relation somerel: bars
   493  			}`,
   494  			"parse error in `no implicit tenant with unspecified tenant on type ref`, line 2, column 23: found reference `bars` without prefix",
   495  			[]SchemaDefinition{},
   496  		},
   497  		{
   498  			"invalid definition name",
   499  			nilPrefix,
   500  			`definition someTenant/fo {}`,
   501  			"parse error in `invalid definition name`, line 1, column 1: error in object definition someTenant/fo: invalid NamespaceDefinition.Name: value does not match regex pattern \"^([a-z][a-z0-9_]{1,62}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$\"",
   502  			[]SchemaDefinition{},
   503  		},
   504  		{
   505  			"invalid relation name",
   506  			nilPrefix,
   507  			`definition some_tenant/foos {
   508  				relation ab: some_tenant/foos
   509  			}`,
   510  			"parse error in `invalid relation name`, line 2, column 5: error in relation ab: invalid Relation.Name: value does not match regex pattern \"^[a-z][a-z0-9_]{1,62}[a-z0-9]$\"",
   511  			[]SchemaDefinition{},
   512  		},
   513  		{
   514  			"no implicit tenant with specified tenant on type ref",
   515  			nilPrefix,
   516  			`definition some_tenant/foos {
   517  				relation somerel: some_tenant/bars
   518  			}`,
   519  			"",
   520  			[]SchemaDefinition{
   521  				namespace.Namespace("some_tenant/foos",
   522  					namespace.MustRelation(
   523  						"somerel",
   524  						nil,
   525  						namespace.AllowedRelation("some_tenant/bars", "..."),
   526  					),
   527  				),
   528  			},
   529  		},
   530  		{
   531  			"doc comments",
   532  			withTenantPrefix,
   533  			`/**
   534  			  * user is a user
   535  			  */
   536  			definition user {}
   537  
   538  			/**
   539  			 * single is a thing
   540  			 */
   541  			definition single {
   542  				/**
   543  				 * some permission
   544  				 */
   545  				permission first = bars + bazs
   546  			}`,
   547  			"",
   548  			[]SchemaDefinition{
   549  				namespace.WithComment("sometenant/user", `/**
   550  * user is a user
   551  */`),
   552  				namespace.WithComment("sometenant/single", `/**
   553  * single is a thing
   554  */`,
   555  					namespace.MustRelationWithComment("first", `/**
   556  * some permission
   557  */`,
   558  						namespace.Union(
   559  							namespace.ComputedUserset("bars"),
   560  							namespace.ComputedUserset("bazs"),
   561  						),
   562  					),
   563  				),
   564  			},
   565  		},
   566  		{
   567  			"duplicate definition",
   568  			withTenantPrefix,
   569  			`definition foo {}
   570  			definition foo {}`,
   571  			"parse error in `duplicate definition`, line 2, column 4: found name reused between multiple definitions and/or caveats: sometenant/foo",
   572  			[]SchemaDefinition{},
   573  		},
   574  		{
   575  			"duplicate definitions across objects and caveats",
   576  			withTenantPrefix,
   577  			`caveat foo(someParam int) {
   578  				someParam == 42
   579  			}
   580  			definition foo {}`,
   581  			"parse error in `duplicate definitions across objects and caveats`, line 4, column 4: found name reused between multiple definitions and/or caveats: sometenant/foo",
   582  			[]SchemaDefinition{},
   583  		},
   584  		{
   585  			"caveat missing parameters",
   586  			withTenantPrefix,
   587  			`caveat foo() {
   588  				someParam == 42
   589  			}`,
   590  			"parse error in `caveat missing parameters`, line 1, column 12: Unexpected token at root level: TokenTypeRightParen",
   591  			[]SchemaDefinition{},
   592  		},
   593  		{
   594  			"caveat missing expression",
   595  			withTenantPrefix,
   596  			`caveat foo(someParam int) {
   597  			}`,
   598  			"Unexpected token at root level: TokenTypeRightBrace",
   599  			[]SchemaDefinition{},
   600  		},
   601  		{
   602  			"caveat invalid parameter type",
   603  			withTenantPrefix,
   604  			`caveat foo(someParam foobar) {
   605  				someParam == 42
   606  			}`,
   607  			"parse error in `caveat invalid parameter type`, line 1, column 12: invalid type for caveat parameter `someParam` on caveat `foo`: unknown type `foobar`",
   608  			[]SchemaDefinition{},
   609  		},
   610  		{
   611  			"caveat invalid parameter type",
   612  			withTenantPrefix,
   613  			`caveat foo(someParam map<foobar>) {
   614  				someParam == 42
   615  			}`,
   616  			"unknown type `foobar`",
   617  			[]SchemaDefinition{},
   618  		},
   619  		{
   620  			"caveat missing parameter",
   621  			withTenantPrefix,
   622  			`caveat foo(someParam int) {
   623  				anotherParam == 42
   624  			}`,
   625  			`ERROR: sometenant/foo:2:5: undeclared reference to 'anotherParam'`,
   626  			[]SchemaDefinition{},
   627  		},
   628  		{
   629  			"caveat missing parameter on a different line",
   630  			withTenantPrefix,
   631  			`caveat foo(someParam int) {
   632  				someParam == 42 &&
   633  					anotherParam
   634  			}`,
   635  			`ERROR: sometenant/foo:3:6: undeclared reference to 'anotherParam'`,
   636  			[]SchemaDefinition{},
   637  		},
   638  		{
   639  			"caveat invalid expression type",
   640  			withTenantPrefix,
   641  			`caveat foo(someParam int) {
   642  				someParam
   643  			}`,
   644  			`caveat expression must result in a boolean value`,
   645  			[]SchemaDefinition{},
   646  		},
   647  		{
   648  			"caveat invalid expression",
   649  			withTenantPrefix,
   650  			`caveat foo(someParam int) {
   651  				someParam:{}
   652  			}`,
   653  			`ERROR: sometenant/foo:2:14: Syntax error: mismatched input ':'`,
   654  			[]SchemaDefinition{},
   655  		},
   656  		{
   657  			"caveat valid",
   658  			withTenantPrefix,
   659  			`caveat foo(someParam int) {
   660  				someParam == 42
   661  			}`,
   662  			``,
   663  			[]SchemaDefinition{
   664  				namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
   665  					map[string]caveattypes.VariableType{
   666  						"someParam": caveattypes.IntType,
   667  					},
   668  				), "sometenant/foo", "someParam == 42"),
   669  			},
   670  		},
   671  		{
   672  			"long caveat valid",
   673  			withTenantPrefix,
   674  			`caveat foo(someParam int, anotherParam string, thirdParam list<int>) {
   675  				someParam == 42 && someParam != 43 && someParam < 12 &&
   676  				someParam > 56 && anotherParam == "hi there" && 42 in thirdParam
   677  			}`,
   678  			``,
   679  			[]SchemaDefinition{
   680  				namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
   681  					map[string]caveattypes.VariableType{
   682  						"someParam":    caveattypes.IntType,
   683  						"anotherParam": caveattypes.StringType,
   684  						"thirdParam":   caveattypes.MustListType(caveattypes.IntType),
   685  					},
   686  				), "sometenant/foo",
   687  					`someParam == 42 && someParam != 43 && someParam < 12 && someParam > 56 
   688  					&& anotherParam == "hi there" && 42 in thirdParam`),
   689  			},
   690  		},
   691  		{
   692  			"caveat IP example",
   693  			withTenantPrefix,
   694  			`caveat has_allowed_ip(user_ip ipaddress) {
   695  				!user_ip.in_cidr('1.2.3.0')
   696  			}`,
   697  			``,
   698  			[]SchemaDefinition{
   699  				namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
   700  					map[string]caveattypes.VariableType{
   701  						"user_ip": caveattypes.IPAddressType,
   702  					},
   703  				), "sometenant/has_allowed_ip",
   704  					`!user_ip.in_cidr('1.2.3.0')`),
   705  			},
   706  		},
   707  		{
   708  			"caveat subtree example",
   709  			withTenantPrefix,
   710  			`caveat something(someMap map<any>, anotherMap map<any>) {
   711  				someMap.isSubtreeOf(anotherMap)
   712  			}`,
   713  			``,
   714  			[]SchemaDefinition{
   715  				namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
   716  					map[string]caveattypes.VariableType{
   717  						"someMap":    caveattypes.MustMapType(caveattypes.AnyType),
   718  						"anotherMap": caveattypes.MustMapType(caveattypes.AnyType),
   719  					},
   720  				), "sometenant/something",
   721  					`someMap.isSubtreeOf(anotherMap)`),
   722  			},
   723  		},
   724  		{
   725  			"union permission with multiple branches",
   726  			withTenantPrefix,
   727  			`definition simple {
   728  				permission foos = first + second + third + fourth
   729  			}`,
   730  			"",
   731  			[]SchemaDefinition{
   732  				namespace.Namespace("sometenant/simple",
   733  					namespace.MustRelation("foos",
   734  						namespace.Union(
   735  							namespace.ComputedUserset("first"),
   736  							namespace.ComputedUserset("second"),
   737  							namespace.ComputedUserset("third"),
   738  							namespace.ComputedUserset("fourth"),
   739  						),
   740  					),
   741  				),
   742  			},
   743  		},
   744  		{
   745  			"union permission with multiple branches, some not union",
   746  			withTenantPrefix,
   747  			`definition simple {
   748  				permission foos = first + second + (foo - bar) + fourth
   749  			}`,
   750  			"",
   751  			[]SchemaDefinition{
   752  				namespace.Namespace("sometenant/simple",
   753  					namespace.MustRelation("foos",
   754  						namespace.Union(
   755  							namespace.ComputedUserset("first"),
   756  							namespace.ComputedUserset("second"),
   757  							namespace.Rewrite(
   758  								namespace.Exclusion(
   759  									namespace.ComputedUserset("foo"),
   760  									namespace.ComputedUserset("bar"),
   761  								),
   762  							),
   763  							namespace.ComputedUserset("fourth"),
   764  						),
   765  					),
   766  				),
   767  			},
   768  		},
   769  		{
   770  			"intersection permission with multiple branches",
   771  			withTenantPrefix,
   772  			`definition simple {
   773  				permission foos = first & second & third & fourth
   774  			}`,
   775  			"",
   776  			[]SchemaDefinition{
   777  				namespace.Namespace("sometenant/simple",
   778  					namespace.MustRelation("foos",
   779  						namespace.Intersection(
   780  							namespace.ComputedUserset("first"),
   781  							namespace.ComputedUserset("second"),
   782  							namespace.ComputedUserset("third"),
   783  							namespace.ComputedUserset("fourth"),
   784  						),
   785  					),
   786  				),
   787  			},
   788  		},
   789  		{
   790  			"exclusion permission with multiple branches",
   791  			withTenantPrefix,
   792  			`definition simple {
   793  				permission foos = first - second - third - fourth
   794  			}`,
   795  			"",
   796  			[]SchemaDefinition{
   797  				namespace.Namespace("sometenant/simple",
   798  					namespace.MustRelation("foos",
   799  						namespace.Exclusion(
   800  							namespace.Rewrite(
   801  								namespace.Exclusion(
   802  									namespace.Rewrite(
   803  										namespace.Exclusion(
   804  											namespace.ComputedUserset("first"),
   805  											namespace.ComputedUserset("second"),
   806  										),
   807  									),
   808  									namespace.ComputedUserset("third"),
   809  								),
   810  							),
   811  							namespace.ComputedUserset("fourth"),
   812  						),
   813  					),
   814  				),
   815  			},
   816  		},
   817  		{
   818  			"wrong tenant is not translated",
   819  			withTenantPrefix,
   820  			`definition someothertenant/simple {
   821  				permission foos = (first + second) + (third + fourth)
   822  			}`,
   823  			"",
   824  			[]SchemaDefinition{
   825  				namespace.Namespace("someothertenant/simple",
   826  					namespace.MustRelation("foos",
   827  						namespace.Union(
   828  							namespace.ComputedUserset("first"),
   829  							namespace.ComputedUserset("second"),
   830  							namespace.ComputedUserset("third"),
   831  							namespace.ComputedUserset("fourth"),
   832  						),
   833  					),
   834  				),
   835  			},
   836  		},
   837  		{
   838  			"multiple-segment tenant",
   839  			withTenantPrefix,
   840  			`definition sometenant/some_team/simple {
   841  				permission foos = (first + second) + (third + fourth)
   842  			}`,
   843  			"",
   844  			[]SchemaDefinition{
   845  				namespace.Namespace("sometenant/some_team/simple",
   846  					namespace.MustRelation("foos",
   847  						namespace.Union(
   848  							namespace.ComputedUserset("first"),
   849  							namespace.ComputedUserset("second"),
   850  							namespace.ComputedUserset("third"),
   851  							namespace.ComputedUserset("fourth"),
   852  						),
   853  					),
   854  				),
   855  			},
   856  		},
   857  		{
   858  			"multiple levels of compressed nesting",
   859  			withTenantPrefix,
   860  			`definition simple {
   861  				permission foos = (first + second) + (third + fourth)
   862  			}`,
   863  			"",
   864  			[]SchemaDefinition{
   865  				namespace.Namespace("sometenant/simple",
   866  					namespace.MustRelation("foos",
   867  						namespace.Union(
   868  							namespace.ComputedUserset("first"),
   869  							namespace.ComputedUserset("second"),
   870  							namespace.ComputedUserset("third"),
   871  							namespace.ComputedUserset("fourth"),
   872  						),
   873  					),
   874  				),
   875  			},
   876  		},
   877  		{
   878  			"multiple levels",
   879  			withTenantPrefix,
   880  			`definition simple {
   881  				permission foos = (first + second) + (middle & thing) + (third + fourth)
   882  			}`,
   883  			"",
   884  			[]SchemaDefinition{
   885  				namespace.Namespace("sometenant/simple",
   886  					namespace.MustRelation("foos",
   887  						namespace.Union(
   888  							namespace.ComputedUserset("first"),
   889  							namespace.ComputedUserset("second"),
   890  							namespace.Rewrite(
   891  								namespace.Intersection(
   892  									namespace.ComputedUserset("middle"),
   893  									namespace.ComputedUserset("thing"),
   894  								),
   895  							),
   896  							namespace.ComputedUserset("third"),
   897  							namespace.ComputedUserset("fourth"),
   898  						),
   899  					),
   900  				),
   901  			},
   902  		},
   903  		{
   904  			"multiple reduction",
   905  			withTenantPrefix,
   906  			`definition simple {
   907  				permission foos = first + second + (fourth & (sixth - seventh) & fifth) + third
   908  			}`,
   909  			"",
   910  			[]SchemaDefinition{
   911  				namespace.Namespace("sometenant/simple",
   912  					namespace.MustRelation("foos",
   913  						namespace.Union(
   914  							namespace.ComputedUserset("first"),
   915  							namespace.ComputedUserset("second"),
   916  							namespace.Rewrite(
   917  								namespace.Intersection(
   918  									namespace.ComputedUserset("fourth"),
   919  									namespace.Rewrite(
   920  										namespace.Exclusion(
   921  											namespace.ComputedUserset("sixth"),
   922  											namespace.ComputedUserset("seventh"),
   923  										),
   924  									),
   925  									namespace.ComputedUserset("fifth"),
   926  								),
   927  							),
   928  							namespace.ComputedUserset("third"),
   929  						),
   930  					),
   931  				),
   932  			},
   933  		},
   934  	}
   935  
   936  	for _, test := range tests {
   937  		test := test
   938  		t.Run(test.name, func(t *testing.T) {
   939  			require := require.New(t)
   940  			compiled, err := Compile(InputSchema{
   941  				input.Source(test.name), test.input,
   942  			}, test.objectPrefix)
   943  
   944  			if test.expectedError != "" {
   945  				require.Error(err)
   946  				require.Contains(err.Error(), test.expectedError)
   947  			} else {
   948  				require.Nil(err)
   949  				require.Equal(len(test.expectedProto), len(compiled.OrderedDefinitions))
   950  				for index, def := range compiled.OrderedDefinitions {
   951  					filterSourcePositions(def.ProtoReflect())
   952  					expectedDef := test.expectedProto[index]
   953  
   954  					if caveatDef, ok := def.(*core.CaveatDefinition); ok {
   955  						expectedCaveatDef, ok := expectedDef.(*core.CaveatDefinition)
   956  						require.True(ok, "definition is not a caveat def")
   957  						require.Equal(expectedCaveatDef.Name, caveatDef.Name)
   958  						require.Equal(len(expectedCaveatDef.ParameterTypes), len(caveatDef.ParameterTypes))
   959  
   960  						for expectedParamName, expectedParam := range expectedCaveatDef.ParameterTypes {
   961  							foundParam, ok := caveatDef.ParameterTypes[expectedParamName]
   962  							require.True(ok, "missing parameter %s", expectedParamName)
   963  							testutil.RequireProtoEqual(t, expectedParam, foundParam, "mismatch type for parameter %s", expectedParamName)
   964  						}
   965  
   966  						parameterTypes, err := caveattypes.DecodeParameterTypes(caveatDef.ParameterTypes)
   967  						require.NoError(err)
   968  
   969  						expectedDecoded, err := caveats.DeserializeCaveat(expectedCaveatDef.SerializedExpression, parameterTypes)
   970  						require.NoError(err)
   971  
   972  						foundDecoded, err := caveats.DeserializeCaveat(caveatDef.SerializedExpression, parameterTypes)
   973  						require.NoError(err)
   974  
   975  						expectedExprString, err := expectedDecoded.ExprString()
   976  						require.NoError(err)
   977  
   978  						foundExprString, err := foundDecoded.ExprString()
   979  						require.NoError(err)
   980  
   981  						require.Equal(expectedExprString, foundExprString)
   982  					} else {
   983  						testutil.RequireProtoEqual(t, test.expectedProto[index], def, "proto mismatch")
   984  					}
   985  				}
   986  			}
   987  		})
   988  	}
   989  }
   990  
   991  func filterSourcePositions(m protoreflect.Message) {
   992  	m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
   993  		if fd.Kind() == protoreflect.MessageKind {
   994  			if fd.IsList() {
   995  				l := v.List()
   996  				for i := 0; i < l.Len(); i++ {
   997  					filterSourcePositions(l.Get(i).Message())
   998  				}
   999  			} else if fd.IsMap() {
  1000  				m := v.Map()
  1001  				m.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
  1002  					filterSourcePositions(v.Message())
  1003  					return true
  1004  				})
  1005  			} else {
  1006  				if string(fd.Message().Name()) == "SourcePosition" {
  1007  					m.Clear(fd)
  1008  				} else {
  1009  					filterSourcePositions(v.Message())
  1010  				}
  1011  			}
  1012  		}
  1013  		return true
  1014  	})
  1015  }
  1016  
  1017  func TestSkipValidation(t *testing.T) {
  1018  	_, err := Compile(InputSchema{"test", `definition a/def {}`}, AllowUnprefixedObjectType())
  1019  	require.Error(t, err)
  1020  
  1021  	_, err = Compile(InputSchema{"test", `definition a/def {}`}, AllowUnprefixedObjectType(), SkipValidation())
  1022  	require.NoError(t, err)
  1023  }
  1024  
  1025  func TestSuperLargeCaveatCompile(t *testing.T) {
  1026  	b, err := os.ReadFile("../parser/tests/superlarge.zed")
  1027  	if err != nil {
  1028  		panic(err)
  1029  	}
  1030  
  1031  	compiled, err := Compile(InputSchema{
  1032  		"superlarge", string(b),
  1033  	}, AllowUnprefixedObjectType())
  1034  	require.NoError(t, err)
  1035  	require.Equal(t, 29, len(compiled.ObjectDefinitions))
  1036  	require.Equal(t, 1, len(compiled.CaveatDefinitions))
  1037  }