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 }