github.com/df-mc/dragonfly@v0.9.13/server/player/form/form.go (about) 1 package form 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strings" 9 "unicode/utf8" 10 ) 11 12 // Form represents a form that may be sent to a Submitter. The three types of forms, custom forms, menu forms 13 // and modal forms implement this interface. 14 type Form interface { 15 json.Marshaler 16 SubmitJSON(b []byte, submitter Submitter) error 17 } 18 19 // Custom represents a form that may be sent to a player and has fields that should be filled out by the 20 // player that the form is sent to. 21 type Custom struct { 22 title string 23 submittable Submittable 24 } 25 26 // MarshalJSON ... 27 func (f Custom) MarshalJSON() ([]byte, error) { 28 return json.Marshal(map[string]any{ 29 "type": "custom_form", 30 "title": f.title, 31 "content": f.Elements(), 32 }) 33 } 34 35 // New creates a new (custom) form with the title passed and returns it. The title is formatted according to 36 // the rules of fmt.Sprintln. 37 // The submittable passed is used to create the structure of the form. The values of the Submittable's form 38 // fields are used to set text, defaults and placeholders. If the Submittable passed is not a struct, New 39 // panics. New also panics if one of the exported field types of the Submittable is not one that implements 40 // the Element interface. 41 func New(submittable Submittable, title ...any) Custom { 42 t := reflect.TypeOf(submittable) 43 if t.Kind() != reflect.Struct { 44 panic("submittable must be struct") 45 } 46 f := Custom{title: format(title), submittable: submittable} 47 f.verify() 48 return f 49 } 50 51 // Title returns the formatted title passed when the form was created using New(). 52 func (f Custom) Title() string { 53 return f.title 54 } 55 56 // Elements returns a list of all elements as set in the Submittable passed to form.New(). 57 func (f Custom) Elements() []Element { 58 v := reflect.New(reflect.TypeOf(f.submittable)).Elem() 59 v.Set(reflect.ValueOf(f.submittable)) 60 n := v.NumField() 61 62 elements := make([]Element, 0, n) 63 for i := 0; i < n; i++ { 64 field := v.Field(i) 65 if !field.CanSet() { 66 continue 67 } 68 // Each exported field is guaranteed to implement the Element interface. 69 elements = append(elements, field.Interface().(Element)) 70 } 71 return elements 72 } 73 74 // SubmitJSON submits a JSON data slice to the form. The form will check all values in the JSON array passed, 75 // making sure their values are valid for the form's elements. 76 // If the values are valid and can be parsed properly, the Submittable.Submit() method of the form's Submittable is 77 // called and the fields of the Submittable will be filled out. 78 func (f Custom) SubmitJSON(b []byte, submitter Submitter) error { 79 if b == nil { 80 if closer, ok := f.submittable.(Closer); ok { 81 closer.Close(submitter) 82 } 83 return nil 84 } 85 86 dec := json.NewDecoder(bytes.NewBuffer(b)) 87 dec.UseNumber() 88 89 var data []any 90 if err := dec.Decode(&data); err != nil { 91 return fmt.Errorf("error decoding JSON data to slice: %w", err) 92 } 93 94 v := reflect.New(reflect.TypeOf(f.submittable)).Elem() 95 v.Set(reflect.ValueOf(f.submittable)) 96 97 for i := 0; i < v.NumField(); i++ { 98 fieldV := v.Field(i) 99 if !fieldV.CanSet() { 100 continue 101 } 102 if len(data) == 0 { 103 return fmt.Errorf("form JSON data array does not have enough values") 104 } 105 elem, err := f.parseValue(fieldV.Interface().(Element), data[0]) 106 if err != nil { 107 return fmt.Errorf("error parsing form response value: %w", err) 108 } 109 fieldV.Set(elem) 110 data = data[1:] 111 } 112 113 v.Interface().(Submittable).Submit(submitter) 114 115 return nil 116 } 117 118 // parseValue parses a value into the Element passed and returns it as a reflection Value. If the value is not 119 // valid for the element, an error is returned. 120 func (f Custom) parseValue(elem Element, s any) (reflect.Value, error) { 121 var ok bool 122 var value reflect.Value 123 124 switch element := elem.(type) { 125 case Label: 126 value = reflect.ValueOf(element) 127 case Input: 128 element.value, ok = s.(string) 129 if !ok { 130 return value, fmt.Errorf("value %v is not allowed for input element", s) 131 } 132 if !utf8.ValidString(element.value) { 133 return value, fmt.Errorf("value %v is not valid UTF8", s) 134 } 135 value = reflect.ValueOf(element) 136 case Toggle: 137 element.value, ok = s.(bool) 138 if !ok { 139 return value, fmt.Errorf("value %v is not allowed for toggle element", s) 140 } 141 value = reflect.ValueOf(element) 142 case Slider: 143 v, ok := s.(json.Number) 144 f, err := v.Float64() 145 if !ok || err != nil { 146 return value, fmt.Errorf("value %v is not allowed for slider element", s) 147 } 148 if f > element.Max || f < element.Min { 149 return value, fmt.Errorf("slider value %v is out of range %v-%v", f, element.Min, element.Max) 150 } 151 element.value = f 152 value = reflect.ValueOf(element) 153 case Dropdown: 154 v, ok := s.(json.Number) 155 f, err := v.Int64() 156 if !ok || err != nil { 157 return value, fmt.Errorf("value %v is not allowed for dropdown element", s) 158 } 159 if f < 0 || int(f) >= len(element.Options) { 160 return value, fmt.Errorf("dropdown value %v is out of range %v-%v", f, 0, len(element.Options)-1) 161 } 162 element.value = int(f) 163 value = reflect.ValueOf(element) 164 case StepSlider: 165 v, ok := s.(json.Number) 166 f, err := v.Int64() 167 if !ok || err != nil { 168 return value, fmt.Errorf("value %v is not allowed for dropdown element", s) 169 } 170 if f < 0 || int(f) >= len(element.Options) { 171 return value, fmt.Errorf("dropdown value %v is out of range %v-%v", f, 0, len(element.Options)-1) 172 } 173 element.value = int(f) 174 value = reflect.ValueOf(element) 175 } 176 return value, nil 177 } 178 179 // verify verifies if the form is valid, checking if the fields all implement the Element interface. It panics 180 // if the form is not valid. 181 func (f Custom) verify() { 182 el := reflect.TypeOf((*Element)(nil)).Elem() 183 184 v := reflect.New(reflect.TypeOf(f.submittable)).Elem() 185 v.Set(reflect.ValueOf(f.submittable)) 186 187 t := reflect.TypeOf(f.submittable) 188 for i := 0; i < v.NumField(); i++ { 189 if !v.Field(i).CanSet() { 190 continue 191 } 192 if !t.Field(i).Type.Implements(el) { 193 panic("all exported fields must implement form.Element interface") 194 } 195 } 196 } 197 198 // format is a utility function to format a list of values to have spaces between them, but no newline at the 199 // end. 200 func format(a []any) string { 201 return strings.TrimSuffix(strings.TrimSuffix(fmt.Sprintln(a...), "\n"), "\n") 202 }