Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 3 additions & 99 deletions internal/cmd/preview.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
package cmd

import (
"errors"
"fmt"
"os"
"path/filepath"

"github.com/ccoveille/go-safecast"
"github.com/jzelinskie/cobrautil/v2"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"golang.org/x/term"

newcompiler "github.com/authzed/spicedb/pkg/composableschemadsl/compiler"
newinput "github.com/authzed/spicedb/pkg/composableschemadsl/input"
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
"github.com/authzed/spicedb/pkg/schemadsl/generator"

"github.com/authzed/zed/internal/commands"
)

func registerPreviewCmd(rootCmd *cobra.Command) {
Expand All @@ -26,7 +10,6 @@ func registerPreviewCmd(rootCmd *cobra.Command) {
previewCmd.AddCommand(schemaCmd)

schemaCmd.AddCommand(schemaCompileCmd)
schemaCompileCmd.Flags().String("out", "", "output filepath; omitting writes to stdout")
}

var previewCmd = &cobra.Command{
Expand All @@ -35,86 +18,7 @@ var previewCmd = &cobra.Command{
}

var schemaCmd = &cobra.Command{
Use: "schema <subcommand>",
Short: "Manage schema for a permissions system",
}

var schemaCompileCmd = &cobra.Command{
Use: "compile <file>",
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
Short: "Compile a schema that uses extended syntax into one that can be written to SpiceDB",
Example: `
Write to stdout:
zed preview schema compile root.zed
Write to an output file:
zed preview schema compile root.zed --out compiled.zed
`,
ValidArgsFunction: commands.FileExtensionCompletions("zed"),
RunE: schemaCompileCmdFunc,
}

// Compiles an input schema written in the new composable schema syntax
// and produces it as a fully-realized schema
func schemaCompileCmdFunc(cmd *cobra.Command, args []string) error {
stdOutFd, err := safecast.ToInt(uint(os.Stdout.Fd()))
if err != nil {
return err
}
outputFilepath := cobrautil.MustGetString(cmd, "out")
if outputFilepath == "" && !term.IsTerminal(stdOutFd) {
return fmt.Errorf("must provide stdout or output file path")
}

inputFilepath := args[0]
inputSourceFolder := filepath.Dir(inputFilepath)
var schemaBytes []byte
schemaBytes, err = os.ReadFile(inputFilepath)
if err != nil {
return fmt.Errorf("failed to read schema file: %w", err)
}
log.Trace().Str("schema", string(schemaBytes)).Str("file", args[0]).Msg("read schema from file")

if len(schemaBytes) == 0 {
return errors.New("attempted to compile empty schema")
}

compiled, err := newcompiler.Compile(newcompiler.InputSchema{
Source: newinput.Source(inputFilepath),
SchemaString: string(schemaBytes),
}, newcompiler.AllowUnprefixedObjectType(),
newcompiler.SourceFolder(inputSourceFolder))
if err != nil {
return err
}

// Attempt to cast one kind of OrderedDefinition to another
oldDefinitions := make([]compiler.SchemaDefinition, 0, len(compiled.OrderedDefinitions))
for _, definition := range compiled.OrderedDefinitions {
oldDefinition, ok := definition.(compiler.SchemaDefinition)
if !ok {
return fmt.Errorf("could not convert definition to old schemadefinition: %v", oldDefinition)
}
oldDefinitions = append(oldDefinitions, oldDefinition)
}

// This is where we functionally assert that the two systems are compatible
generated, _, err := generator.GenerateSchema(oldDefinitions)
if err != nil {
return fmt.Errorf("could not generate resulting schema: %w", err)
}

// Add a newline at the end for hygiene's sake
terminated := generated + "\n"

if outputFilepath == "" {
// Print to stdout
fmt.Print(terminated)
} else {
err = os.WriteFile(outputFilepath, []byte(terminated), 0o_600)
if err != nil {
return err
}
}

return nil
Use: "schema <subcommand>",
Short: "Manage schema for a permissions system",
Deprecated: "please use `zed schema compile`",
}
69 changes: 0 additions & 69 deletions internal/cmd/preview_test.go

This file was deleted.

86 changes: 86 additions & 0 deletions internal/cmd/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/ccoveille/go-safecast"
Expand All @@ -17,6 +18,8 @@ import (

v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/authzed/spicedb/pkg/caveats/types"
newcompiler "github.com/authzed/spicedb/pkg/composableschemadsl/compiler"
newinput "github.com/authzed/spicedb/pkg/composableschemadsl/input"
"github.com/authzed/spicedb/pkg/diff"
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
"github.com/authzed/spicedb/pkg/schemadsl/generator"
Expand All @@ -37,6 +40,9 @@ func registerAdditionalSchemaCmds(schemaCmd *cobra.Command) {
schemaWriteCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before writing")

schemaCmd.AddCommand(schemaDiffCmd)

schemaCmd.AddCommand(schemaCompileCmd)
schemaCompileCmd.Flags().String("out", "", "output filepath; omitting writes to stdout")
}

var schemaWriteCmd = &cobra.Command{
Expand All @@ -62,6 +68,20 @@ var schemaDiffCmd = &cobra.Command{
RunE: schemaDiffCmdFunc,
}

var schemaCompileCmd = &cobra.Command{
Use: "compile <file>",
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
Short: "Compile a schema that uses extended syntax into one that can be written to SpiceDB",
Example: `
Write to stdout:
zed preview schema compile root.zed
Write to an output file:
zed preview schema compile root.zed --out compiled.zed
`,
ValidArgsFunction: commands.FileExtensionCompletions("zed"),
RunE: schemaCompileCmdFunc,
}

func schemaDiffCmdFunc(_ *cobra.Command, args []string) error {
beforeBytes, err := os.ReadFile(args[0])
if err != nil {
Expand Down Expand Up @@ -317,3 +337,69 @@ func determinePrefixForSchema(ctx context.Context, specifiedPrefix string, clien

return "", nil
}

// Compiles an input schema written in the new composable schema syntax
// and produces it as a fully-realized schema
func schemaCompileCmdFunc(cmd *cobra.Command, args []string) error {
stdOutFd, err := safecast.ToInt(uint(os.Stdout.Fd()))
if err != nil {
return err
}
outputFilepath := cobrautil.MustGetString(cmd, "out")
if outputFilepath == "" && !term.IsTerminal(stdOutFd) {
return fmt.Errorf("must provide stdout or output file path")
}

inputFilepath := args[0]
inputSourceFolder := filepath.Dir(inputFilepath)
var schemaBytes []byte
schemaBytes, err = os.ReadFile(inputFilepath)
if err != nil {
return fmt.Errorf("failed to read schema file: %w", err)
}
log.Trace().Str("schema", string(schemaBytes)).Str("file", args[0]).Msg("read schema from file")

if len(schemaBytes) == 0 {
return errors.New("attempted to compile empty schema")
}

compiled, err := newcompiler.Compile(newcompiler.InputSchema{
Source: newinput.Source(inputFilepath),
SchemaString: string(schemaBytes),
}, newcompiler.AllowUnprefixedObjectType(),
newcompiler.SourceFolder(inputSourceFolder))
if err != nil {
return err
}

// Attempt to cast one kind of OrderedDefinition to another
oldDefinitions := make([]compiler.SchemaDefinition, 0, len(compiled.OrderedDefinitions))
for _, definition := range compiled.OrderedDefinitions {
oldDefinition, ok := definition.(compiler.SchemaDefinition)
if !ok {
return fmt.Errorf("could not convert definition to old schemadefinition: %v", oldDefinition)
}
oldDefinitions = append(oldDefinitions, oldDefinition)
}

// This is where we functionally assert that the two systems are compatible
generated, _, err := generator.GenerateSchema(oldDefinitions)
if err != nil {
return fmt.Errorf("could not generate resulting schema: %w", err)
}

// Add a newline at the end for hygiene's sake
terminated := generated + "\n"

if outputFilepath == "" {
// Print to stdout
fmt.Print(terminated)
} else {
err = os.WriteFile(outputFilepath, []byte(terminated), 0o_600)
if err != nil {
return err
}
}

return nil
}
62 changes: 62 additions & 0 deletions internal/cmd/schema_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package cmd

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

zedtesting "github.com/authzed/zed/internal/testing"
)

func TestDeterminePrefixForSchema(t *testing.T) {
Expand Down Expand Up @@ -114,3 +118,61 @@ caveat test/some_caveat(someCondition int) {
})
}
}

func TestSchemaCompile(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
files []string
out string
expectErr string
expectStr string
}{
`file_not_found`: {
files: []string{
filepath.Join("preview-test", "nonexistent.zed"),
},
expectErr: `no such file or directory`,
},
`happy_path`: {
files: []string{
filepath.Join("preview-test", "composable-schema-root.zed"),
},
expectStr: `definition user {}

definition resource {
relation user: user
permission view = user
}
`,
},
`cannot_be_compiled_because_using_reserved_keyword`: {
files: []string{
filepath.Join("preview-test", "composable-schema-invalid-root.zed"),
},
expectErr: "line 4, column 12: Expected identifier, found token TokenTypeKeyword",
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
require := require.New(t)

tempOutFile := filepath.Join(t.TempDir(), "out.zed")
cmd := zedtesting.CreateTestCobraCommandWithFlagValue(t,
zedtesting.StringFlag{FlagName: "out", FlagValue: tempOutFile})

err := schemaCompileCmdFunc(cmd, tc.files)
if tc.expectErr == "" {
require.NoError(err)
tempOutString, err := os.ReadFile(tempOutFile)
require.NoError(err)
require.Equal(tc.expectStr, string(tempOutString))
} else {
require.Error(err)
require.Contains(err.Error(), tc.expectErr)
}
})
}
}
Loading