Skip to content
Prev Previous commit
Merge remote-tracking branch 'origin/main' into cj/2229/decouple-ws-d…
…eadline-from-ttl-again
  • Loading branch information
johnstcn committed Jun 14, 2022
commit feef82388e90a05cfec10d562ea50392c50c32bd
78 changes: 78 additions & 0 deletions coderd/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,84 @@ func validWorkspaceSchedule(s *string, min time.Duration) (sql.NullString, error
}, nil
}

// workspaceSearchQuery takes a query string and returns the workspace filter.
// It also can return the list of validation errors to return to the api.
func workspaceSearchQuery(query string) (database.GetWorkspacesWithFilterParams, []httpapi.Error) {
searchParams := make(url.Values)
if query == "" {
// No filter
return database.GetWorkspacesWithFilterParams{}, nil
}
// Because we do this in 2 passes, we want to maintain quotes on the first
// pass.Further splitting occurs on the second pass and quotes will be
// dropped.
elements := splitQueryParameterByDelimiter(query, ' ', true)
for _, element := range elements {
parts := splitQueryParameterByDelimiter(element, ':', false)
switch len(parts) {
case 1:
// No key:value pair. It is a workspace name, and maybe includes an owner
parts = splitQueryParameterByDelimiter(element, '/', false)
switch len(parts) {
case 1:
searchParams.Set("name", parts[0])
case 2:
searchParams.Set("owner", parts[0])
searchParams.Set("name", parts[1])
default:
return database.GetWorkspacesWithFilterParams{}, []httpapi.Error{
{Field: "q", Detail: fmt.Sprintf("Query element %q can only contain 1 '/'", element)},
}
}
case 2:
searchParams.Set(parts[0], parts[1])
default:
return database.GetWorkspacesWithFilterParams{}, []httpapi.Error{
{Field: "q", Detail: fmt.Sprintf("Query element %q can only contain 1 ':'", element)},
}
}
}

// Using the query param parser here just returns consistent errors with
// other parsing.
parser := httpapi.NewQueryParamParser()
filter := database.GetWorkspacesWithFilterParams{
Deleted: false,
OwnerUsername: parser.String(searchParams, "", "owner"),
TemplateName: parser.String(searchParams, "", "template"),
Name: parser.String(searchParams, "", "name"),
}

return filter, parser.Errors
}

// splitQueryParameterByDelimiter takes a query string and splits it into the individual elements
// of the query. Each element is separated by a delimiter. All quoted strings are
// kept as a single element.
//
// Although all our names cannot have spaces, that is a validation error.
// We should still parse the quoted string as a single value so that validation
// can properly fail on the space. If we do not, a value of `template:"my name"`
// will search `template:"my name:name"`, which produces an empty list instead of
// an error.
// nolint:revive
func splitQueryParameterByDelimiter(query string, delimiter rune, maintainQuotes bool) []string {
quoted := false
parts := strings.FieldsFunc(query, func(r rune) bool {
if r == '"' {
quoted = !quoted
}
return !quoted && r == delimiter
})
if !maintainQuotes {
for i, part := range parts {
parts[i] = strings.Trim(part, "\"")
}
}

return parts
}

func min(x, y int64) int64 {
if x < y {
return x
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.