-
Notifications
You must be signed in to change notification settings - Fork 97
docs/design: Initial Validation design document #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Reference: #17 Covering background, prior implementations, and goals. Further updates to this document will outline proposals and ultimately recommendations.
…f Validation design document
…e value validation
…out generic interface
Co-authored-by: Paddy <[email protected]>
docs/design/validation.md
Outdated
Validate(context.Context, *tftypes.AttributePath, attr.Value) error | ||
} | ||
|
||
// StringValueValidator is an interface type for implementing String value validation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StringValueValidator
has great UX - no type assertions needed in the provider code!
How would this work with complex types, e.g. types.List
? Since there are as many list types as there are element types, it seems that the options are either:
- Create a single
ListValueValidator
interface, and require type assertions on the list elements inside theValidate
function, - Create multiple
ListOfStringsValueValidator
, etc, interfaces, accepting that not all cases can be covered but trying to provide helpers for the most common ones, or - Do not create strongly typed validator interfaces for complex types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my head I was thinking in the area of 2 and leaving an escape hatch interface (3). 1 could make 2 slightly easier to do, so might be beneficial.
I'm also curious though of what validation space exists here -- in the previous framework there is not much available validation for complex types (e.g. you can only validate the number of elements in a list/set/map). There have been very odd cases of needing a list type for ordering, but needing element uniqueness (like a set).
I think my focus would be covering the very common string/number cases then allowing those more odd cases to bubble up.
Really well structured design doc. One option I'd like to see considered is the request/response pattern for the parameters in the validation functions. All other things being equal (which they likely aren't), it might be good to use that pattern consistently across CRUD, plan modification, and validation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to see options outlined for:
- having a
Validate
method on the resource/provider/data source that allows for validation to be implemented inline at the resource level, instead of needing to return validator interfaces. - Using the request and response pattern for these validation interfaces.
I have concerns about the strongly typed function signatures. I think that may hurt discoverability, and I think it's going to be an absolute mess when complex types get involved. One option that's raised in #64 is to have an attr.ValueAs
helper that works like Get
, meaning we could use the same plan/config/state access as the rest of the framework and be consistent with plan modification (if it goes that route). It may be worth exploring that here, too.
I have concerns with the typed error returns and using errors in general when the rest of the framework is diagnostics. It seems to prioritize sharing information with callers, but we're the only callers, and I think the primary audience for that error information is the practitioner. I'm worried this is either going to be too restrictive for provider developers to offer a good experience, or add cognitive load on provider developers that we can avoid. I'm interested in hearing how you feel about returning diagnostics like we do everywhere else, and iterating on the interface to and design of diagnostics to try and reap some of the benefits you're identifying.
I'm having some trouble keeping track of the distinctions you're introducing here. It may be easier to track if we collapse some of the sections--for example, we may not need to talk about how something is set on a Schema and what its type/type signature is separately. I think it's okay to pick one example when showing it on the schema, and then just show alternate types/type signatures. It may reduce the number of headings and the level of nesting in the headings, which gets hard to follow. But mostly, the first time you refer to something (say, "PathValidation") it may be helpful to just note what makes it distinct from the other, similar concepts you've introduced.
I think this is great, a lot of context, and you've covered the problem space really well. I'd love to see the other solutions I mentioned considered and their benefits/tradeoffs weighed. I'd also like to see some more information about how complex types would be handled in your proposed strongly-typed helpers.
docs/design/validation.md
Outdated
|
||
This proposal would introduce no changes to `schema.Attribute`. Instead, this would require value validation declarations at the `DataSource` and `Resource` level similar to other proposed attribute validations in the [Declaring Multiple Attribute Validation for Resources](#declaring-multiple-attribute-validation-for-resources) section. | ||
|
||
This proposal makes value validation behaviors occur at a distance, meaning it is harder for provider developers to correlate the validation logic to the name/path and type information. It would also be very verbose for even moderately sized schemas with thorough value validation. The only real potential benefit to this type of value validation is that the framework implementation is very straightforward, to just go through this single list of validations instead of walking all attributes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another potential benefit is that it is the most flexible implementation, allowing incredibly complex validations to have their most straightforward implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "straightforward" (in the comment) intended to contradict "verbose" (in the text)? Without seeing a ValueValidator
I can't work out whether the "incredibly complex validations" will be verbose or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, missed this in the shuffle. I think what I'm trying to say is that, for example:
// ignore the type signature, it's a place holder
func (r computeInstanceResource) Validate(ctx context.Context, req ValidateResourceRequest, resp *ValidateResourceResponse) {
// assume we want to ensure, e.g., that no disks combine to more than one terabyte of storage, or something
totalStorage := 0
for _, disk := range plan.Disks { // not how any of this works, but pseudo-code!
totalStorage += disk.Size
}
if totalStorage >= 1024*1024*1024*1024 {
// return an error!
}
}
This is a weird thing to define a reusable helper for, and it's weirder if we throw more details on this: maybe it can only be 1tb of spinning rust disks, 512gb of SSDs, and no more than 1tb of disks total. Maybe the size of the instance comes into play. That's what I mean by "incredibly complex"--I guess a better way to phrase it would be "unlikely to be reused across resources".
To implement this declaratively, you'd need to do something like this:
// ignore the type signature, it's a place holder
func (r computeInstanceResource) Validators(ctx context.Context) []Validators {
return []Validators{computeInstanceValidator{}}
}
type computeInstanceValidator struct{}
func (r computeInstanceValidator) Validate(ctx context.Context, req ValidateResourceRequest, resp *ValidateResourceResponse) {
// assume we want to ensure, e.g., that no disks combine to more than one terabyte of storage, or something
totalStorage := 0
for _, disk := range plan.Disks { // not how any of this works, but pseudo-code!
totalStorage += disk.Size
}
if totalStorage >= 1024*1024*1024*1024 {
// return an error!
}
}
I'm arguing this is less straightforward because you need to introduce the indirection of declaring the validation logic separately from the resource, in a reusable chunk, even though you're only using it for the resource. The code is a bit more verbose, and feels a little less intuitive, imho.
docs/design/validation.md
Outdated
|
||
Implementing against that design could prove complex for the framework as they are intended to serve differing purposes. It could also be confusing for provider developers in the same way that `CustomizeDiff` was confusing where differing logical rules applied to differing attribute value and operation scenarios. | ||
|
||
##### `AttributeValidations` for Resources |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we also solve this with a resource-level Validator method, if we're pushing to the resource-level?
… should not buck the trend without good reason Also fixes up some of the example diagnostics code.
The flow of the design document will need further updates to better highlight the proposed layering and abstraction concepts.
In the future, validators may not only be for attributes.
… really the only thing that can be validated currently Terraform currently re-calls ValidateDataSourceConfig and ValidateResourceConfig during plan, not a separate RPC and not a separate logical behavior of the previous framework.
…w request/response types and split data source vs resource
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more thorough review prior to the request/response pattern additions (edit: which I see you've just added. Will do another pass if I get time later today).
docs/design/validation.md
Outdated
|
||
#### Declaring Value Validation for Attributes | ||
|
||
This section includes examples and details with `schema.Attribute` implemented as a Go structure type as it exists today. Future design considerations around creating specialized or custom attribute types may warrant switching this to an interface type with separate concrete types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above regarding implementations of custom types using the attr.Type
interface - or perhaps I'm misunderstanding the custom type design this refers to, in which case an example would be helpful.
docs/design/validation.md
Outdated
|
||
This proposal would introduce no changes to `schema.Attribute`. Instead, this would require value validation declarations at the `DataSource` and `Resource` level similar to other proposed attribute validations in the [Declaring Multiple Attribute Validation for Resources](#declaring-multiple-attribute-validation-for-resources) section. | ||
|
||
This proposal makes value validation behaviors occur at a distance, meaning it is harder for provider developers to correlate the validation logic to the name/path and type information. It would also be very verbose for even moderately sized schemas with thorough value validation. The only real potential benefit to this type of value validation is that the framework implementation is very straightforward, to just go through this single list of validations instead of walking all attributes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "straightforward" (in the comment) intended to contradict "verbose" (in the text)? Without seeing a ValueValidator
I can't work out whether the "incredibly complex validations" will be verbose or not.
docs/design/validation.md
Outdated
|
||
It could be possible to implement another proposal in this space, while also supporting this one, however this could introduce unnecessary complexity into the implementation. | ||
|
||
#### Defining Attribute Value Validation Functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, are this and the previous section to be considered orthogonal concerns: firstly, what the ValueValidator
s are attached to, and secondly, what type of Go construct (func type, interface) they are?
docs/design/validation.md
Outdated
To support passing through the provider instance, separate interface types could be introduced that include a function call with the `tfsdk.Provider` interface type: | ||
|
||
```go | ||
type StringValueValidatorWithProvider interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the flexibility of being able to add this later if needed.
docs/design/validation.md
Outdated
|
||
This could be a double edged sword for extensibility. Implementators do not need to worry about handling the attribute path in error messages that are returned to practitioners or manually adding logging around it. This does however prevent the ability to provide that additional context to the validation logic, if for example the logic warrants making decisions based on the given path or additional logging that includes the full path. In practice with validation functions in the previous framework, path based decisions are rare at best, and this framework could be opinionated against that particular pattern. | ||
|
||
##### Adding Attribute Path to Context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using context for attribute paths is a really interesting idea in general that I hadn't thought of before, and I appreciate the opportunity to add nuance to my general principle "you probably shouldn't use context.WithValue()
".
From thecontext.WithValue()
docs: "Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.".
If we consider the RPC and its handling in framework and provider code a request, then the attribute path is certainly request-scoped data that transits processes (Terraform and the plugin) and APIs (the gRPC call, the framework handler, the provider validation func).
I agree, however, that it is less discoverable, and having to make a type assertion on the attribute path isn't very nice. Interesting idea to keep in mind though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am incredibly in favor of "you probably shouldn't use context.WithValue
", with the exception of request-scoped data. What is considered to be request-scoped data I think will continue to be the source of some debate.
…elpers can pare down types if necessary.
…ifications would introduce special framework logic
…alidator, remove slice alias types, punt stronger value typing until later
…rences between attribute value and multiple attribute validation
Co-authored-by: kmoe <[email protected]>
…provider, and resource level validations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This latest revision looks great, and the proposal looks reasonable to me. There are a couple of doc suggestions here that don't really touch the design, and some bikeshedding on some naming, but overall, I'm really happy with the doc and the direction. I'm super interested in hearing @kmoe's thoughts, though, and how she thinks it will intersect with plan modification, as I think we want those designs to be similar.
…tion (#75) Reference: #17 Reference: #65 - Adds `AttributeValidator` interface type and `Validators` field to `Attributes` type. - Adds `DataSourceConfigValidator`, `DataSourceWithConfigValidators`, and `DataSourceWithValidateConfig` interface types - Adds `ProviderConfigValidator`, `ProviderWithConfigValidators`, and `ProviderWithValidateConfig` interface types - Adds `ResourceConfigValidator`, `ResourceWithConfigValidators`, and `ResourceWithValidateConfig` interface types Response diagnostics are passed through all functionality to allow overriding previous diagnostics.
I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions. |
Reference: #17
Covering background, prior implementations, and goals. Further updates to this document will outline proposals and ultimately recommendations.