Resources that can be grouped into the http:server resource must have that prefix. Grouping is basically hierarchical, and without that common prefix, it means we'd have to special-case our grouping algorithm.
674 lines
20 KiB
Go
674 lines
20 KiB
Go
// Mgmt
|
|
// Copyright (C) James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
package resources
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/purpleidea/mgmt/engine"
|
|
"github.com/purpleidea/mgmt/engine/resources/http_server_ui/common"
|
|
"github.com/purpleidea/mgmt/engine/traits"
|
|
"github.com/purpleidea/mgmt/util/errwrap"
|
|
)
|
|
|
|
const (
|
|
httpServerUIInputKind = httpServerUIKind + ":input"
|
|
|
|
httpServerUIInputStoreKey = "key"
|
|
httpServerUIInputStoreSchemeLocal = "local"
|
|
httpServerUIInputStoreSchemeWorld = "world"
|
|
|
|
httpServerUIInputTypeText = common.HTTPServerUIInputTypeText // "text"
|
|
httpServerUIInputTypeRange = common.HTTPServerUIInputTypeRange // "range"
|
|
)
|
|
|
|
func init() {
|
|
engine.RegisterResource(httpServerUIInputKind, func() engine.Res { return &HTTPServerUIInputRes{} })
|
|
}
|
|
|
|
// HTTPServerUIInputRes is a form element that exists within a http:server:ui
|
|
// resource, which exists within an http server. The name is used as the unique
|
|
// id of the field, unless the id field is specified, and in that case it is
|
|
// used instead. The way this works is that it autogroups at runtime with an
|
|
// existing http:server:ui resource, and in doing so makes the form field
|
|
// associated with this resource available as part of that ui which is itself
|
|
// grouped and served from the http server resource.
|
|
type HTTPServerUIInputRes struct {
|
|
traits.Base // add the base methods without re-implementation
|
|
traits.Edgeable // XXX: add autoedge support
|
|
traits.Groupable // can be grouped into HTTPServerUIRes
|
|
traits.Sendable
|
|
|
|
init *engine.Init
|
|
|
|
// Path is the name of the http ui resource to group this into. If it is
|
|
// omitted, and there is only a single http ui resource, then it will
|
|
// be grouped into it automatically. If there is more than one main http
|
|
// ui resource being used, then the grouping behaviour is *undefined*
|
|
// when this is not specified, and it is not recommended to leave this
|
|
// blank!
|
|
Path string `lang:"path" yaml:"path"`
|
|
|
|
// ID is the unique id for this element. It is used in form fields and
|
|
// should not be a private identifier. It must be unique within a given
|
|
// http ui.
|
|
ID string `lang:"id" yaml:"id"`
|
|
|
|
// Value is the default value to use for the form field. If you change
|
|
// it, then the resource graph will change and we'll rebuild and have
|
|
// the new value visible. You can use either this or the Store field.
|
|
// XXX: If we ever add our resource mutate API, we might not need to
|
|
// swap to a new resource graph, and maybe Store is not needed?
|
|
Value string `lang:"value" yaml:"value"`
|
|
|
|
// Store the data in this source. It will also read in a default value
|
|
// from there if one is present. It will watch it for changes as well,
|
|
// and update the displayed value if it's changed from another source.
|
|
// This cannot be used at the same time as the Value field.
|
|
Store string `lang:"store" yaml:"store"`
|
|
|
|
// Type specifies the type of input field this is, and some information
|
|
// about it.
|
|
// XXX: come up with a format such as "multiline://?max=60&style=foo"
|
|
Type string `lang:"type" yaml:"type"`
|
|
|
|
// Sort is a string that you can use to determine the global sorted
|
|
// display order of all the elements in a ui.
|
|
Sort string `lang:"sort" yaml:"sort"`
|
|
|
|
scheme string // the scheme we're using with Store, cached for later
|
|
key string // the key we're using with Store, cached for later
|
|
typeURL *url.URL // the type data, cached for later
|
|
typeURLValues url.Values // the type data, cached for later
|
|
last *string // the last value we sent
|
|
value string // what we've last received from SetValue
|
|
storeEvent bool // did a store event happen?
|
|
mutex *sync.Mutex // guards storeEvent and value
|
|
event chan struct{} // local event that the setValue sends
|
|
}
|
|
|
|
// Default returns some sensible defaults for this resource.
|
|
func (obj *HTTPServerUIInputRes) Default() engine.Res {
|
|
return &HTTPServerUIInputRes{
|
|
Type: "text://",
|
|
}
|
|
}
|
|
|
|
// Validate checks if the resource data structure was populated correctly.
|
|
func (obj *HTTPServerUIInputRes) Validate() error {
|
|
if obj.GetID() == "" {
|
|
return fmt.Errorf("empty id")
|
|
}
|
|
|
|
if obj.Value != "" && obj.Store != "" {
|
|
return fmt.Errorf("may only use either Value or Store")
|
|
}
|
|
|
|
if obj.Value != "" {
|
|
if err := obj.checkValue(obj.Value); err != nil {
|
|
return errwrap.Wrapf(err, "the Value field is invalid")
|
|
}
|
|
}
|
|
|
|
if obj.Store != "" {
|
|
// XXX: check the URI format
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Init runs some startup code for this resource.
|
|
func (obj *HTTPServerUIInputRes) Init(init *engine.Init) error {
|
|
obj.init = init // save for later
|
|
|
|
u, err := url.Parse(obj.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if u == nil {
|
|
return fmt.Errorf("can't parse Type")
|
|
}
|
|
if u.Scheme != httpServerUIInputTypeText && u.Scheme != httpServerUIInputTypeRange {
|
|
return fmt.Errorf("unknown scheme: %s", u.Scheme)
|
|
}
|
|
values, err := url.ParseQuery(u.RawQuery)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj.typeURL = u
|
|
obj.typeURLValues = values
|
|
|
|
if obj.Store != "" {
|
|
u, err := url.Parse(obj.Store)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if u == nil {
|
|
return fmt.Errorf("can't parse Store")
|
|
}
|
|
if u.Scheme != httpServerUIInputStoreSchemeLocal && u.Scheme != httpServerUIInputStoreSchemeWorld {
|
|
return fmt.Errorf("unknown scheme: %s", u.Scheme)
|
|
}
|
|
values, err := url.ParseQuery(u.RawQuery)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
obj.scheme = u.Scheme // cache for later
|
|
obj.key = obj.Name() // default
|
|
|
|
x, exists := values[httpServerUIInputStoreKey]
|
|
if exists && len(x) > 0 && x[0] != "" { // ignore absent or broken keys
|
|
obj.key = x[0]
|
|
}
|
|
}
|
|
|
|
// populate our obj.value cache somehow, so we don't mutate obj.Value
|
|
obj.value = obj.Value // copy
|
|
obj.mutex = &sync.Mutex{}
|
|
obj.event = make(chan struct{}, 1) // buffer to avoid blocks or deadlock
|
|
|
|
return nil
|
|
}
|
|
|
|
// Cleanup is run by the engine to clean up after the resource is done.
|
|
func (obj *HTTPServerUIInputRes) Cleanup() error {
|
|
return nil
|
|
}
|
|
|
|
// getKey returns the key to be used for this resource. If the Store field is
|
|
// specified, it will use that parsed part, otherwise it uses the Name.
|
|
func (obj *HTTPServerUIInputRes) getKey() string {
|
|
if obj.Store != "" {
|
|
return obj.key
|
|
}
|
|
|
|
return obj.Name()
|
|
}
|
|
|
|
// ParentName is used to limit which resources autogroup into this one. If it's
|
|
// empty then it's ignored, otherwise it must match the Name of the parent to
|
|
// get grouped.
|
|
func (obj *HTTPServerUIInputRes) ParentName() string {
|
|
return obj.Path
|
|
}
|
|
|
|
// GetKind returns the kind of this resource.
|
|
func (obj *HTTPServerUIInputRes) GetKind() string {
|
|
// NOTE: We don't *need* to return such a specific string, and "input"
|
|
// would be enough, but we might as well use this because we have it.
|
|
return httpServerUIInputKind
|
|
}
|
|
|
|
// GetID returns the actual ID we respond to. When ID is not specified, we use
|
|
// the Name.
|
|
func (obj *HTTPServerUIInputRes) GetID() string {
|
|
if obj.ID != "" {
|
|
return obj.ID
|
|
}
|
|
return obj.Name()
|
|
}
|
|
|
|
// SetValue stores the new value field that was obtained from submitting the
|
|
// form. This receives the raw, unsafe value that you must validate first.
|
|
func (obj *HTTPServerUIInputRes) SetValue(ctx context.Context, vs []string) error {
|
|
if len(vs) != 1 {
|
|
return fmt.Errorf("unexpected length of %d", len(vs))
|
|
}
|
|
value := vs[0]
|
|
|
|
if err := obj.checkValue(value); err != nil {
|
|
return err
|
|
}
|
|
|
|
obj.mutex.Lock()
|
|
obj.setValue(ctx, value) // also sends an event
|
|
obj.mutex.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// setValue is the helper version where the caller must provide the mutex.
|
|
func (obj *HTTPServerUIInputRes) setValue(ctx context.Context, val string) error {
|
|
obj.value = val
|
|
|
|
select {
|
|
case obj.event <- struct{}{}:
|
|
default:
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) checkValue(value string) error {
|
|
// XXX: validate based on obj.Type
|
|
// XXX: validate what kind of values are allowed, probably no \n, etc...
|
|
return nil
|
|
}
|
|
|
|
// GetValue gets a string representation for the form value, that we'll use in
|
|
// our html form.
|
|
func (obj *HTTPServerUIInputRes) GetValue(ctx context.Context) (string, error) {
|
|
obj.mutex.Lock()
|
|
defer obj.mutex.Unlock()
|
|
|
|
if obj.storeEvent {
|
|
val, exists, err := obj.storeGet(ctx, obj.getKey())
|
|
if err != nil {
|
|
return "", errwrap.Wrapf(err, "error during get")
|
|
}
|
|
if !exists {
|
|
return "", nil // default
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
return obj.value, nil
|
|
}
|
|
|
|
// GetType returns a map that you can use to build the input field in the ui.
|
|
func (obj *HTTPServerUIInputRes) GetType() map[string]string {
|
|
m := make(map[string]string)
|
|
|
|
if obj.typeURL.Scheme == httpServerUIInputTypeRange {
|
|
m = obj.rangeGetType()
|
|
}
|
|
|
|
m[common.HTTPServerUIInputType] = obj.typeURL.Scheme
|
|
|
|
return m
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) rangeGetType() map[string]string {
|
|
m := make(map[string]string)
|
|
base := 10
|
|
bits := 64
|
|
|
|
if sa, exists := obj.typeURLValues[common.HTTPServerUIInputTypeRangeMin]; exists && len(sa) > 0 {
|
|
if x, err := strconv.ParseInt(sa[0], base, bits); err == nil {
|
|
m[common.HTTPServerUIInputTypeRangeMin] = strconv.FormatInt(x, base)
|
|
}
|
|
}
|
|
if sa, exists := obj.typeURLValues[common.HTTPServerUIInputTypeRangeMax]; exists && len(sa) > 0 {
|
|
if x, err := strconv.ParseInt(sa[0], base, bits); err == nil {
|
|
m[common.HTTPServerUIInputTypeRangeMax] = strconv.FormatInt(x, base)
|
|
}
|
|
}
|
|
if sa, exists := obj.typeURLValues[common.HTTPServerUIInputTypeRangeStep]; exists && len(sa) > 0 {
|
|
if x, err := strconv.ParseInt(sa[0], base, bits); err == nil {
|
|
m[common.HTTPServerUIInputTypeRangeStep] = strconv.FormatInt(x, base)
|
|
}
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// GetSort returns a string that you can use to determine the global sorted
|
|
// display order of all the elements in a ui.
|
|
func (obj *HTTPServerUIInputRes) GetSort() string {
|
|
return obj.Sort
|
|
}
|
|
|
|
// Watch is the primary listener for this resource and it outputs events. This
|
|
// particular one does absolutely nothing but block until we've received a done
|
|
// signal.
|
|
func (obj *HTTPServerUIInputRes) Watch(ctx context.Context) error {
|
|
if obj.Store != "" && obj.scheme == httpServerUIInputStoreSchemeLocal {
|
|
return obj.localWatch(ctx)
|
|
}
|
|
if obj.Store != "" && obj.scheme == httpServerUIInputStoreSchemeWorld {
|
|
return obj.worldWatch(ctx)
|
|
}
|
|
|
|
obj.init.Running() // when started, notify engine that we're running
|
|
|
|
// XXX: do we need to watch on obj.event for normal .Value stuff?
|
|
|
|
select {
|
|
case <-ctx.Done(): // closed by the engine to signal shutdown
|
|
}
|
|
|
|
//obj.init.Event() // notify engine of an event (this can block)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) localWatch(ctx context.Context) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
ch, err := obj.init.Local.ValueWatch(ctx, obj.getKey()) // get possible events!
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "error during watch")
|
|
}
|
|
|
|
obj.init.Running() // when started, notify engine that we're running
|
|
|
|
for {
|
|
select {
|
|
case _, ok := <-ch:
|
|
if !ok { // channel shutdown
|
|
return nil
|
|
}
|
|
obj.mutex.Lock()
|
|
obj.storeEvent = true
|
|
obj.mutex.Unlock()
|
|
|
|
case <-obj.event:
|
|
|
|
case <-ctx.Done(): // closed by the engine to signal shutdown
|
|
return nil
|
|
}
|
|
|
|
if obj.init.Debug {
|
|
obj.init.Logf("event!")
|
|
}
|
|
obj.init.Event() // notify engine of an event (this can block)
|
|
}
|
|
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) worldWatch(ctx context.Context) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
ch, err := obj.init.World.StrWatch(ctx, obj.getKey()) // get possible events!
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "error during watch")
|
|
}
|
|
|
|
obj.init.Running() // when started, notify engine that we're running
|
|
|
|
for {
|
|
select {
|
|
case err, ok := <-ch:
|
|
if !ok { // channel shutdown
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "unknown %s watcher error", obj)
|
|
}
|
|
obj.mutex.Lock()
|
|
obj.storeEvent = true
|
|
obj.mutex.Unlock()
|
|
|
|
case <-obj.event:
|
|
|
|
case <-ctx.Done(): // closed by the engine to signal shutdown
|
|
return nil
|
|
}
|
|
|
|
if obj.init.Debug {
|
|
obj.init.Logf("event!")
|
|
}
|
|
obj.init.Event() // notify engine of an event (this can block)
|
|
}
|
|
|
|
}
|
|
|
|
// CheckApply performs the send/recv portion of this autogrouped resources. That
|
|
// can fail, but only if the send portion fails for some reason. If we're using
|
|
// the Store feature, then it also reads and writes to and from that store.
|
|
func (obj *HTTPServerUIInputRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
|
if obj.init.Debug {
|
|
obj.init.Logf("CheckApply")
|
|
}
|
|
|
|
// If we're in ".Value" mode, we want to look at the incoming value, and
|
|
// send it onwards. This function mostly exists as a stub in this case.
|
|
// The private value gets set by obj.SetValue from the http:server:ui
|
|
// parent. If we're in ".Store" mode, then we're reconciling between the
|
|
// "World" and the http:server:ui "Web".
|
|
|
|
if obj.Store != "" {
|
|
return obj.storeCheckApply(ctx, apply)
|
|
}
|
|
|
|
return obj.valueCheckApply(ctx, apply)
|
|
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) valueCheckApply(ctx context.Context, apply bool) (bool, error) {
|
|
|
|
obj.mutex.Lock()
|
|
value := obj.value // gets set by obj.SetValue
|
|
obj.mutex.Unlock()
|
|
|
|
if obj.last != nil && *obj.last == value {
|
|
if err := obj.init.Send(&HTTPServerUIInputSends{
|
|
Value: &value,
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil // expected value has already been sent
|
|
}
|
|
|
|
if !apply {
|
|
if err := obj.init.Send(&HTTPServerUIInputSends{
|
|
Value: &value, // XXX: arbitrary since we're in noop mode
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
s := value // copy
|
|
obj.last = &s // cache
|
|
|
|
// XXX: This is getting called twice, what's the bug?
|
|
obj.init.Logf("sending: %s", value)
|
|
|
|
// send
|
|
if err := obj.init.Send(&HTTPServerUIInputSends{
|
|
Value: &value,
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return false, nil
|
|
//return true, nil // always succeeds, with nothing to do!
|
|
}
|
|
|
|
// storeCheckApply is a tricky function where we attempt to reconcile the state
|
|
// between a third-party changing the value in the World database, and a recent
|
|
// "http:server:ui" change by an end user. Basically whoever runs last is the
|
|
// "right" value that we want to use. We know who sent the event from reading
|
|
// the storeEvent variable, and if it was the World, we want to cache it
|
|
// locally, and if it was the Web, then we want to push it up to the store.
|
|
func (obj *HTTPServerUIInputRes) storeCheckApply(ctx context.Context, apply bool) (bool, error) {
|
|
|
|
v1, exists, err := obj.storeGet(ctx, obj.getKey())
|
|
if err != nil {
|
|
return false, errwrap.Wrapf(err, "error during get")
|
|
}
|
|
|
|
obj.mutex.Lock()
|
|
v2 := obj.value // gets set by obj.SetValue
|
|
storeEvent := obj.storeEvent
|
|
obj.storeEvent = false // reset it
|
|
obj.mutex.Unlock()
|
|
|
|
if exists && v1 == v2 { // both sides are happy
|
|
if err := obj.init.Send(&HTTPServerUIInputSends{
|
|
Value: &v2,
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
if !apply {
|
|
if err := obj.init.Send(&HTTPServerUIInputSends{
|
|
Value: &v2, // XXX: arbitrary since we're in noop mode
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
obj.mutex.Lock()
|
|
if storeEvent { // event from World, pull down the value
|
|
err = obj.setValue(ctx, v1) // also sends an event
|
|
}
|
|
value := obj.value
|
|
obj.mutex.Unlock()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !exists || !storeEvent { // event from web, push up the value
|
|
if err := obj.storeSet(ctx, obj.getKey(), value); err != nil {
|
|
return false, errwrap.Wrapf(err, "error during set")
|
|
}
|
|
}
|
|
|
|
obj.init.Logf("sending: %s", value)
|
|
|
|
// send
|
|
if err := obj.init.Send(&HTTPServerUIInputSends{
|
|
Value: &value,
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) storeGet(ctx context.Context, key string) (string, bool, error) {
|
|
if obj.Store != "" && obj.scheme == httpServerUIInputStoreSchemeLocal {
|
|
val, err := obj.init.Local.ValueGet(ctx, key)
|
|
if err != nil {
|
|
return "", false, err // real error
|
|
}
|
|
if val == nil { // if val is nil, and no error then it doesn't exist
|
|
return "", false, nil // val doesn't exist
|
|
}
|
|
s, ok := val.(string)
|
|
if !ok {
|
|
// TODO: support different types perhaps?
|
|
return "", false, fmt.Errorf("not a string") // real error
|
|
}
|
|
return s, true, nil
|
|
}
|
|
|
|
if obj.Store != "" && obj.scheme == httpServerUIInputStoreSchemeWorld {
|
|
val, err := obj.init.World.StrGet(ctx, key)
|
|
if err != nil && obj.init.World.StrIsNotExist(err) {
|
|
return "", false, nil // val doesn't exist
|
|
}
|
|
if err != nil {
|
|
return "", false, err // real error
|
|
}
|
|
return val, true, nil
|
|
}
|
|
|
|
return "", false, nil // something else
|
|
}
|
|
|
|
func (obj *HTTPServerUIInputRes) storeSet(ctx context.Context, key, val string) error {
|
|
|
|
if obj.Store != "" && obj.scheme == httpServerUIInputStoreSchemeLocal {
|
|
return obj.init.Local.ValueSet(ctx, key, val)
|
|
}
|
|
|
|
if obj.Store != "" && obj.scheme == httpServerUIInputStoreSchemeWorld {
|
|
return obj.init.World.StrSet(ctx, key, val)
|
|
}
|
|
|
|
return nil // something else
|
|
}
|
|
|
|
// Cmp compares two resources and returns an error if they are not equivalent.
|
|
func (obj *HTTPServerUIInputRes) Cmp(r engine.Res) error {
|
|
// we can only compare HTTPServerUIInputRes to others of the same resource kind
|
|
res, ok := r.(*HTTPServerUIInputRes)
|
|
if !ok {
|
|
return fmt.Errorf("res is not the same kind")
|
|
}
|
|
|
|
if obj.Path != res.Path {
|
|
return fmt.Errorf("the Path differs")
|
|
}
|
|
if obj.ID != res.ID {
|
|
return fmt.Errorf("the ID differs")
|
|
}
|
|
if obj.Value != res.Value {
|
|
return fmt.Errorf("the Value differs")
|
|
}
|
|
if obj.Store != res.Store {
|
|
return fmt.Errorf("the Store differs")
|
|
}
|
|
if obj.Type != res.Type {
|
|
return fmt.Errorf("the Type differs")
|
|
}
|
|
if obj.Sort != res.Sort {
|
|
return fmt.Errorf("the Sort differs")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
|
|
// primarily useful for setting the defaults.
|
|
func (obj *HTTPServerUIInputRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
type rawRes HTTPServerUIInputRes // indirection to avoid infinite recursion
|
|
|
|
def := obj.Default() // get the default
|
|
res, ok := def.(*HTTPServerUIInputRes) // put in the right format
|
|
if !ok {
|
|
return fmt.Errorf("could not convert to HTTPServerUIInputRes")
|
|
}
|
|
raw := rawRes(*res) // convert; the defaults go here
|
|
|
|
if err := unmarshal(&raw); err != nil {
|
|
return err
|
|
}
|
|
|
|
*obj = HTTPServerUIInputRes(raw) // restore from indirection with type conversion!
|
|
return nil
|
|
}
|
|
|
|
// HTTPServerUIInputSends is the struct of data which is sent after a successful
|
|
// Apply.
|
|
type HTTPServerUIInputSends struct {
|
|
// Value is the text element value being sent.
|
|
Value *string `lang:"value"`
|
|
}
|
|
|
|
// Sends represents the default struct of values we can send using Send/Recv.
|
|
func (obj *HTTPServerUIInputRes) Sends() interface{} {
|
|
return &HTTPServerUIInputSends{
|
|
Value: nil,
|
|
}
|
|
}
|