Skip to content

Commit 7d3342e

Browse files
authored
Merge pull request #2495 from keboola/fix-skip-transformation-schema-validation
fix: Skip transformation schema validation
2 parents cf7e1a0 + 5c37370 commit 7d3342e

10 files changed

Lines changed: 88 additions & 17 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ vendor/*
5151

5252
# CPU profiles
5353
/**/*.prof
54+
/.claude
5455

5556
# Certificates
5657
/ca

internal/pkg/diff/reporter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (r *Reporter) Paths() []string {
9999
}
100100

101101
func (r *Reporter) relationsDiff(remoteValue, localValue reflect.Value) ([]string, bool) {
102-
relationsType := reflect.TypeOf((*model.Relations)(nil)).Elem()
102+
relationsType := reflect.TypeFor[model.Relations]()
103103
if remoteValue.IsValid() && localValue.IsValid() && remoteValue.Type().ConvertibleTo(relationsType) && localValue.Type().ConvertibleTo(relationsType) {
104104
onlyInRemote, onlyInLocal := remoteValue.Interface().(model.Relations).Diff(localValue.Interface().(model.Relations))
105105
var out []string

internal/pkg/encoding/json/schema/schema.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ func ValidateConfig(component *keboola.Component, config *model.Config) error {
6868
if component.IsDeprecated() {
6969
return nil
7070
}
71+
// Skip components with custom schema handling
72+
if skipSchemaValidation(component.ID) {
73+
return nil
74+
}
7175
return ValidateContent(component.Schema, config.Content)
7276
}
7377

@@ -76,6 +80,10 @@ func ValidateConfigRow(component *keboola.Component, configRow *model.ConfigRow)
7680
if component.IsDeprecated() {
7781
return nil
7882
}
83+
// Skip components with custom schema handling
84+
if skipSchemaValidation(component.ID) {
85+
return nil
86+
}
7987
return ValidateContent(component.SchemaRow, configRow.Content)
8088
}
8189

@@ -249,3 +257,15 @@ func compileSchema(s []byte, savePropertyOrder bool) (*jsonschema.Schema, error)
249257

250258
return schema, nil
251259
}
260+
261+
// skipSchemaValidation returns true for components where schema validation should be skipped.
262+
func skipSchemaValidation(componentID keboola.ComponentID) bool {
263+
switch componentID {
264+
case "keboola.python-transformation-v2",
265+
"keboola.snowflake-transformation",
266+
"keboola.google-bigquery-transformation":
267+
return true
268+
default:
269+
return false
270+
}
271+
}

internal/pkg/encoding/json/schema/schema_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,55 @@ func testInvalidComponentSchema(t *testing.T, invalidSchema []byte, expectedLogs
384384
assert.Equal(t, strings.TrimLeft(expectedLogs, "\n"), logger.AllMessagesTxt())
385385
}
386386

