Skip to content

Commit

Permalink
feat: load root cause validation errors into MVS overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
chase-crumbaugh committed Aug 16, 2024
1 parent c3a7a0f commit 21c1e05
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 18 deletions.
4 changes: 2 additions & 2 deletions cmd/generate/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var genUsageSnippetCmd = &model.ExecutableCommand[GenerateUsageSnippetFlags]{
The following languages are currently supported:
- %s
You can generate usage snippets by OperationID or by Namespace. By default this command will write to stdout.
You can generate usage snippets by AffectedOperationIDs or by Namespace. By default this command will write to stdout.
You can also select to write to a file or write to a formatted output directory.
`, strings.Join(usagegen.SupportedLanguagesUsageSnippets, "\n - ")),
Expand All @@ -48,7 +48,7 @@ You can also select to write to a file or write to a formatted output directory.
flag.StringFlag{
Name: "operation-id",
Shorthand: "i",
Description: "The OperationID to generate usage snippet for",
Description: "The AffectedOperationIDs to generate usage snippet for",
},
flag.StringFlag{
Name: "namespace",
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,5 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
oras.land/oras-go/v2 v2.5.0 // indirect
)

replace github.com/speakeasy-api/openapi-generation/v2 => ../openapi-generation
28 changes: 18 additions & 10 deletions internal/run/minimumViableSpec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package run

import (
"context"
"errors"
"fmt"
"github.com/speakeasy-api/openapi-generation/v2/pkg/errors"
"github.com/speakeasy-api/sdk-gen-config/workflow"
"github.com/speakeasy-api/speakeasy-core/openapi"
"github.com/speakeasy-api/speakeasy/internal/charm/styles"
Expand All @@ -15,23 +15,31 @@ import (
"path/filepath"
)

func (w *Workflow) retryWithMinimumViableSpec(ctx context.Context, parentStep *workflowTracking.WorkflowStep, sourceID, targetID string, viableOperations []string) (string, *SourceResult, error) {
func (w *Workflow) retryWithMinimumViableSpec(ctx context.Context, parentStep *workflowTracking.WorkflowStep, sourceID, targetID string, vErrs []error) (string, *SourceResult, error) {
invalidOperationToErr := make(map[string]error)
for _, err := range vErrs {
vErr := errors.GetValidationErr(err)
for _, op := range vErr.AffectedOperationIDs {
invalidOperationToErr[op] = err // TODO: support multiple errors per operation?
}
}

substep := parentStep.NewSubstep("Retrying with minimum viable document")
source := w.workflow.Sources[sourceID]
baseLocation := source.Inputs[0].Location
workingDir := workflow.GetTempDir()

// This is intended to only be used from quickstart, we must assume a singular input document
if len(source.Inputs)+len(source.Overlays) > 1 {
return "", nil, errors.New("multiple inputs are not supported for minimum viable spec")
return "", nil, fmt.Errorf("multiple inputs are not supported for minimum viable spec")
}

tempBase := fmt.Sprintf("downloaded_%s%s", randStringBytes(10), filepath.Ext(baseLocation))

if source.Inputs[0].IsRemote() {
outResolved, err := download.ResolveRemoteDocument(ctx, source.Inputs[0], tempBase)
if err != nil {
return "", nil, err
return "", nil, fmt.Errorf("failed to download remote document: %w", err)
}

baseLocation = outResolved
Expand All @@ -40,7 +48,7 @@ func (w *Workflow) retryWithMinimumViableSpec(ctx context.Context, parentStep *w
overlayOut := filepath.Join(workingDir, fmt.Sprintf("mvs_overlay_%s.yaml", randStringBytes(10)))
overlayFile, err := os.Create(overlayOut)
if err != nil {
return "", nil, err
return "", nil, fmt.Errorf("failed to create overlay file: %w", err)
}
defer overlayFile.Close()

Expand All @@ -56,24 +64,24 @@ func (w *Workflow) retryWithMinimumViableSpec(ctx context.Context, parentStep *w

_, _, model, err := openapi.LoadDocument(ctx, source.Inputs[0].Location)
if err != nil {
return "", nil, err
return "", nil, fmt.Errorf("failed to load document: %w", err)
}

overlay := transform.BuildFilterOperationsOverlay(model, true, viableOperations)
overlay := transform.BuildRemoveInvalidOperationsOverlay(model, invalidOperationToErr)
if err = modifications.UpsertOverlay(&source, overlay); err != nil {
return "", nil, err
return "", nil, fmt.Errorf("failed to upsert overlay: %w", err)
}

w.workflow.Sources[sourceID] = source

sourcePath, sourceRes, err := w.RunSource(ctx, substep, sourceID, targetID)
if err != nil {
failedRetry = true
return "", nil, err
return "", nil, fmt.Errorf("failed to re-run source: %w", err)
}

if err := workflow.Save(w.ProjectDir, &w.workflow); err != nil {
return "", nil, err
return "", nil, fmt.Errorf("failed to save workflow: %w", err)
}

return sourcePath, sourceRes, err
Expand Down
2 changes: 1 addition & 1 deletion internal/run/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (w *Workflow) runTarget(ctx context.Context, target string) (*SourceResult,
*cliEvent.GenerateNumberOfOperationsIgnored = int64(len(sourceRes.LintResult.InvalidOperations))
}

retriedPath, retriedRes, retriedErr := w.retryWithMinimumViableSpec(ctx, rootStep, t.Source, target, sourceRes.LintResult.ValidOperations)
retriedPath, retriedRes, retriedErr := w.retryWithMinimumViableSpec(ctx, rootStep, t.Source, target, sourceRes.LintResult.AllErrors)
if retriedErr != nil {
log.From(ctx).Errorf("Failed to retry with minimum viable spec: %s", retriedErr)
// return the original error
Expand Down
4 changes: 3 additions & 1 deletion internal/run/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Workflow struct {
SkipLinting bool
SkipChangeReport bool
SkipSnapshot bool
SkipCleanup bool
SkipCleanup bool
FromQuickstart bool
RepoSubDirs map[string]string
InstallationURLs map[string]string
Expand Down Expand Up @@ -98,6 +98,8 @@ func NewWorkflow(
opt(w)
}

w.FromQuickstart = true //TODO!

w.RootStep = workflowTracking.NewWorkflowStep(w.workflowName, log.From(ctx), nil)

return w, nil
Expand Down
2 changes: 1 addition & 1 deletion internal/studio/modifications/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const (

func UpsertOverlay(source *workflow.Source, o overlay.Overlay) error {
// Open the file with read and write permissions
overlayFile, err := os.OpenFile(OverlayPath, os.O_RDWR|os.O_CREATE, 0644)
overlayFile, err := os.OpenFile(OverlayPath, os.O_RDWR, 0644)
var baseOverlay *overlay.Overlay

// If the file exists, load the current overlay
Expand Down
17 changes: 14 additions & 3 deletions internal/transform/filterOperations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/speakeasy-api/openapi-overlay/pkg/overlay"
"github.com/speakeasy-api/speakeasy-core/openapi"
"github.com/speakeasy-api/speakeasy-core/suggestions"
"golang.org/x/exp/maps"
"gopkg.in/yaml.v3"
"io"
"slices"
Expand All @@ -20,7 +21,7 @@ func FilterOperations(ctx context.Context, schemaPath string, includeOps []strin
return err
}

overlay := BuildFilterOperationsOverlay(model, include, includeOps)
overlay := BuildFilterOperationsOverlay(model, include, includeOps, nil)

root := model.Index.GetRootNode()
if err := overlay.ApplyTo(root); err != nil {
Expand All @@ -32,7 +33,11 @@ func FilterOperations(ctx context.Context, schemaPath string, includeOps []strin
return enc.Encode(root)
}

func BuildFilterOperationsOverlay(model *libopenapi.DocumentModel[v3.Document], include bool, ops []string) overlay.Overlay {
func BuildRemoveInvalidOperationsOverlay(model *libopenapi.DocumentModel[v3.Document], opToErr map[string]error) overlay.Overlay {
return BuildFilterOperationsOverlay(model, false, maps.Keys(opToErr), opToErr)
}

func BuildFilterOperationsOverlay(model *libopenapi.DocumentModel[v3.Document], include bool, ops []string, opToErr map[string]error) overlay.Overlay {
actionFn := func(method, path string, operation *v3.Operation) (map[string]string, *overlay.Action, *suggestions.ModificationExtension) {
operationID := operation.OperationId

Expand All @@ -57,9 +62,15 @@ func BuildFilterOperationsOverlay(model *libopenapi.DocumentModel[v3.Document],
Target: target,
Remove: true,
}

before := "<invalid_operation>"
if err, ok := opToErr[operationID]; ok {
before = err.Error()
}

return nil, &action, &suggestions.ModificationExtension{
Type: suggestions.ModificationTypeRemoveInvalid,
Before: "<invalid_operation>", // TODO: insert the actual error message here
Before: before,
After: "<removed>",
}
}
Expand Down

0 comments on commit 21c1e05

Please sign in to comment.