-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Initialize SDK generator (#2033)
* Start generator PoC * Generate nested structs * Ensure tags order * Generate basic impl * Generate empty unit tests * Generate empty validations * Handle basic validations on the top level * Extract validation template (WIP) * Invoke inner validations (WIP) * Add at least one of validation * Add comment which test should be generated * Extract print to std out method * Extract running all templates into method * Extract methods * Write generated to files * Extract example package * Add generate directive * Add package * Add TODO comments for each validation * Fix filename * Generate DTOs * Use DTOs for interface and for impl * Generate builders * Add required to field * Add SDK definitions to be closer to compile * Add packages temporarily * Add toOpts mapping placeholders * Fix identifier and random * Try to map dto -> options (WIP) * Map non-struct fields * Map struct fields (WIP) * Map struct fields (without path still) * Add integration tests placeholder * Add identifier definitions * Extract OptsField inside Operation * Fix validations * Extract IsRoot() * Handle path for validations and mappings * Simplify definition * Generate nested DTOs * Add nested validations * Add nested validations for unit tests * Extract template executors * Change mapping entries * Add README * Add generated files * Document generation model
- Loading branch information
1 parent
2d0eaeb
commit 96b47e5
Showing
20 changed files
with
1,291 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
## SDK generator PoC | ||
|
||
PoC of generating full object implementation based on object definition. | ||
|
||
### Description | ||
|
||
There is an example file ready for generation [database_role_def.go](example/database_role_def.go) which creates files: | ||
- [database_role_gen.go](example/database_role_gen.go) - SDK interface, options structs | ||
- [database_role_dto_gen.go](example/database_role_dto_gen.go) - SDK Request DTOs | ||
- [database_role_dto_builders_gen.go](example/database_role_dto_builders_gen.go) - SDK Request DTOs constructors and builder methods (this file is generated using [dto-builder-generator](../dto-builder-generator/main.go)) | ||
- [database_role_validations_gen.go](example/database_role_validations_gen.go) - options structs validations | ||
- [database_role_impl_gen.go](example/database_role_impl_gen.go) - SDK interface implementation | ||
- [database_role_gen_test.go](example/database_role_gen_test.go) - unit tests placeholders with guidance comments (at least for now) | ||
- [database_role_gen_integration_test.go](example/database_role_gen_integration_test.go) - integration test placeholder file | ||
|
||
### How it works | ||
##### Creating object generation definition | ||
|
||
To create definition for object generation: | ||
|
||
1. Create file `object_name_def.go` (like example [database_role_def.go](example/database_role_def.go) file). | ||
2. Put go generate directive at the top: `//go:generate go run ../main.go`. Remember that you may have to change the path to [main.go](main.go) file. | ||
3. Create object interface definition. | ||
4. Add key-value entry to `definitionMapping` in [main.go](main.go): | ||
- key should be created file name (for [database_role_def.go](example/database_role_def.go) example file: `"database_role_def.go"`) | ||
- value should be created definition (like for [database_role_def.go](example/database_role_def.go) example file: `DatabaseRole`) | ||
5. You are all set to run generation. | ||
|
||
##### Invoking generation | ||
|
||
To invoke example generation (with first cleaning all the generated files) run: | ||
```shell | ||
make clean-generator-poc run-generator-poc | ||
``` | ||
|
||
### Next steps | ||
##### Essentials | ||
- use DSL to build object definitions (from branch [go-builder-dsl](https://github.com/Snowflake-Labs/terraform-provider-snowflake/tree/go-builder-dsl)) - ideally leave two options of defining objects and proceed with generation based on definition provided | ||
- differentiate between different actions implementations (now only `Create` and `Alter` has been considered, `Show` on the other hand has totally different implementation) | ||
- generate `struct`s for `Show` and `ShowID` | ||
- handle arrays | ||
- handle more validation types | ||
|
||
##### Improvements | ||
- automatic names of nested `struct`s (e.g. `DatabaseRoleRename`) | ||
- check if generating with package name + invoking format removes unnecessary qualifier | ||
- consider merging templates `StructTemplate` and `OptionsTemplate` (requires moving Doc to Field) | ||
- add unit tests to this generator | ||
|
||
##### Known issues | ||
- spaces in templates (especially nested validations) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package example | ||
|
||
import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" | ||
|
||
//go:generate go run ../main.go | ||
|
||
var _ = DatabaseRole | ||
|
||
var DatabaseRole = g.NewInterface("DatabaseRoles", "DatabaseRole", "DatabaseObjectIdentifier").WithOperations( | ||
[]*g.Operation{ | ||
g.NewOperation("Create", "https://docs.snowflake.com/en/sql-reference/sql/create-database-role").WithOptsField( | ||
g.NewField("<should be updated programmatically>", "<should be updated programmatically>", nil). | ||
WithFields([]*g.Field{ | ||
g.NewField("create", "bool", map[string][]string{"ddl": {"static"}, "sql": {"CREATE"}}), | ||
g.NewField("OrReplace", "*bool", map[string][]string{"ddl": {"keyword"}, "sql": {"OR REPLACE"}}), | ||
g.NewField("databaseRole", "bool", map[string][]string{"ddl": {"static"}, "sql": {"DATABASE ROLE"}}), | ||
g.NewField("IfNotExists", "*bool", map[string][]string{"ddl": {"keyword"}, "sql": {"IF NOT EXISTS"}}), | ||
g.NewField("name", "DatabaseObjectIdentifier", map[string][]string{"ddl": {"identifier"}}).WithRequired(true), | ||
g.NewField("Comment", "*string", map[string][]string{"ddl": {"parameter", "single_quotes"}, "sql": {"COMMENT"}}), | ||
}). | ||
WithValidations([]*g.Validation{ | ||
g.NewValidation(g.ValidIdentifier, []string{"name"}), | ||
g.NewValidation(g.ConflictingFields, []string{"OrReplace", "IfNotExists"}), | ||
}), | ||
), | ||
g.NewOperation("Alter", "https://docs.snowflake.com/en/sql-reference/sql/alter-database-role").WithOptsField( | ||
g.NewField("<should be updated programmatically>", "<should be updated programmatically>", nil). | ||
WithFields([]*g.Field{ | ||
g.NewField("alter", "bool", map[string][]string{"ddl": {"static"}, "sql": {"ALTER"}}), | ||
g.NewField("databaseRole", "bool", map[string][]string{"ddl": {"static"}, "sql": {"DATABASE ROLE"}}), | ||
g.NewField("IfExists", "*bool", map[string][]string{"ddl": {"keyword"}, "sql": {"IF EXISTS"}}), | ||
g.NewField("name", "DatabaseObjectIdentifier", map[string][]string{"ddl": {"identifier"}}).WithRequired(true), | ||
g.NewField("Rename", "*DatabaseRoleRename", map[string][]string{"ddl": {"list,no_parentheses"}, "sql": {"RENAME TO"}}). | ||
WithFields([]*g.Field{ | ||
g.NewField("Name", "DatabaseObjectIdentifier", map[string][]string{"ddl": {"identifier"}}).WithRequired(true), | ||
}). | ||
WithValidations([]*g.Validation{ | ||
g.NewValidation(g.ValidIdentifier, []string{"Name"}), | ||
}), | ||
g.NewField("Set", "*DatabaseRoleSet", map[string][]string{"ddl": {"list,no_parentheses"}, "sql": {"SET"}}). | ||
WithFields([]*g.Field{ | ||
g.NewField("Comment", "string", map[string][]string{"ddl": {"parameter", "single_quotes"}, "sql": {"COMMENT"}}).WithRequired(true), | ||
g.NewField("NestedThirdLevel", "*NestedThirdLevel", map[string][]string{"ddl": {"list,no_parentheses"}, "sql": {"NESTED"}}). | ||
WithFields([]*g.Field{ | ||
g.NewField("Field", "DatabaseObjectIdentifier", map[string][]string{"ddl": {"identifier"}}).WithRequired(true), | ||
}). | ||
WithValidations([]*g.Validation{ | ||
g.NewValidation(g.AtLeastOneValueSet, []string{"Field"}), | ||
}), | ||
}), | ||
g.NewField("Unset", "*DatabaseRoleUnset", map[string][]string{"ddl": {"list,no_parentheses"}, "sql": {"UNSET"}}). | ||
WithFields([]*g.Field{ | ||
g.NewField("Comment", "bool", map[string][]string{"ddl": {"keyword"}, "sql": {"COMMENT"}}).WithRequired(true), | ||
}). | ||
WithValidations([]*g.Validation{ | ||
g.NewValidation(g.AtLeastOneValueSet, []string{"Comment"}), | ||
}), | ||
}). | ||
WithValidations([]*g.Validation{ | ||
g.NewValidation(g.ValidIdentifier, []string{"name"}), | ||
g.NewValidation(g.ExactlyOneValueSet, []string{"Rename", "Set", "Unset"}), | ||
}), | ||
), | ||
}, | ||
) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package example | ||
|
||
//go:generate go run ../../dto-builder-generator/main.go | ||
|
||
var ( | ||
_ optionsProvider[CreateDatabaseRoleOptions] = new(CreateDatabaseRoleRequest) | ||
_ optionsProvider[AlterDatabaseRoleOptions] = new(AlterDatabaseRoleRequest) | ||
) | ||
|
||
type CreateDatabaseRoleRequest struct { | ||
OrReplace *bool | ||
IfNotExists *bool | ||
name DatabaseObjectIdentifier // required | ||
Comment *string | ||
} | ||
|
||
type AlterDatabaseRoleRequest struct { | ||
IfExists *bool | ||
name DatabaseObjectIdentifier // required | ||
Rename *DatabaseRoleRenameRequest | ||
Set *DatabaseRoleSetRequest | ||
Unset *DatabaseRoleUnsetRequest | ||
} | ||
|
||
type DatabaseRoleRenameRequest struct { | ||
Name DatabaseObjectIdentifier // required | ||
} | ||
|
||
type DatabaseRoleSetRequest struct { | ||
Comment string // required | ||
NestedThirdLevel *NestedThirdLevelRequest | ||
} | ||
|
||
type NestedThirdLevelRequest struct { | ||
Field DatabaseObjectIdentifier // required | ||
} | ||
|
||
type DatabaseRoleUnsetRequest struct { | ||
Comment bool // required | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package example | ||
|
||
import "context" | ||
|
||
type DatabaseRoles interface { | ||
Create(ctx context.Context, request *CreateDatabaseRoleRequest) error | ||
Alter(ctx context.Context, request *AlterDatabaseRoleRequest) error | ||
} | ||
|
||
// CreateDatabaseRoleOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-database-role. | ||
type CreateDatabaseRoleOptions struct { | ||
create bool `ddl:"static" sql:"CREATE"` | ||
OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` | ||
databaseRole bool `ddl:"static" sql:"DATABASE ROLE"` | ||
IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` | ||
name DatabaseObjectIdentifier `ddl:"identifier"` | ||
Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` | ||
} | ||
|
||
// AlterDatabaseRoleOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-database-role. | ||
type AlterDatabaseRoleOptions struct { | ||
alter bool `ddl:"static" sql:"ALTER"` | ||
databaseRole bool `ddl:"static" sql:"DATABASE ROLE"` | ||
IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` | ||
name DatabaseObjectIdentifier `ddl:"identifier"` | ||
Rename *DatabaseRoleRename `ddl:"list,no_parentheses" sql:"RENAME TO"` | ||
Set *DatabaseRoleSet `ddl:"list,no_parentheses" sql:"SET"` | ||
Unset *DatabaseRoleUnset `ddl:"list,no_parentheses" sql:"UNSET"` | ||
} | ||
|
||
type DatabaseRoleRename struct { | ||
Name DatabaseObjectIdentifier `ddl:"identifier"` | ||
} | ||
|
||
type DatabaseRoleSet struct { | ||
Comment string `ddl:"parameter,single_quotes" sql:"COMMENT"` | ||
NestedThirdLevel *NestedThirdLevel `ddl:"list,no_parentheses" sql:"NESTED"` | ||
} | ||
|
||
type NestedThirdLevel struct { | ||
Field DatabaseObjectIdentifier `ddl:"identifier"` | ||
} | ||
|
||
type DatabaseRoleUnset struct { | ||
Comment bool `ddl:"keyword" sql:"COMMENT"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package example | ||
|
||
import "testing" | ||
|
||
func TestInt_DatabaseRoles(t *testing.T) { | ||
// TODO: fill me | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package example | ||
|
||
import "testing" | ||
|
||
func TestDatabaseRoles_Create(t *testing.T) { | ||
id := randomDatabaseObjectIdentifier(t) | ||
|
||
defaultOpts := func() *CreateDatabaseRoleOptions { | ||
return &CreateDatabaseRoleOptions{ | ||
name: id, | ||
} | ||
} | ||
// TODO: remove me | ||
_ = defaultOpts() | ||
|
||
// TODO: fill me | ||
|
||
// TODO: validate valid identifier for [opts.name] | ||
// TODO: validate conflicting fields for [opts.OrReplace opts.IfNotExists] | ||
|
||
} | ||
|
||
func TestDatabaseRoles_Alter(t *testing.T) { | ||
id := randomDatabaseObjectIdentifier(t) | ||
|
||
defaultOpts := func() *AlterDatabaseRoleOptions { | ||
return &AlterDatabaseRoleOptions{ | ||
name: id, | ||
} | ||
} | ||
// TODO: remove me | ||
_ = defaultOpts() | ||
|
||
// TODO: fill me | ||
|
||
// TODO: validate valid identifier for [opts.name] | ||
// TODO: validate exactly one field from [opts.Rename opts.Set opts.Unset] is present | ||
|
||
// TODO: validate valid identifier for [opts.Rename.Name] | ||
|
||
// TODO: validate at least one of fields [opts.Set.NestedThirdLevel.Field] set | ||
|
||
// TODO: validate at least one of fields [opts.Unset.Comment] set | ||
|
||
} |
Oops, something went wrong.