drone/registry/app/api/handler/utils/jsonform.go

95 lines
2.7 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"strings"
"github.com/rs/zerolog/log"
)
// fillFromForm uses reflection to fill fields of 'data' from r.FormValue.
// It looks for a 'json' struct tag and uses that as the key in FormValue.
func FillFromForm(r *http.Request, data interface{}) error {
// Make sure form data is parsed
if err := r.ParseForm(); err != nil {
return err
}
if err := r.ParseMultipartForm(32 << 22); err != nil {
return err
}
v := reflect.ValueOf(data).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "" {
// Skip fields with no json tag
continue
}
// The tag might be `json:"author,omitempty"`, so split on comma to isolate the key
tagParts := strings.Split(jsonTag, ",")
key := tagParts[0]
// Single-value retrieval:
formVal := r.FormValue(key)
// Now decide how to set based on the field type
fieldVal := v.Field(i)
switch fieldVal.Kind() { // nolint:exhaustive
case reflect.String:
// Just set the string
fieldVal.SetString(formVal)
case reflect.Slice:
// Check if it's a slice of strings
if fieldVal.Type().Elem().Kind() == reflect.String {
// For slices, let's fetch all form values under that key
// e.g. name=foo&name=bar => r.Form["name"] = []string{"foo","bar"}
values := r.Form[key]
fieldVal.Set(reflect.ValueOf(values))
}
case reflect.Map:
// Check if it's a map[string]string
if fieldVal.Type().Key().Kind() == reflect.String &&
fieldVal.Type().Elem().Kind() == reflect.String {
// We'll assume the form value is a JSON string. For example:
// extra={"foo":"bar","something":"else"}
if formVal == "" {
// If nothing is provided, just set an empty map
fieldVal.Set(reflect.ValueOf(map[string]string{}))
} else {
m := make(map[string]string)
if err := json.Unmarshal([]byte(formVal), &m); err != nil {
return fmt.Errorf("cannot unmarshal map from key %q: %w", key, err)
}
fieldVal.Set(reflect.ValueOf(m))
}
}
default:
log.Warn().Ctx(r.Context()).Msgf("Unsupported field type %v", fieldVal.Kind())
}
}
return nil
}