From b95f0a2e2c58d90408ca02d91826e8a87215551b Mon Sep 17 00:00:00 2001 From: David Alberto Adler Date: Mon, 1 Jul 2024 12:59:51 +0100 Subject: [PATCH] fix: marshalling of fallback code samples --- workflow/source.go | 66 +++++++++++++++++------ workflow/source_test.go | 117 +++++++++++++++++++++++++++------------- 2 files changed, 130 insertions(+), 53 deletions(-) diff --git a/workflow/source.go b/workflow/source.go index 4e14836..303f316 100644 --- a/workflow/source.go +++ b/workflow/source.go @@ -27,15 +27,29 @@ type Overlay struct { } func (o *Overlay) UnmarshalYAML(unmarshal func(interface{}) error) error { - var doc Document - if err := unmarshal(&doc); err == nil { - o.Document = &doc + // Overlay is flat, so we need to unmarshal it into a map to determine if it's a document or fallbackCodeSamples + var overlayMap map[string]interface{} + if err := unmarshal(&overlayMap); err != nil { + return err + } + + if _, ok := overlayMap["fallbackCodeSamplesLanguage"]; ok { + var fallbackCodeSamples FallbackCodeSamples + if err := unmarshal(&fallbackCodeSamples); err != nil { + return err + } + + o.FallbackCodeSamples = &fallbackCodeSamples return nil } - var fallback FallbackCodeSamples - if err := unmarshal(&fallback); err == nil { - o.FallbackCodeSamples = &fallback + if _, ok := overlayMap["location"]; ok { + var document Document + if err := unmarshal(&document); err != nil { + return err + } + + o.Document = &document return nil } @@ -95,16 +109,8 @@ func (s Source) Validate() error { } for i, overlay := range s.Overlays { - if overlay.Document != nil { - if err := overlay.Document.Validate(); err != nil { - return fmt.Errorf("failed to validate overlay document %d: %w", i, err) - } - } - - if overlay.FallbackCodeSamples != nil { - if overlay.FallbackCodeSamples.FallbackCodeSamplesLanguage == "" { - return fmt.Errorf("fallbackCodeSamplesLanguage is required") - } + if err := overlay.Validate(); err != nil { + return fmt.Errorf("failed to validate overlay %d: %w", i, err) } } @@ -200,6 +206,34 @@ func (d Document) IsSpeakeasyRegistry() bool { return strings.Contains(d.Location, "registry.speakeasyapi.dev") } +func (f FallbackCodeSamples) Validate() error { + if f.FallbackCodeSamplesLanguage == "" { + return fmt.Errorf("fallbackCodeSamplesLanguage is required") + } + + return nil +} + +func (o Overlay) Validate() error { + if o.Document != nil { + if err := o.Document.Validate(); err != nil { + return fmt.Errorf("failed to validate overlay document: %w", err) + } + } + + if o.FallbackCodeSamples != nil { + if err := o.FallbackCodeSamples.Validate(); err != nil { + return fmt.Errorf("failed to validate overlay fallbackCodeSamples: %w", err) + } + } + + if o.Document == nil && o.FallbackCodeSamples == nil { + return fmt.Errorf("overlay must have either a document or fallbackCodeSamples") + } + + return nil +} + // Parse the location to extract the namespace ID, namespace name, and reference // The location should be in the format registry.speakeasyapi.dev/org/workspace/name[:tag|@sha256:digest] func ParseSpeakeasyRegistryReference(location string) *SpeakeasyRegistryDocument { diff --git a/workflow/source_test.go b/workflow/source_test.go index 44fc020..daa07de 100644 --- a/workflow/source_test.go +++ b/workflow/source_test.go @@ -12,6 +12,7 @@ import ( "github.com/speakeasy-api/sdk-gen-config/workflow" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestSource_Validate(t *testing.T) { @@ -111,13 +112,9 @@ func TestSource_Validate(t *testing.T) { }, }, }, - Overlays: []workflow.Document{ - { - Location: "overlay.yaml", - }, - { - Location: "http://example.com/overlay.yaml", - }, + Overlays: []workflow.Overlay{ + {Document: &workflow.Document{Location: "overlay.yaml"}}, + {Document: &workflow.Document{Location: "http://example.com/overlay.yaml"}}, }, Output: pointer.ToString("openapi.yaml"), }, @@ -133,13 +130,9 @@ func TestSource_Validate(t *testing.T) { Location: "openapi.yaml", }, }, - Overlays: []workflow.Document{ - { - Location: "overlay.yaml", - }, - { - Location: "overlay.yaml", - }, + Overlays: []workflow.Overlay{ + {Document: &workflow.Document{Location: "overlay.yaml"}}, + {Document: &workflow.Document{Location: "overlay.yaml"}}, }, Output: pointer.ToString("openapi.json"), }, @@ -158,10 +151,8 @@ func TestSource_Validate(t *testing.T) { Location: "openapi.yaml", }, }, - Overlays: []workflow.Document{ - { - Location: "overlay.yaml", - }, + Overlays: []workflow.Overlay{ + {Document: &workflow.Document{Location: "overlay.yaml"}}, }, Output: pointer.ToString("openapi.json"), }, @@ -173,10 +164,8 @@ func TestSource_Validate(t *testing.T) { args: args{ source: workflow.Source{ Inputs: []workflow.Document{}, - Overlays: []workflow.Document{ - { - Location: "overlay.yaml", - }, + Overlays: []workflow.Overlay{ + {Document: &workflow.Document{Location: "overlay.yaml"}}, }, }, }, @@ -236,12 +225,45 @@ func TestSource_Validate(t *testing.T) { Location: "openapi.yaml", }, }, - Overlays: []workflow.Document{ - {}, + Overlays: []workflow.Overlay{{ + Document: &workflow.Document{}, + }}, + }, + }, + wantErr: fmt.Errorf("failed to validate overlay 0: failed to validate overlay document: location is required"), + }, + { + name: "overlay fails with no fallbackCodeSamplesLanguage", + args: args{ + source: workflow.Source{ + Inputs: []workflow.Document{ + { + Location: "openapi.yaml", + }, }, + Overlays: []workflow.Overlay{{ + FallbackCodeSamples: &workflow.FallbackCodeSamples{}, + }}, + }, + }, + wantErr: fmt.Errorf("failed to validate overlay 0: failed to validate overlay fallbackCodeSamples: fallbackCodeSamplesLanguage is required"), + }, + { + name: "overlay with fallbackCodeSamplesLanguage", + args: args{ + source: workflow.Source{ + Inputs: []workflow.Document{ + { + Location: "openapi.yaml", + }, + }, + Overlays: []workflow.Overlay{{ + FallbackCodeSamples: &workflow.FallbackCodeSamples{ + FallbackCodeSamplesLanguage: "python", + }, + }}, }, }, - wantErr: fmt.Errorf("failed to validate overlay 0: location is required"), }, { name: "registry success", @@ -306,6 +328,29 @@ func TestSource_Validate(t *testing.T) { } else { assert.NoError(t, err) } + + if tt.wantErr == nil { + // Marshal to yaml + w := workflow.Workflow{ + Version: workflow.WorkflowVersion, + SpeakeasyVersion: "latest", + Sources: map[string]workflow.Source{ + "source": tt.args.source, + }, + } + data, err := yaml.Marshal(w) + require.NoError(t, err) + + // Unmarshal yaml + var w2 workflow.Workflow + err = yaml.Unmarshal(data, &w2) + require.NoError(t, err) + + // Validate + err = w2.Validate([]string{}) + + assert.NoError(t, err) + } }) } } @@ -404,10 +449,8 @@ func TestSource_GetOutputLocation(t *testing.T) { Location: "openapi.yaml", }, }, - Overlays: []workflow.Document{ - { - Location: "overlay.yaml", - }, + Overlays: []workflow.Overlay{ + {Document: &workflow.Document{Location: "overlay.yaml"}}, }, Output: pointer.ToString("processed.yaml"), }, @@ -423,10 +466,8 @@ func TestSource_GetOutputLocation(t *testing.T) { Location: "openapi.yaml", }, }, - Overlays: []workflow.Document{ - { - Location: "overlay.yaml", - }, + Overlays: []workflow.Overlay{ + {Document: &workflow.Document{Location: "overlay.yaml"}}, }, }, }, @@ -553,10 +594,12 @@ func createLocalFiles(s workflow.Source) (string, error) { } for _, overlay := range s.Overlays { - _, err := url.ParseRequestURI(overlay.Location) - if err != nil { - if err := createEmptyFile(filepath.Join(tmpDir, overlay.Location)); err != nil { - return "", err + if overlay.Document != nil { + _, err := url.ParseRequestURI(overlay.Document.Location) + if err != nil { + if err := createEmptyFile(filepath.Join(tmpDir, overlay.Document.Location)); err != nil { + return "", err + } } } }