387+
func TestValidateConfig_SkipTransformationComponents(t *testing.T) {
388+
t.Parallel()
389+
390+
// Schema that requires "firstName" field
391+
schema := getTestSchema()
392+
393+
// Content that violates the schema (missing required "firstName")
394+
invalidContent := orderedmap.FromPairs([]orderedmap.Pair{
395+
{
396+
Key: "parameters",
397+
Value: orderedmap.FromPairs([]orderedmap.Pair{
398+
{Key: "lastName", Value: "Brown"},
399+
}),
400+
},
401+
})
402+
403+
// Test that validation is skipped for transformation components
404+
transformationComponents := []keboola.ComponentID{
405+
"keboola.python-transformation-v2",
406+
"keboola.snowflake-transformation",
407+
"keboola.google-bigquery-transformation",
408+
}
409+
410+
for _, componentID := range transformationComponents {
411+
component := &keboola.Component{
412+
ComponentKey: keboola.ComponentKey{ID: componentID},
413+
Type: "transformation",
414+
Name: "Test Transformation",
415+
Schema: schema,
416+
}
417+
config := &model.Config{Content: invalidContent}
418+
419+
// Should return nil because validation is skipped for transformation components
420+
err := ValidateConfig(component, config)
421+
require.NoError(t, err, "validation should be skipped for component %s", componentID)
422+
}
423+
424+
// Verify that a regular component still gets validated
425+
regularComponent := &keboola.Component{
426+
ComponentKey: keboola.ComponentKey{ID: "keboola.ex-generic"},
427+
Type: "extractor",
428+
Name: "Generic Extractor",
429+
Schema: schema,
430+
}
431+
config := &model.Config{Content: invalidContent}
432+
err := ValidateConfig(regularComponent, config)
433+
require.Error(t, err, "validation should NOT be skipped for regular component")
434+
}
435+
387436
func TestNormalizeSchema_RequiredTrue(t *testing.T) {
388437
t.Parallel()
389438

internal/pkg/filesystem/aferofs/filesystem_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ type testCases struct {
9797
func (tc *testCases) runTests(t *testing.T) {
9898
t.Helper()
9999
// Call all Test* methods
100-
tp := reflect.TypeOf(tc)
100+
tp := reflect.TypeFor[*testCases]()
101101
prefix := `Test`
102102
for i := range tp.NumMethod() {
103103
method := tp.Method(i)

internal/pkg/filesystem/fileloader/fileloader_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type testCases struct {
5757
func (tc *testCases) runTests(t *testing.T) {
5858
t.Helper()
5959
// Call all Test* methods
60-
tp := reflect.TypeOf(tc)
60+
tp := reflect.TypeFor[*testCases]()
6161
prefix := `Test`
6262
for i := range tp.NumMethod() {
6363
method := tp.Method(i)

internal/pkg/log/debug.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func (l *debugLogger) AllMessagesTxt() string {
142142
allMessages := l.all.String()
143143
scanner := bufio.NewScanner(strings.NewReader(strings.Trim(allMessages, "\n")))
144144

145-
output := ""
145+
var output strings.Builder
146146
for scanner.Scan() {
147147
message := scanner.Text()
148148
var messageData map[string]any
@@ -161,10 +161,10 @@ func (l *debugLogger) AllMessagesTxt() string {
161161
panic(errors.New("log message is a json but does not have a \"level\" field"))
162162
}
163163

164-
output += strings.ToUpper(level) + " " + message + "\n"
164+
output.WriteString(strings.ToUpper(level) + " " + message + "\n")
165165
}
166166

167-
return output
167+
return output.String()
168168
}
169169

170170
// CompareJSONMessages checks that expected json messages appear in actual in the same order.

internal/pkg/service/cli/dialog/inputs_detail.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ func (d *inputsDetailDialog) parse(ctx context.Context, result string) (input.St
181181

182182
func (d *inputsDetailDialog) defaultValue() string {
183183
// File header - info for user
184-
fileHeader := `
184+
var fileHeader strings.Builder
185+
fileHeader.WriteString(`
185186
<!--
186187
Please complete definition of the user inputs.
187188
Edit lines below "## Input ...".
@@ -227,26 +228,26 @@ Options format:
227228
options: {"value1":"Label 1","value2":"Label 2","value3":"Label 3"}
228229
229230
Preview of steps and groups you created:
230-
`
231+
`)
231232
var defaultStepID string
232233
for _, group := range d.stepsGroups {
233-
fileHeader += fmt.Sprintf("- Group %d: %s\n", group.GroupIndex+1, group.Description)
234+
fileHeader.WriteString(fmt.Sprintf("- Group %d: %s\n", group.GroupIndex+1, group.Description))
234235
for _, step := range group.Steps {
235-
fileHeader += fmt.Sprintf(" - Step \"%s\": %s - %s\n", step.ID, step.Name, step.Description)
236+
fileHeader.WriteString(fmt.Sprintf(" - Step \"%s\": %s - %s\n", step.ID, step.Name, step.Description))
236237
if step.GroupIndex == 0 && step.StepIndex == 0 {
237238
defaultStepID = step.ID
238239
}
239240
}
240241
}
241-
fileHeader += `
242+
fileHeader.WriteString(`
242243
-->
243244
244245
245-
`
246+
`)
246247

247248
// Add definitions
248249
var lines strings.Builder
249-
lines.WriteString(fileHeader)
250+
lines.WriteString(fileHeader.String())
250251
for _, inputID := range d.inputs.Ids() {
251252
i, _ := d.inputs.Get(inputID)
252253
lines.WriteString(fmt.Sprintf("## Input \"%s\" (%s)\n", i.ID, i.Type))

pkg/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `/pkg`
22

3-
Library code that's ok to use by external applications (e.g., `/pkg/mypubliclib`). Other projects will import these libraries expecting them to work, so think twice before you put something here :-) Note that the `internal` directory is a better way to ensure your private packages are not importable because it's enforced by Go. The `/pkg` directory is still a good way to explicitly communicate that the code in that directory is safe for use by others. The [`I'll take pkg over internal`](https://travisjeffery.com/posts/2019-11-01-pkg/) blog post by Travis Jeffery provides a good overview of the `pkg` and `internal` directories and when it might make sense to use them.
3+
Library code that's ok to use by external applications (e.g., `/pkg/mypubliclib`). Other projects will import these libraries expecting them to work, so think twice before you put something here :-) Note that the `internal` directory is a better way to ensure your private packages are not importable because it's enforced by Go. The `/pkg` directory is still a good way to explicitly communicate that the code in that directory is safe for use by others. The blog post by Travis Jeffery provides a good overview of the `pkg` and `internal` directories and when it might make sense to use them.
44

55
It's also a way to group Go code in one place when your root directory contains lots of non-Go components and directories making it easier to run various Go tools (as mentioned in these talks: [`Best Practices for Industrial Programming`](https://www.youtube.com/watch?v=PTE4VJIdHPg) from GopherCon EU 2018, [GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps](https://www.youtube.com/watch?v=oL6JBUk6tj0) and [GoLab 2018 - Massimiliano Pippi - Project layout patterns in Go](https://www.youtube.com/watch?v=3gQa1LWwuzk)).
66

test/stream/bridge/keboola/keboola_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ func (ts *testState) testSlicesUpload(t *testing.T, ctx context.Context, expecta
816816

817817
// Check content of slices in the staging storage
818818
ts.logSection(t, "checking slices content in the staging storage")
819-
var allSlicesContent string
819+
var allSlicesContent strings.Builder
820820
for _, slice := range slicesList {
821821
rawReader, err := keboola.DownloadSliceReader(ctx, downloadCred, slice)
822822
require.NoError(t, err)
@@ -826,12 +826,12 @@ func (ts *testState) testSlicesUpload(t *testing.T, ctx context.Context, expecta
826826
require.NoError(t, err)
827827
sliceContent := string(sliceContentBytes)
828828
assert.Equal(t, expectations.records.count/2, strings.Count(sliceContent, "\n"))
829-
allSlicesContent += sliceContent
829+
allSlicesContent.WriteString(sliceContent)
830830
require.NoError(t, gzipReader.Close())
831831
require.NoError(t, rawReader.Close())
832832
}
833833
for i := range expectations.records.count {
834-
assert.Contains(t, allSlicesContent, fmt.Sprintf(`,"foo%d"`, expectations.records.startID+i))
834+
assert.Contains(t, allSlicesContent.String(), fmt.Sprintf(`,"foo%d"`, expectations.records.startID+i))
835835
}
836836
}
837837

0 commit comments

Comments
 (0)