feat: listvalues endpoint WIP#875
Conversation
…#691) Signed-off-by: Divy Singhvi <divysinghvi5@gmail.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
andyatmiami
left a comment
There was a problem hiding this comment.
Really appreciate you raising this PR and starting to make real some of the abstract thoughts we have had in this area.
Seeing the code really made me think through what this would look like from a golang perspective - which the API Proposal and guidance I had provided you thus far didn't really speak to (instead really focusing on the JSON structure itself).
As we are still clearly trying to harden and establish "best practices" in this repo - feel free to push back and/or question any of the comments I have made. Happy to continue discussions as necessary to ensure you understand the underlying motivation as well as agree on the outcome.
Look forward to continuing to collaborate and getting this PR eventually merged! 💯
There was a problem hiding this comment.
We should discuss if ListValues is a "good enough" prefix vs. something (sadly wayy more verbose and) specific.
- ex:
PodTemplateOptionsListValues
Hard to know right now - but if/as we add non PodTemplate data structures in the future - its possible ListValues is misleadingly vague.
There was a problem hiding this comment.
@andyatmiami Yes I agree, this will be more detailed even though it is a bit verbose.
| // parse request body | ||
| var requestBody models.ListValuesRequest | ||
| if err := a.DecodeJSON(r, &requestBody); err != nil { | ||
| a.badRequestResponse(w, r, err) | ||
| return | ||
| } |
There was a problem hiding this comment.
After decoding the request payload - we want to make sure we are validating the contents. Existing codebase strives for a clear/repeatable pattern in how this is accomplished:
Handler Pattern(e.g., secrets_handler.go, workspaces_handler.go)
// validate the request body
dataPath := field.NewPath("data")
if bodyEnvelope.Data == nil {
valErrs := field.ErrorList{field.Required(dataPath, "data is required")}
a.failedValidationResponse(w, r, errMsgRequestBodyInvalid, valErrs, nil)
return
}
valErrs = bodyEnvelope.Data.Validate(dataPath)
if len(valErrs) > 0 {
a.failedValidationResponse(w, r, errMsgRequestBodyInvalid, valErrs, nil)
return
}
Model Validate Pattern(e.g., workspaces/types_write.go)
func (w *WorkspaceCreate) Validate(prefix *field.Path) []*field.Error {
var errs []*field.Error
// validate the workspace name
namePath := prefix.Child("name")
errs = append(errs, helper.ValidateWorkspaceName(namePath, w.Name)...)
// validate nested struct
podTemplatePath := prefix.Child("podTemplate")
errs = append(errs, w.PodTemplate.Validate(podTemplatePath)...)
return errs
}
- ℹ️ As your request payload simply acts as filters - your
modeldefinitions defined in atypes.gois the proper way to be structured!
There was a problem hiding this comment.
I would follow/adhere to existing patterns when calling DecodeJSON
For instance, in secrets_handler.go (and/or most files) - you see this pattern:
// parse request body
bodyEnvelope := &SecretCreateEnvelope{}
err := a.DecodeJSON(r, bodyEnvelope)
if err != nil {
if a.IsMaxBytesError(err) {
a.requestEntityTooLargeResponse(w, r, err)
return
}
//
// TODO: handle UnmarshalTypeError and return 422,
// decode the paths which were failed to decode (included in the error)
// and also do this in the other handlers which decode json
//
a.badRequestResponse(w, r, fmt.Errorf("error decoding request body: %w", err))
return
}
Lets structure similarly in this PR.
There was a problem hiding this comment.
Done in #972 (WIP for comments below)
There was a problem hiding this comment.
This it TOTALLY MY FAULT and I humbly apologize for the oversight, but we use camelCase in our backend API - and I have no idea why I forgot this when originally drafting up the API spec 😢
The following kebab_case attributes should be moved to camelCase:
rule_effects->ruleEffectsui_hide->uiHide
| } | ||
|
|
||
| func buildImageConfigValuesWithRules(imageConfig ImageConfig, context *ListValuesContext) []ImageConfigValueWithRules { | ||
| values := []ImageConfigValueWithRules{} |
There was a problem hiding this comment.
| values := []ImageConfigValueWithRules{} | |
| // Pre-allocate to avoid repeated allocations and copies | |
| values := make([]ImageConfigValueWithRules, 0, len(imageConfig.Values)) |
| } | ||
|
|
||
| func buildPodConfigValuesWithRules(podConfig PodConfig, context *ListValuesContext) []PodConfigValueWithRules { | ||
| values := []PodConfigValueWithRules{} |
There was a problem hiding this comment.
| values := []PodConfigValueWithRules{} | |
| // Pre-allocate to avoid repeated allocations and copies | |
| values := make([]PodConfigValueWithRules, 0, len(podConfig.Values)) |
|
|
||
| type ListValuesRequest struct { | ||
| Data ListValuesRequestData `json:"data"` | ||
| } | ||
|
|
||
| type ListValuesRequestData struct { | ||
| Context *ListValuesContext `json:"context,omitempty"` | ||
| } | ||
|
|
There was a problem hiding this comment.
| type ListValuesRequest struct { | |
| Data ListValuesRequestData `json:"data"` | |
| } | |
| type ListValuesRequestData struct { | |
| Context *ListValuesContext `json:"context,omitempty"` | |
| } | |
| // REMOVE this - it duplicates Envelope | |
| // type ListValuesRequest struct { | |
| // Data ListValuesRequestData `json:"data"` | |
| // } | |
| // KEEP this - it has the "context" field | |
| type ListValuesRequest struct { | |
| Context *ListValuesContext `json:"context,omitempty"` | |
| } | |
There was a problem hiding this comment.
💡 Please note that I changed the remaining model here from ListValuesRequestData to ListValuesRequest
In api/workspacekinds_handler.go:
// Use the standard Envelope pattern wrapping ListValuesRequest
type ListValuesRequestEnvelope Envelope[*models.ListValuesRequest]
| a.failedValidationResponse(w, r, errMsgPathParamsInvalid, valErrs, nil) | ||
| return | ||
| } | ||
|
|
There was a problem hiding this comment.
We want to be consistent with the other API endpoints in notebooks-v2.
After path param validation - given we are parsing a request body - we need need to validate content-type here:
// validate the Content-Type header
if success := a.ValidateContentType(w, r, MediaTypeJson); !success {
return
}
you can see this pattern in effect (for instance) here:
| a.createdResponse(w, r, responseEnvelope, location) | ||
| } | ||
|
|
||
| // ListValuesHandler returns filtered imageConfig and podConfig options for a WorkspaceKind. |
There was a problem hiding this comment.
Based on other PR review comments I have made - I think we need to update annotations to reflect other 4xx status codes that can be returned:
// @Failure 413 {object} ErrorEnvelope "Request Entity Too Large. The request body is too large."
// @Failure 415 {object} ErrorEnvelope "Unsupported Media Type. Content-Type header is not correct."
// @Failure 422 {object} ErrorEnvelope "Unprocessable Entity. Validation error."
There was a problem hiding this comment.
Given existing pattern we are starting to form (with SecretListItem and WorkspaceListItem - how do you feel about the following naming conventions?
Use <Entity>ListItem pattern for semantic alignment:
// Consistent with SecretListItem, WorkspaceListItem
type ImageConfigValueListItem struct {
ImageConfigValue
RuleEffects RuleEffects `json:"ruleEffects"`
}
type PodConfigValueListItem struct {
PodConfigValue
RuleEffects RuleEffects `json:"ruleEffects"`
}
// Response structs
type ImageConfigListResult struct {
Default string `json:"default"`
Values []ImageConfigValueListItem `json:"values"`
}
type PodConfigListResult struct {
Default string `json:"default"`
Values []PodConfigValueListItem `json:"values"`
}
type ListValuesResponse struct {
ImageConfig ImageConfigListResult `json:"imageConfig"`
PodConfig PodConfigListResult `json:"podConfig"`
}
Stub /listvalues endpoint (#691)
Implements stub POST
/api/v1/workspacekinds/{name}/podtemplate/options/listvaluesendpoint.Changes:
rule_effects.ui_hide: falseimageConfig.idandpodConfig.idNote: Full compatibility selector rules to be implemented in follow-up work.