Skip to content

Commit

Permalink
feat: add local schema diff
Browse files Browse the repository at this point in the history
If no remote URI is specified for schema diff it will attempt to diff the local project

closes #2174
  • Loading branch information
stuartwdouglas committed Jul 28, 2024
1 parent 48bc91d commit 446e20e
Showing 1 changed file with 84 additions and 3 deletions.
87 changes: 84 additions & 3 deletions cmd/ftl/cmd_schema_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,66 @@ package main

import (
"context"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"

"connectrpc.com/connect"
"github.com/alecthomas/chroma/v2/quick"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"github.com/mattn/go-isatty"
"google.golang.org/protobuf/proto"

ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema"
"github.com/TBD54566975/ftl/backend/schema"
"github.com/TBD54566975/ftl/common/projectconfig"
"github.com/TBD54566975/ftl/internal/log"
"github.com/TBD54566975/ftl/internal/rpc"
)

type schemaDiffCmd struct {
OtherEndpoint url.URL `arg:"" help:"Other endpoint URL to compare against."`
OtherEndpoint url.URL `arg:"" help:"Other endpoint URL to compare against. If this is not specified then ftl will perform a diff against the local schema." optional:""`
Color bool `help:"Enable colored output regardless of TTY."`
}

func (d *schemaDiffCmd) Run(ctx context.Context, currentURL *url.URL) error {
other, err := schemaForURL(ctx, d.OtherEndpoint)
func (d *schemaDiffCmd) Run(ctx context.Context, currentURL *url.URL, projConfig projectconfig.Config) error {
var other *schema.Schema
var err error
sameModulesOnly := false
if d.OtherEndpoint.String() == "" {
sameModulesOnly = true
other, err = localSchema(projConfig)
} else {
other, err = schemaForURL(ctx, d.OtherEndpoint)
}

if err != nil {
return fmt.Errorf("failed to get other schema: %w", err)
}
current, err := schemaForURL(ctx, *currentURL)
if err != nil {
return fmt.Errorf("failed to get current schema: %w", err)
}
if sameModulesOnly {
tempModules := current.Modules
current.Modules = []*schema.Module{}
moduleMap := map[string]*schema.Module{}
for _, i := range tempModules {
moduleMap[i.Name] = i
}
for _, i := range other.Modules {
println(i.Name)
if mod, ok := moduleMap[i.Name]; ok {
current.Modules = append(current.Modules, mod)
}
}
}

edits := myers.ComputeEdits(span.URIFromPath(""), other.String(), current.String())
diff := fmt.Sprint(gotextdiff.ToUnified(d.OtherEndpoint.String(), currentURL.String(), other.String(), edits))
Expand All @@ -57,6 +84,60 @@ func (d *schemaDiffCmd) Run(ctx context.Context, currentURL *url.URL) error {
return nil
}

func localSchema(moduleDirs projectconfig.Config) (*schema.Schema, error) {
pb := &schemapb.Schema{}
found := false
tried := ""
moduleFound := false
moduleTried := ""
for _, modulePaths := range moduleDirs.AbsModuleDirs() {
entries, err := os.ReadDir(modulePaths)
if err != nil {
return nil, fmt.Errorf("failed to list directory %w", err)
}
for _, module := range entries {
if !module.IsDir() {
continue
}
path := filepath.Join(modulePaths, module.Name(), ".ftl", "schema.pb")
mod, err := loadProtoSchema(path)
if err != nil {
moduleTried += fmt.Sprintf("failed to read schema file %s; did you run ftl build?", path)
} else {
moduleFound = true
found = true
pb.Modules = append(pb.Modules, mod)
}
}
}
if !moduleFound {
tried += moduleTried
}

if !found {
return nil, errors.New(tried)
}

s, err := schema.FromProto(pb)
if err != nil {
return nil, fmt.Errorf("failed to parse local schema: %w", err)
}
return s, nil
}

func loadProtoSchema(file string) (*schemapb.Module, error) {
content, err := os.ReadFile(file)
if err != nil {
return nil, err

Check failure on line 131 in cmd/ftl/cmd_schema_diff.go

View workflow job for this annotation

GitHub Actions / Lint

error returned from external package is unwrapped: sig: func os.ReadFile(name string) ([]byte, error) (wrapcheck)
}
module := &schemapb.Module{}
err = proto.Unmarshal(content, module)
if err != nil {
return nil, err

Check failure on line 136 in cmd/ftl/cmd_schema_diff.go

View workflow job for this annotation

GitHub Actions / Lint

error returned from external package is unwrapped: sig: func google.golang.org/protobuf/proto.Unmarshal(b []byte, m google.golang.org/protobuf/reflect/protoreflect.ProtoMessage) error (wrapcheck)
}
return module, nil
}

func schemaForURL(ctx context.Context, url url.URL) (*schema.Schema, error) {
client := rpc.Dial(ftlv1connect.NewControllerServiceClient, url.String(), log.Error)
resp, err := client.PullSchema(ctx, connect.NewRequest(&ftlv1.PullSchemaRequest{}))
Expand Down

0 comments on commit 446e20e

Please sign in to comment.