2022-05-10 22:31:17 +02:00
|
|
|
package server
|
2022-03-22 09:21:55 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2022-05-04 12:23:53 +02:00
|
|
|
"strconv"
|
2022-03-22 09:21:55 +01:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"forge.cadoles.com/wpetit/formidable/internal/jsonpointer"
|
|
|
|
"forge.cadoles.com/wpetit/formidable/internal/server/template"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/santhosh-tekuri/jsonschema/v5"
|
|
|
|
)
|
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
func (s *Server) serveFormReq(w http.ResponseWriter, r *http.Request) {
|
|
|
|
data := &template.FormItemData{
|
|
|
|
Parent: nil,
|
|
|
|
Schema: s.schema,
|
|
|
|
Property: "",
|
|
|
|
Defaults: s.defaults,
|
|
|
|
Values: s.values,
|
|
|
|
}
|
2022-03-22 09:21:55 +01:00
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
if err := s.schema.Validate(data.Values); err != nil {
|
|
|
|
validationErr, ok := err.(*jsonschema.ValidationError)
|
|
|
|
if !ok {
|
|
|
|
panic(errors.Wrap(err, "could not validate values"))
|
2022-03-22 09:21:55 +01:00
|
|
|
}
|
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
data.Error = validationErr
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := template.Exec("index.html.tmpl", w, data); err != nil {
|
|
|
|
panic(errors.WithStack(err))
|
2022-03-22 09:21:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
func (s *Server) handleFormReq(w http.ResponseWriter, r *http.Request) {
|
|
|
|
data := &template.FormItemData{
|
|
|
|
Parent: nil,
|
|
|
|
Schema: s.schema,
|
|
|
|
Property: "",
|
|
|
|
Defaults: s.defaults,
|
|
|
|
Values: s.values,
|
|
|
|
}
|
2022-03-22 09:21:55 +01:00
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
var values interface{}
|
2022-03-22 09:21:55 +01:00
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
panic(errors.WithStack(err))
|
|
|
|
} else {
|
2022-09-27 22:20:44 +02:00
|
|
|
values, err = handleForm(r.Form, s.schema, s.values)
|
2022-05-10 22:31:17 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(errors.WithStack(err))
|
2022-03-22 09:21:55 +01:00
|
|
|
}
|
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
data.Values = values
|
|
|
|
}
|
2022-03-22 09:21:55 +01:00
|
|
|
|
2022-09-27 22:20:44 +02:00
|
|
|
if err := s.schema.Validate(values); err != nil {
|
2022-05-10 22:31:17 +02:00
|
|
|
validationErr, ok := err.(*jsonschema.ValidationError)
|
|
|
|
if !ok {
|
|
|
|
panic(errors.Wrap(err, "could not validate values"))
|
2022-03-22 09:21:55 +01:00
|
|
|
}
|
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
data.Error = validationErr
|
|
|
|
}
|
2022-05-04 12:23:53 +02:00
|
|
|
|
2022-05-10 22:31:17 +02:00
|
|
|
if data.Error == nil {
|
|
|
|
if s.onUpdate != nil {
|
2022-09-27 22:20:44 +02:00
|
|
|
if err := s.onUpdate(values); err != nil {
|
2022-05-10 22:31:17 +02:00
|
|
|
panic(errors.Wrap(err, "could not update values"))
|
|
|
|
}
|
2022-03-22 09:21:55 +01:00
|
|
|
}
|
2022-05-10 22:31:17 +02:00
|
|
|
|
|
|
|
data.SuccessMessage = "Data updated."
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := template.Exec("index.html.tmpl", w, data); err != nil {
|
|
|
|
panic(errors.WithStack(err))
|
2022-03-22 09:21:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleForm(form url.Values, schema *jsonschema.Schema, values interface{}) (interface{}, error) {
|
|
|
|
pendingDeletes := make([]string, 0)
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
for name, fieldValues := range form {
|
|
|
|
if name == "submit" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
prefix, property, err := parseFieldName(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch prefix {
|
|
|
|
case "bool":
|
2022-05-04 12:23:53 +02:00
|
|
|
if fieldValues[0] == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-03-22 09:21:55 +01:00
|
|
|
booVal, err := parseBoolean(fieldValues[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not parse boolean field '%s'", property)
|
|
|
|
}
|
|
|
|
|
|
|
|
pointer := jsonpointer.New(property)
|
|
|
|
|
|
|
|
values, err = pointer.Force(values, booVal)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not set property '%s' with value '%v'", property, fieldValues[0])
|
|
|
|
}
|
|
|
|
|
2022-05-04 12:23:53 +02:00
|
|
|
case "num":
|
|
|
|
if fieldValues[0] == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
numVal, err := parseNumeric(fieldValues[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not parse numeric field '%s'", property)
|
|
|
|
}
|
|
|
|
|
|
|
|
pointer := jsonpointer.New(property)
|
|
|
|
|
|
|
|
values, err = pointer.Force(values, numVal)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not set property '%s' with value '%v'", property, fieldValues[0])
|
|
|
|
}
|
|
|
|
|
2022-03-22 09:21:55 +01:00
|
|
|
case "add":
|
|
|
|
pointer := jsonpointer.New(property)
|
|
|
|
|
|
|
|
values, err = pointer.Force(values, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not add item '%s'", property)
|
|
|
|
}
|
|
|
|
|
|
|
|
case "del":
|
|
|
|
// Mark property for deletion pass
|
|
|
|
pendingDeletes = append(pendingDeletes, property)
|
|
|
|
|
|
|
|
default:
|
|
|
|
pointer := jsonpointer.New(property)
|
|
|
|
|
|
|
|
values, err = pointer.Force(values, fieldValues[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not set property '%s' with value '%v'", property, fieldValues[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, property := range pendingDeletes {
|
|
|
|
pointer := jsonpointer.New(property)
|
|
|
|
|
|
|
|
values, err = pointer.Delete(values)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not delete property '%s'", property)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseBoolean(value string) (bool, error) {
|
|
|
|
switch value {
|
|
|
|
case "yes":
|
|
|
|
return true, nil
|
|
|
|
case "no":
|
|
|
|
return false, nil
|
|
|
|
default:
|
|
|
|
return false, errors.Errorf("unexpected boolean value '%s'", value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-04 12:23:53 +02:00
|
|
|
func parseNumeric(value string) (float64, error) {
|
|
|
|
numVal, err := strconv.ParseFloat(value, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return numVal, nil
|
|
|
|
}
|
|
|
|
|
2022-03-22 09:21:55 +01:00
|
|
|
func parseFieldName(name string) (string, string, error) {
|
|
|
|
tokens := strings.SplitN(name, ":", 2)
|
|
|
|
|
|
|
|
if len(tokens) == 1 {
|
|
|
|
return "", tokens[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(tokens) == 2 {
|
|
|
|
return tokens[0], tokens[1], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", "", errors.Errorf("unexpected field name '%s'", name)
|
|
|
|
}
|