diff --git a/cli/internal/specs/v0/gen/main.go b/cli/internal/specs/v0/gen/main.go index d8e7088d4f904d..5b38500a61c060 100644 --- a/cli/internal/specs/v0/gen/main.go +++ b/cli/internal/specs/v0/gen/main.go @@ -23,6 +23,7 @@ func main() { return reflect.VisibleFields(reflect.TypeOf(struct { Source specs.Source Destination specs.Destination + Transform specs.Transform }{})) } return nil diff --git a/cli/internal/specs/v0/kind.go b/cli/internal/specs/v0/kind.go index ff2560faeb5e1d..702d968008a941 100644 --- a/cli/internal/specs/v0/kind.go +++ b/cli/internal/specs/v0/kind.go @@ -13,12 +13,14 @@ type Kind int const ( KindSource Kind = iota KindDestination + KindTransform ) var ( AllKinds = [...]string{ KindSource: "source", KindDestination: "destination", + KindTransform: "transform", } ) diff --git a/cli/internal/specs/v0/schema.json b/cli/internal/specs/v0/schema.json index 3c77593c736b63..cf7f6e6dd514ec 100644 --- a/cli/internal/specs/v0/schema.json +++ b/cli/internal/specs/v0/schema.json @@ -315,6 +315,24 @@ } } } + }, + { + "if": { + "properties": { + "kind": { + "type": "string", + "const": "transform", + "description": "CloudQuery plugin kind" + } + } + }, + "then": { + "properties": { + "spec": { + "$ref": "#/$defs/Transform" + } + } + } } ], "properties": { @@ -322,7 +340,8 @@ "type": "string", "enum": [ "source", - "destination" + "destination", + "transform" ], "description": "CloudQuery plugin kind" }, @@ -333,6 +352,9 @@ }, { "$ref": "#/$defs/Destination" + }, + { + "$ref": "#/$defs/Transform" } ] } @@ -343,6 +365,19 @@ "kind", "spec" ] + }, + "Transform": { + "properties": { + "name": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ] } } } diff --git a/cli/internal/specs/v0/spec.go b/cli/internal/specs/v0/spec.go index 1639f3d4bb2bad..3eba4723e4090e 100644 --- a/cli/internal/specs/v0/spec.go +++ b/cli/internal/specs/v0/spec.go @@ -38,6 +38,8 @@ func (s *Spec) UnmarshalJSON(data []byte) error { s.Spec = new(Source) case KindDestination: s.Spec = new(Destination) + case KindTransform: + s.Spec = new(Transform) default: return fmt.Errorf("unknown kind %s", s.Kind) } @@ -55,11 +57,12 @@ func (Spec) JSONSchemaExtend(sc *jsonschema.Schema) { // delete & obtain the values source, _ := sc.Properties.Delete("Source") destination, _ := sc.Properties.Delete("Destination") + transform, _ := sc.Properties.Delete("Transform") // update `spec` property spec := sc.Properties.Value("spec") // we can use `one_of because source & destination specs are mutually exclusive based on the kind - spec.OneOf = []*jsonschema.Schema{source, destination} + spec.OneOf = []*jsonschema.Schema{source, destination, transform} sc.AllOf = []*jsonschema.Schema{ { @@ -102,6 +105,25 @@ func (Spec) JSONSchemaExtend(sc *jsonschema.Schema) { }(), }, }, + { + If: &jsonschema.Schema{ + Properties: func() *orderedmap.OrderedMap[string, *jsonschema.Schema] { + properties := jsonschema.NewProperties() + kind := *sc.Properties.Value("kind") + kind.Const = "transform" + kind.Enum = nil + properties.Set("kind", &kind) + return properties + }(), + }, + Then: &jsonschema.Schema{ + Properties: func() *orderedmap.OrderedMap[string, *jsonschema.Schema] { + properties := jsonschema.NewProperties() + properties.Set("spec", transform) + return properties + }(), + }, + }, } } diff --git a/cli/internal/specs/v0/spec_reader.go b/cli/internal/specs/v0/spec_reader.go index ed06fc1b653bd5..01627830f0f9d8 100644 --- a/cli/internal/specs/v0/spec_reader.go +++ b/cli/internal/specs/v0/spec_reader.go @@ -20,12 +20,15 @@ import ( type SpecReader struct { sourcesMap map[string]*Source destinationsMap map[string]*Destination + transformsMap map[string]*Transform sourceWarningsMap map[string]Warnings destinationWarningsMap map[string]Warnings + transformWarningsMap map[string]Warnings Sources []*Source Destinations []*Destination + Transforms []*Transform } var fileRegex = regexp.MustCompile(`\$\{file:([^}]+)\}`) @@ -96,6 +99,18 @@ func (r *SpecReader) loadSpecsFromFile(path string) error { return fmt.Errorf("failed to unmarshal file %s: %w", path, err) } switch s.Kind { + case KindTransform: + transform := s.Spec.(*Transform) + if r.transformsMap[transform.Name] != nil { + return fmt.Errorf("duplicate transform name %s", transform.Name) + } + r.transformWarningsMap[transform.Name] = transform.GetWarnings() + transform.SetDefaults() + if err := transform.Validate(); err != nil { + return fmt.Errorf("failed to validate transform %s: %w", transform.Name, err) + } + r.transformsMap[transform.Name] = transform + r.Transforms = append(r.Transforms, transform) case KindSource: source := s.Spec.(*Source) if r.sourcesMap[source.Name] != nil { @@ -261,10 +276,13 @@ func newSpecReader(paths []string) (*SpecReader, error) { reader := &SpecReader{ sourcesMap: make(map[string]*Source), destinationsMap: make(map[string]*Destination), + transformsMap: make(map[string]*Transform), Sources: make([]*Source, 0), Destinations: make([]*Destination, 0), + Transforms: make([]*Transform, 0), sourceWarningsMap: make(map[string]Warnings), destinationWarningsMap: make(map[string]Warnings), + transformWarningsMap: make(map[string]Warnings), } for _, path := range paths { file, err := os.Open(path) diff --git a/cli/internal/specs/v0/transform.go b/cli/internal/specs/v0/transform.go new file mode 100644 index 00000000000000..db32bee61b01c6 --- /dev/null +++ b/cli/internal/specs/v0/transform.go @@ -0,0 +1,22 @@ +package specs + +import "errors" + +type Transform struct { + Name string `json:"name" jsonschema:"required,minLength=1"` +} + +func (*Transform) GetWarnings() Warnings { + warnings := make(map[string]string) + return warnings +} + +func (*Transform) SetDefaults() {} + +func (t *Transform) Validate() error { + if t.Name == "" { + return errors.New("name is required") + } + + return nil +}