github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/v1/expreflection.go (about) 1 package v1 2 3 import ( 4 "sort" 5 "strings" 6 7 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 8 "golang.org/x/exp/maps" 9 10 "github.com/authzed/spicedb/pkg/caveats" 11 caveattypes "github.com/authzed/spicedb/pkg/caveats/types" 12 "github.com/authzed/spicedb/pkg/datastore" 13 "github.com/authzed/spicedb/pkg/diff" 14 caveatdiff "github.com/authzed/spicedb/pkg/diff/caveats" 15 nsdiff "github.com/authzed/spicedb/pkg/diff/namespace" 16 "github.com/authzed/spicedb/pkg/namespace" 17 core "github.com/authzed/spicedb/pkg/proto/core/v1" 18 iv1 "github.com/authzed/spicedb/pkg/proto/impl/v1" 19 "github.com/authzed/spicedb/pkg/spiceerrors" 20 "github.com/authzed/spicedb/pkg/tuple" 21 "github.com/authzed/spicedb/pkg/zedtoken" 22 ) 23 24 type schemaFilters struct { 25 filters []*v1.ExpSchemaFilter 26 } 27 28 func newSchemaFilters(filters []*v1.ExpSchemaFilter) (*schemaFilters, error) { 29 for _, filter := range filters { 30 if filter.OptionalDefinitionNameFilter != "" { 31 if filter.OptionalCaveatNameFilter != "" { 32 return nil, NewInvalidFilterErr("cannot filter by both definition and caveat name", filter.String()) 33 } 34 } 35 36 if filter.OptionalRelationNameFilter != "" { 37 if filter.OptionalDefinitionNameFilter == "" { 38 return nil, NewInvalidFilterErr("relation name match requires definition name match", filter.String()) 39 } 40 41 if filter.OptionalPermissionNameFilter != "" { 42 return nil, NewInvalidFilterErr("cannot filter by both relation and permission name", filter.String()) 43 } 44 } 45 46 if filter.OptionalPermissionNameFilter != "" { 47 if filter.OptionalDefinitionNameFilter == "" { 48 return nil, NewInvalidFilterErr("permission name match requires definition name match", filter.String()) 49 } 50 } 51 } 52 53 return &schemaFilters{filters: filters}, nil 54 } 55 56 func (sf *schemaFilters) HasNamespaces() bool { 57 if len(sf.filters) == 0 { 58 return true 59 } 60 61 for _, filter := range sf.filters { 62 if filter.OptionalDefinitionNameFilter != "" { 63 return true 64 } 65 } 66 67 return false 68 } 69 70 func (sf *schemaFilters) HasCaveats() bool { 71 if len(sf.filters) == 0 { 72 return true 73 } 74 75 for _, filter := range sf.filters { 76 if filter.OptionalCaveatNameFilter != "" { 77 return true 78 } 79 } 80 81 return false 82 } 83 84 func (sf *schemaFilters) HasNamespace(namespaceName string) bool { 85 if len(sf.filters) == 0 { 86 return true 87 } 88 89 hasDefinitionFilter := false 90 for _, filter := range sf.filters { 91 if filter.OptionalDefinitionNameFilter == "" { 92 continue 93 } 94 95 hasDefinitionFilter = true 96 isMatch := strings.HasPrefix(namespaceName, filter.OptionalDefinitionNameFilter) 97 if isMatch { 98 return true 99 } 100 } 101 102 return !hasDefinitionFilter 103 } 104 105 func (sf *schemaFilters) HasCaveat(caveatName string) bool { 106 if len(sf.filters) == 0 { 107 return true 108 } 109 110 hasCaveatFilter := false 111 for _, filter := range sf.filters { 112 if filter.OptionalCaveatNameFilter == "" { 113 continue 114 } 115 116 hasCaveatFilter = true 117 isMatch := strings.HasPrefix(caveatName, filter.OptionalCaveatNameFilter) 118 if isMatch { 119 return true 120 } 121 } 122 123 return !hasCaveatFilter 124 } 125 126 func (sf *schemaFilters) HasRelation(namespaceName, relationName string) bool { 127 if len(sf.filters) == 0 { 128 return true 129 } 130 131 hasRelationFilter := false 132 for _, filter := range sf.filters { 133 if filter.OptionalRelationNameFilter == "" { 134 continue 135 } 136 137 hasRelationFilter = true 138 isMatch := strings.HasPrefix(relationName, filter.OptionalRelationNameFilter) 139 if !isMatch { 140 continue 141 } 142 143 isMatch = strings.HasPrefix(namespaceName, filter.OptionalDefinitionNameFilter) 144 if isMatch { 145 return true 146 } 147 } 148 149 return !hasRelationFilter 150 } 151 152 func (sf *schemaFilters) HasPermission(namespaceName, permissionName string) bool { 153 if len(sf.filters) == 0 { 154 return true 155 } 156 157 hasPermissionFilter := false 158 for _, filter := range sf.filters { 159 if filter.OptionalPermissionNameFilter == "" { 160 continue 161 } 162 163 hasPermissionFilter = true 164 isMatch := strings.HasPrefix(permissionName, filter.OptionalPermissionNameFilter) 165 if !isMatch { 166 continue 167 } 168 169 isMatch = strings.HasPrefix(namespaceName, filter.OptionalDefinitionNameFilter) 170 if isMatch { 171 return true 172 } 173 } 174 175 return !hasPermissionFilter 176 } 177 178 // convertDiff converts a schema diff into an API response. 179 func convertDiff( 180 diff *diff.SchemaDiff, 181 existingSchema *diff.DiffableSchema, 182 comparisonSchema *diff.DiffableSchema, 183 atRevision datastore.Revision, 184 ) (*v1.ExperimentalDiffSchemaResponse, error) { 185 size := len(diff.AddedNamespaces) + len(diff.RemovedNamespaces) + len(diff.AddedCaveats) + len(diff.RemovedCaveats) + len(diff.ChangedNamespaces) + len(diff.ChangedCaveats) 186 diffs := make([]*v1.ExpSchemaDiff, 0, size) 187 188 // Add/remove namespaces. 189 for _, ns := range diff.AddedNamespaces { 190 nsDef, err := namespaceAPIReprForName(ns, comparisonSchema) 191 if err != nil { 192 return nil, err 193 } 194 195 diffs = append(diffs, &v1.ExpSchemaDiff{ 196 Diff: &v1.ExpSchemaDiff_DefinitionAdded{ 197 DefinitionAdded: nsDef, 198 }, 199 }) 200 } 201 202 for _, ns := range diff.RemovedNamespaces { 203 nsDef, err := namespaceAPIReprForName(ns, existingSchema) 204 if err != nil { 205 return nil, err 206 } 207 208 diffs = append(diffs, &v1.ExpSchemaDiff{ 209 Diff: &v1.ExpSchemaDiff_DefinitionRemoved{ 210 DefinitionRemoved: nsDef, 211 }, 212 }) 213 } 214 215 // Add/remove caveats. 216 for _, caveat := range diff.AddedCaveats { 217 caveatDef, err := caveatAPIReprForName(caveat, comparisonSchema) 218 if err != nil { 219 return nil, err 220 } 221 222 diffs = append(diffs, &v1.ExpSchemaDiff{ 223 Diff: &v1.ExpSchemaDiff_CaveatAdded{ 224 CaveatAdded: caveatDef, 225 }, 226 }) 227 } 228 229 for _, caveat := range diff.RemovedCaveats { 230 caveatDef, err := caveatAPIReprForName(caveat, existingSchema) 231 if err != nil { 232 return nil, err 233 } 234 235 diffs = append(diffs, &v1.ExpSchemaDiff{ 236 Diff: &v1.ExpSchemaDiff_CaveatRemoved{ 237 CaveatRemoved: caveatDef, 238 }, 239 }) 240 } 241 242 // Changed namespaces. 243 for nsName, nsDiff := range diff.ChangedNamespaces { 244 for _, delta := range nsDiff.Deltas() { 245 switch delta.Type { 246 case nsdiff.AddedPermission: 247 permission, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 248 if !ok { 249 return nil, spiceerrors.MustBugf("permission %q not found in namespace %q", delta.RelationName, nsName) 250 } 251 252 perm, err := permissionAPIRepr(permission, nsName, nil) 253 if err != nil { 254 return nil, err 255 } 256 257 diffs = append(diffs, &v1.ExpSchemaDiff{ 258 Diff: &v1.ExpSchemaDiff_PermissionAdded{ 259 PermissionAdded: perm, 260 }, 261 }) 262 263 case nsdiff.AddedRelation: 264 relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 265 if !ok { 266 return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) 267 } 268 269 rel, err := relationAPIRepr(relation, nsName, nil) 270 if err != nil { 271 return nil, err 272 } 273 274 diffs = append(diffs, &v1.ExpSchemaDiff{ 275 Diff: &v1.ExpSchemaDiff_RelationAdded{ 276 RelationAdded: rel, 277 }, 278 }) 279 280 case nsdiff.ChangedPermissionComment: 281 permission, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 282 if !ok { 283 return nil, spiceerrors.MustBugf("permission %q not found in namespace %q", delta.RelationName, nsName) 284 } 285 286 perm, err := permissionAPIRepr(permission, nsName, nil) 287 if err != nil { 288 return nil, err 289 } 290 291 diffs = append(diffs, &v1.ExpSchemaDiff{ 292 Diff: &v1.ExpSchemaDiff_PermissionDocCommentChanged{ 293 PermissionDocCommentChanged: perm, 294 }, 295 }) 296 297 case nsdiff.ChangedPermissionImpl: 298 permission, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 299 if !ok { 300 return nil, spiceerrors.MustBugf("permission %q not found in namespace %q", delta.RelationName, nsName) 301 } 302 303 perm, err := permissionAPIRepr(permission, nsName, nil) 304 if err != nil { 305 return nil, err 306 } 307 308 diffs = append(diffs, &v1.ExpSchemaDiff{ 309 Diff: &v1.ExpSchemaDiff_PermissionExprChanged{ 310 PermissionExprChanged: perm, 311 }, 312 }) 313 314 case nsdiff.ChangedRelationComment: 315 relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 316 if !ok { 317 return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) 318 } 319 320 rel, err := relationAPIRepr(relation, nsName, nil) 321 if err != nil { 322 return nil, err 323 } 324 325 diffs = append(diffs, &v1.ExpSchemaDiff{ 326 Diff: &v1.ExpSchemaDiff_RelationDocCommentChanged{ 327 RelationDocCommentChanged: rel, 328 }, 329 }) 330 331 case nsdiff.LegacyChangedRelationImpl: 332 return nil, spiceerrors.MustBugf("legacy relation implementation changes are not supported") 333 334 case nsdiff.NamespaceCommentsChanged: 335 def, err := namespaceAPIReprForName(nsName, comparisonSchema) 336 if err != nil { 337 return nil, err 338 } 339 340 diffs = append(diffs, &v1.ExpSchemaDiff{ 341 Diff: &v1.ExpSchemaDiff_DefinitionDocCommentChanged{ 342 DefinitionDocCommentChanged: def, 343 }, 344 }) 345 346 case nsdiff.RelationAllowedTypeRemoved: 347 relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 348 if !ok { 349 return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) 350 } 351 352 rel, err := relationAPIRepr(relation, nsName, nil) 353 if err != nil { 354 return nil, err 355 } 356 357 diffs = append(diffs, &v1.ExpSchemaDiff{ 358 Diff: &v1.ExpSchemaDiff_RelationSubjectTypeRemoved{ 359 RelationSubjectTypeRemoved: &v1.ExpRelationSubjectTypeChange{ 360 Relation: rel, 361 ChangedSubjectType: typeAPIRepr(delta.AllowedType), 362 }, 363 }, 364 }) 365 366 case nsdiff.RelationAllowedTypeAdded: 367 relation, ok := comparisonSchema.GetRelation(nsName, delta.RelationName) 368 if !ok { 369 return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) 370 } 371 372 rel, err := relationAPIRepr(relation, nsName, nil) 373 if err != nil { 374 return nil, err 375 } 376 377 diffs = append(diffs, &v1.ExpSchemaDiff{ 378 Diff: &v1.ExpSchemaDiff_RelationSubjectTypeAdded{ 379 RelationSubjectTypeAdded: &v1.ExpRelationSubjectTypeChange{ 380 Relation: rel, 381 ChangedSubjectType: typeAPIRepr(delta.AllowedType), 382 }, 383 }, 384 }) 385 386 case nsdiff.RemovedPermission: 387 permission, ok := existingSchema.GetRelation(nsName, delta.RelationName) 388 if !ok { 389 return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) 390 } 391 392 perm, err := permissionAPIRepr(permission, nsName, nil) 393 if err != nil { 394 return nil, err 395 } 396 397 diffs = append(diffs, &v1.ExpSchemaDiff{ 398 Diff: &v1.ExpSchemaDiff_PermissionRemoved{ 399 PermissionRemoved: perm, 400 }, 401 }) 402 403 case nsdiff.RemovedRelation: 404 relation, ok := existingSchema.GetRelation(nsName, delta.RelationName) 405 if !ok { 406 return nil, spiceerrors.MustBugf("relation %q not found in namespace %q", delta.RelationName, nsName) 407 } 408 409 rel, err := relationAPIRepr(relation, nsName, nil) 410 if err != nil { 411 return nil, err 412 } 413 414 diffs = append(diffs, &v1.ExpSchemaDiff{ 415 Diff: &v1.ExpSchemaDiff_RelationRemoved{ 416 RelationRemoved: rel, 417 }, 418 }) 419 420 case nsdiff.NamespaceAdded: 421 return nil, spiceerrors.MustBugf("should be handled above") 422 423 case nsdiff.NamespaceRemoved: 424 return nil, spiceerrors.MustBugf("should be handled above") 425 426 default: 427 return nil, spiceerrors.MustBugf("unexpected delta type %v", delta.Type) 428 } 429 } 430 } 431 432 // Changed caveats. 433 for caveatName, caveatDiff := range diff.ChangedCaveats { 434 for _, delta := range caveatDiff.Deltas() { 435 switch delta.Type { 436 case caveatdiff.CaveatCommentsChanged: 437 caveat, err := caveatAPIReprForName(caveatName, comparisonSchema) 438 if err != nil { 439 return nil, err 440 } 441 442 diffs = append(diffs, &v1.ExpSchemaDiff{ 443 Diff: &v1.ExpSchemaDiff_CaveatDocCommentChanged{ 444 CaveatDocCommentChanged: caveat, 445 }, 446 }) 447 448 case caveatdiff.AddedParameter: 449 paramDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, comparisonSchema) 450 if err != nil { 451 return nil, err 452 } 453 454 diffs = append(diffs, &v1.ExpSchemaDiff{ 455 Diff: &v1.ExpSchemaDiff_CaveatParameterAdded{ 456 CaveatParameterAdded: paramDef, 457 }, 458 }) 459 460 case caveatdiff.RemovedParameter: 461 paramDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, existingSchema) 462 if err != nil { 463 return nil, err 464 } 465 466 diffs = append(diffs, &v1.ExpSchemaDiff{ 467 Diff: &v1.ExpSchemaDiff_CaveatParameterRemoved{ 468 CaveatParameterRemoved: paramDef, 469 }, 470 }) 471 472 case caveatdiff.ParameterTypeChanged: 473 previousParamDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, existingSchema) 474 if err != nil { 475 return nil, err 476 } 477 478 paramDef, err := caveatAPIParamRepr(delta.ParameterName, caveatName, comparisonSchema) 479 if err != nil { 480 return nil, err 481 } 482 483 diffs = append(diffs, &v1.ExpSchemaDiff{ 484 Diff: &v1.ExpSchemaDiff_CaveatParameterTypeChanged{ 485 CaveatParameterTypeChanged: &v1.ExpCaveatParameterTypeChange{ 486 Parameter: paramDef, 487 PreviousType: previousParamDef.Type, 488 }, 489 }, 490 }) 491 492 case caveatdiff.CaveatExpressionChanged: 493 caveat, err := caveatAPIReprForName(caveatName, comparisonSchema) 494 if err != nil { 495 return nil, err 496 } 497 498 diffs = append(diffs, &v1.ExpSchemaDiff{ 499 Diff: &v1.ExpSchemaDiff_CaveatExprChanged{ 500 CaveatExprChanged: caveat, 501 }, 502 }) 503 504 case caveatdiff.CaveatAdded: 505 return nil, spiceerrors.MustBugf("should be handled above") 506 507 case caveatdiff.CaveatRemoved: 508 return nil, spiceerrors.MustBugf("should be handled above") 509 510 default: 511 return nil, spiceerrors.MustBugf("unexpected delta type %v", delta.Type) 512 } 513 } 514 } 515 516 return &v1.ExperimentalDiffSchemaResponse{ 517 Diffs: diffs, 518 ReadAt: zedtoken.MustNewFromRevision(atRevision), 519 }, nil 520 } 521 522 // namespaceAPIReprForName builds an API representation of a namespace. 523 func namespaceAPIReprForName(namespaceName string, schema *diff.DiffableSchema) (*v1.ExpDefinition, error) { 524 nsDef, ok := schema.GetNamespace(namespaceName) 525 if !ok { 526 return nil, spiceerrors.MustBugf("namespace %q not found in schema", namespaceName) 527 } 528 529 return namespaceAPIRepr(nsDef, nil) 530 } 531 532 func namespaceAPIRepr(nsDef *core.NamespaceDefinition, schemaFilters *schemaFilters) (*v1.ExpDefinition, error) { 533 if schemaFilters != nil && !schemaFilters.HasNamespace(nsDef.Name) { 534 return nil, nil 535 } 536 537 relations := make([]*v1.ExpRelation, 0, len(nsDef.Relation)) 538 permissions := make([]*v1.ExpPermission, 0, len(nsDef.Relation)) 539 540 for _, rel := range nsDef.Relation { 541 if namespace.GetRelationKind(rel) == iv1.RelationMetadata_PERMISSION { 542 permission, err := permissionAPIRepr(rel, nsDef.Name, schemaFilters) 543 if err != nil { 544 return nil, err 545 } 546 547 if permission != nil { 548 permissions = append(permissions, permission) 549 } 550 continue 551 } 552 553 relation, err := relationAPIRepr(rel, nsDef.Name, schemaFilters) 554 if err != nil { 555 return nil, err 556 } 557 558 if relation != nil { 559 relations = append(relations, relation) 560 } 561 } 562 563 comments := namespace.GetComments(nsDef.Metadata) 564 return &v1.ExpDefinition{ 565 Name: nsDef.Name, 566 Comment: strings.Join(comments, "\n"), 567 Relations: relations, 568 Permissions: permissions, 569 }, nil 570 } 571 572 // permissionAPIRepr builds an API representation of a permission. 573 func permissionAPIRepr(relation *core.Relation, parentDefName string, schemaFilters *schemaFilters) (*v1.ExpPermission, error) { 574 if schemaFilters != nil && !schemaFilters.HasPermission(parentDefName, relation.Name) { 575 return nil, nil 576 } 577 578 comments := namespace.GetComments(relation.Metadata) 579 return &v1.ExpPermission{ 580 Name: relation.Name, 581 Comment: strings.Join(comments, "\n"), 582 ParentDefinitionName: parentDefName, 583 }, nil 584 } 585 586 // relationAPIRepresentation builds an API representation of a relation. 587 func relationAPIRepr(relation *core.Relation, parentDefName string, schemaFilters *schemaFilters) (*v1.ExpRelation, error) { 588 if schemaFilters != nil && !schemaFilters.HasRelation(parentDefName, relation.Name) { 589 return nil, nil 590 } 591 592 comments := namespace.GetComments(relation.Metadata) 593 594 var subjectTypes []*v1.ExpTypeReference 595 if relation.TypeInformation != nil { 596 subjectTypes = make([]*v1.ExpTypeReference, 0, len(relation.TypeInformation.AllowedDirectRelations)) 597 for _, subjectType := range relation.TypeInformation.AllowedDirectRelations { 598 typeref := typeAPIRepr(subjectType) 599 subjectTypes = append(subjectTypes, typeref) 600 } 601 } 602 603 return &v1.ExpRelation{ 604 Name: relation.Name, 605 Comment: strings.Join(comments, "\n"), 606 ParentDefinitionName: parentDefName, 607 SubjectTypes: subjectTypes, 608 }, nil 609 } 610 611 // typeAPIRepr builds an API representation of a type. 612 func typeAPIRepr(subjectType *core.AllowedRelation) *v1.ExpTypeReference { 613 typeref := &v1.ExpTypeReference{ 614 SubjectDefinitionName: subjectType.Namespace, 615 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 616 } 617 618 if subjectType.GetRelation() != tuple.Ellipsis && subjectType.GetRelation() != "" { 619 typeref.Typeref = &v1.ExpTypeReference_OptionalRelationName{ 620 OptionalRelationName: subjectType.GetRelation(), 621 } 622 } else if subjectType.GetPublicWildcard() != nil { 623 typeref.Typeref = &v1.ExpTypeReference_IsPublicWildcard{ 624 IsPublicWildcard: true, 625 } 626 } 627 628 if subjectType.GetRequiredCaveat() != nil { 629 typeref.OptionalCaveatName = subjectType.GetRequiredCaveat().CaveatName 630 } 631 632 return typeref 633 } 634 635 // caveatAPIReprForName builds an API representation of a caveat. 636 func caveatAPIReprForName(caveatName string, schema *diff.DiffableSchema) (*v1.ExpCaveat, error) { 637 caveatDef, ok := schema.GetCaveat(caveatName) 638 if !ok { 639 return nil, spiceerrors.MustBugf("caveat %q not found in schema", caveatName) 640 } 641 642 return caveatAPIRepr(caveatDef, nil) 643 } 644 645 // caveatAPIRepr builds an API representation of a caveat. 646 func caveatAPIRepr(caveatDef *core.CaveatDefinition, schemaFilters *schemaFilters) (*v1.ExpCaveat, error) { 647 if schemaFilters != nil && !schemaFilters.HasCaveat(caveatDef.Name) { 648 return nil, nil 649 } 650 651 parameters := make([]*v1.ExpCaveatParameter, 0, len(caveatDef.ParameterTypes)) 652 paramNames := maps.Keys(caveatDef.ParameterTypes) 653 sort.Strings(paramNames) 654 655 for _, paramName := range paramNames { 656 paramType, ok := caveatDef.ParameterTypes[paramName] 657 if !ok { 658 return nil, spiceerrors.MustBugf("parameter %q not found in caveat %q", paramName, caveatDef.Name) 659 } 660 661 decoded, err := caveattypes.DecodeParameterType(paramType) 662 if err != nil { 663 return nil, spiceerrors.MustBugf("invalid parameter type on caveat: %v", err) 664 } 665 666 parameters = append(parameters, &v1.ExpCaveatParameter{ 667 Name: paramName, 668 Type: decoded.String(), 669 ParentCaveatName: caveatDef.Name, 670 }) 671 } 672 673 parameterTypes, err := caveattypes.DecodeParameterTypes(caveatDef.ParameterTypes) 674 if err != nil { 675 return nil, spiceerrors.MustBugf("invalid caveat parameters: %v", err) 676 } 677 678 deserializedExpression, err := caveats.DeserializeCaveat(caveatDef.SerializedExpression, parameterTypes) 679 if err != nil { 680 return nil, spiceerrors.MustBugf("invalid caveat expression bytes: %v", err) 681 } 682 683 exprString, err := deserializedExpression.ExprString() 684 if err != nil { 685 return nil, spiceerrors.MustBugf("invalid caveat expression: %v", err) 686 } 687 688 comments := namespace.GetComments(caveatDef.Metadata) 689 return &v1.ExpCaveat{ 690 Name: caveatDef.Name, 691 Comment: strings.Join(comments, "\n"), 692 Parameters: parameters, 693 Expression: exprString, 694 }, nil 695 } 696 697 // caveatAPIParamRepresentation builds an API representation of a caveat parameter. 698 func caveatAPIParamRepr(paramName, parentCaveatName string, schema *diff.DiffableSchema) (*v1.ExpCaveatParameter, error) { 699 caveatDef, ok := schema.GetCaveat(parentCaveatName) 700 if !ok { 701 return nil, spiceerrors.MustBugf("caveat %q not found in schema", parentCaveatName) 702 } 703 704 paramType, ok := caveatDef.ParameterTypes[paramName] 705 if !ok { 706 return nil, spiceerrors.MustBugf("parameter %q not found in caveat %q", paramName, parentCaveatName) 707 } 708 709 decoded, err := caveattypes.DecodeParameterType(paramType) 710 if err != nil { 711 return nil, spiceerrors.MustBugf("invalid parameter type on caveat: %v", err) 712 } 713 714 return &v1.ExpCaveatParameter{ 715 Name: paramName, 716 Type: decoded.String(), 717 ParentCaveatName: parentCaveatName, 718 }, nil 719 }