diff --git a/pkg/composableschemadsl/compiler/importer.go b/pkg/composableschemadsl/compiler/importer.go index 37c51a927e..7313e5c789 100644 --- a/pkg/composableschemadsl/compiler/importer.go +++ b/pkg/composableschemadsl/compiler/importer.go @@ -16,7 +16,7 @@ type importContext struct { pathSegments []string sourceFolder string names *mapz.Set[string] - visitedFiles *mapz.Set[string] + visitedFiles *mapz.Set[string] } const SchemaFileSuffix = ".zed" @@ -25,35 +25,44 @@ func importFile(importContext importContext) (*CompiledSchema, error) { relativeFilepath := constructFilePath(importContext.pathSegments) filePath := path.Join(importContext.sourceFolder, relativeFilepath) - currentVisitedFiles := importContext.visitedFiles.Copy() - if ok := currentVisitedFiles.Add(filePath); !ok { - return nil, fmt.Errorf("circular import: file %s has already been visited", filePath) - } - newSourceFolder := filepath.Dir(filePath) - var schemaBytes []byte + if ok := importContext.visitedFiles.Add(filePath); !ok { + // If the file has already been visited, we short-circuit the import process + // by not reading the schema file in and compiling a schema with an empty string. + // This prevents duplicate definitions from ending up in the output, as well + // as preventing circular imports. + log.Warn().Str("filepath", filePath).Msg("possible circular import: file %s has already been visited") + return compileImpl(InputSchema{ + Source: input.Source(filePath), + SchemaString: "", + }, + compilationContext{ + existingNames: importContext.names, + visitedFiles: importContext.visitedFiles, + }, + AllowUnprefixedObjectType(), + SourceFolder(newSourceFolder), + ) + } + schemaBytes, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read schema file: %w", err) } log.Trace().Str("schema", string(schemaBytes)).Str("file", filePath).Msg("read schema from file") - compiled, err := compileImpl(InputSchema{ + return compileImpl(InputSchema{ Source: input.Source(filePath), SchemaString: string(schemaBytes), }, compilationContext{ existingNames: importContext.names, - visitedFiles: currentVisitedFiles, + visitedFiles: importContext.visitedFiles, }, AllowUnprefixedObjectType(), SourceFolder(newSourceFolder), ) - if err != nil { - return nil, err - } - return compiled, nil } func constructFilePath(segments []string) string {