Skip to content

Commit

Permalink
Merge pull request #2212 from authzed/implement-partials-compilation
Browse files Browse the repository at this point in the history
Implement partials compilation
  • Loading branch information
tstirrat15 authored Jan 31, 2025
2 parents 88063a2 + 5b6840f commit f5c4424
Show file tree
Hide file tree
Showing 5 changed files with 484 additions and 144 deletions.
67 changes: 31 additions & 36 deletions pkg/composableschemadsl/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,55 +86,39 @@ type Option func(*config)

type ObjectPrefixOption func(*config)

type compilationContext struct {
// The set of definition names that we've seen as we compile.
// If these collide we throw an error.
existingNames *mapz.Set[string]
// The global set of files we've visited in the import process.
// If these collide we short circuit, preventing duplicate imports.
globallyVisitedFiles *mapz.Set[string]
// The set of files that we've visited on a particular leg of the recursion.
// This allows for detection of circular imports.
// NOTE: This depends on an assumption that a depth-first search will always
// find a cycle, even if we're otherwise marking globally visited nodes.
locallyVisitedFiles *mapz.Set[string]
}

// Compile compilers the input schema into a set of namespace definition protos.
func Compile(schema InputSchema, prefix ObjectPrefixOption, opts ...Option) (*CompiledSchema, error) {
cctx := compilationContext{
existingNames: mapz.NewSet[string](),
globallyVisitedFiles: mapz.NewSet[string](),
locallyVisitedFiles: mapz.NewSet[string](),
}
return compileImpl(schema, cctx, prefix, opts...)
}

func compileImpl(schema InputSchema, cctx compilationContext, prefix ObjectPrefixOption, opts ...Option) (*CompiledSchema, error) {
cfg := &config{}
prefix(cfg) // required option

for _, fn := range opts {
fn(cfg)
}

mapper := newPositionMapper(schema)
root := parser.Parse(createAstNode, schema.Source, schema.SchemaString).(*dslNode)
errs := root.FindAll(dslshape.NodeTypeError)
if len(errs) > 0 {
err := errorNodeToError(errs[0], mapper)
root, mapper, err := parseSchema(schema)
if err != nil {
return nil, err
}

compiled, err := translate(translationContext{
objectTypePrefix: cfg.objectTypePrefix,
mapper: mapper,
schemaString: schema.SchemaString,
skipValidate: cfg.skipValidation,
// NOTE: import translation is done separately so that partial references
// and definitions defined in separate files can correctly resolve.
err = translateImports(importResolutionContext{
globallyVisitedFiles: mapz.NewSet[string](),
locallyVisitedFiles: mapz.NewSet[string](),
sourceFolder: cfg.sourceFolder,
existingNames: cctx.existingNames,
locallyVisitedFiles: cctx.locallyVisitedFiles,
globallyVisitedFiles: cctx.globallyVisitedFiles,
}, root)
if err != nil {
return nil, err
}

compiled, err := translate(translationContext{
objectTypePrefix: cfg.objectTypePrefix,
mapper: mapper,
schemaString: schema.SchemaString,
skipValidate: cfg.skipValidation,
existingNames: mapz.NewSet[string](),
compiledPartials: make(map[string][]*core.Relation),
unresolvedPartials: mapz.NewMultiMap[string, *dslNode](),
}, root)
if err != nil {
var withNodeError withNodeError
Expand All @@ -148,6 +132,17 @@ func compileImpl(schema InputSchema, cctx compilationContext, prefix ObjectPrefi
return compiled, nil
}

func parseSchema(schema InputSchema) (*dslNode, input.PositionMapper, error) {
mapper := newPositionMapper(schema)
root := parser.Parse(createAstNode, schema.Source, schema.SchemaString).(*dslNode)
errs := root.FindAll(dslshape.NodeTypeError)
if len(errs) > 0 {
err := errorNodeToError(errs[0], mapper)
return nil, nil, err
}
return root, mapper, nil
}

func errorNodeToError(node *dslNode, mapper input.PositionMapper) error {
if node.GetType() != dslshape.NodeTypeError {
return fmt.Errorf("given none error node")
Expand Down
232 changes: 232 additions & 0 deletions pkg/composableschemadsl/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,238 @@ func TestCompile(t *testing.T) {
),
},
},
{
"simple partial",
withTenantPrefix,
`partial view_partial {
relation user: user;
}
definition simple {
...view_partial
}`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/simple",
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
),
},
},
{
"more complex partial",
withTenantPrefix,
`
definition user {}
definition organization {}
partial view_partial {
relation user: user;
permission view = user
}
definition resource {
relation organization: organization
permission manage = organization
...view_partial
}
`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/user"),
namespace.Namespace("sometenant/organization"),
namespace.Namespace("sometenant/resource",
namespace.MustRelation("organization", nil,
namespace.AllowedRelation("sometenant/organization", "..."),
),
namespace.MustRelation("manage",
namespace.Union(
namespace.ComputedUserset("organization"),
),
),
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
namespace.MustRelation("view",
namespace.Union(
namespace.ComputedUserset("user"),
),
),
),
},
},
{
"partial defined after reference",
withTenantPrefix,
`definition simple {
...view_partial
}
partial view_partial {
relation user: user;
}`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/simple",
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
),
},
},
{
"transitive partials",
withTenantPrefix,
`
partial view_partial {
relation user: user;
}
partial transitive_partial {
...view_partial
}
definition simple {
...view_partial
}
`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/simple",
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
),
},
},
{
"transitive partials out of order",
withTenantPrefix,
`
partial transitive_partial {
...view_partial
}
partial view_partial {
relation user: user;
}
definition simple {
...view_partial
}
`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/simple",
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
),
},
},
{
"transitive partials in reverse order",
withTenantPrefix,
`
definition simple {
...view_partial
}
partial transitive_partial {
...view_partial
}
partial view_partial {
relation user: user;
}
`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/simple",
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
),
},
},
{
"forking transitive partials out of order",
withTenantPrefix,
`
partial transitive_partial {
...view_partial
...group_partial
}
partial view_partial {
relation user: user;
}
partial group_partial {
relation group: group;
}
definition simple {
...transitive_partial
}
`,
"",
[]SchemaDefinition{
namespace.Namespace("sometenant/simple",
namespace.MustRelation("user", nil,
namespace.AllowedRelation("sometenant/user", "..."),
),
namespace.MustRelation("group", nil,
namespace.AllowedRelation("sometenant/group", "..."),
),
),
},
},
{
"circular reference in partials",
withTenantPrefix,
`
partial one_partial {
...another_partial
}
partial another_partial {
...one_partial
}
definition simple {
...one_partial
}
`,
"could not resolve partials",
[]SchemaDefinition{},
},
{
"definition reference to nonexistent partial",
withTenantPrefix,
`
definition simple {
...some_partial
}
`,
"could not find partial reference",
[]SchemaDefinition{},
},
{
"definition reference to another definition errors",
withTenantPrefix,
`
definition some_definition {}
definition simple {
...some_definition
}
`,
"could not find partial reference",
[]SchemaDefinition{},
},
{
"explicit relation",
withTenantPrefix,
Expand Down
Loading

0 comments on commit f5c4424

Please sign in to comment.