github.com/Comcast/plax@v0.8.32/dsl/bindings.go (about)

     1  /*
     2   * Copyright 2021 Comcast Cable Communications Management, LLC
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * SPDX-License-Identifier: Apache-2.0
    17   */
    18  
    19  package dsl
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/Comcast/plax/subst"
    27  )
    28  
    29  type Bindings subst.Bindings
    30  
    31  func NewBindings() *Bindings {
    32  	bs := (Bindings)(subst.NewBindings())
    33  	return &bs
    34  }
    35  
    36  var subber *subst.Subber
    37  
    38  func init() {
    39  	b, err := subst.NewSubber("")
    40  	if err != nil {
    41  		panic(err)
    42  	}
    43  	subber = b
    44  }
    45  
    46  func (ctx *Ctx) subst() *subst.Ctx {
    47  	c := subst.NewCtx(ctx.Context, ctx.IncludeDirs)
    48  	// ToDo: LogLevel.
    49  	return c
    50  }
    51  
    52  func (bs *Bindings) StringSub(ctx *Ctx, s string) (string, error) {
    53  	c := ctx.subst()
    54  	b := subber.WithProcs(proc(ctx, bangBangSub), proc(ctx, atAtSub))
    55  	b.DefaultSerialization = "text"
    56  	s, err := b.Sub(c, *(*subst.Bindings)(bs), s)
    57  	if err != nil {
    58  		return "", err
    59  	}
    60  	return s, nil
    61  }
    62  
    63  func proc(ctx *Ctx, f func(*Ctx, string) (string, error)) subst.Proc {
    64  	return func(_ *subst.Ctx, s string) (string, error) {
    65  		return f(ctx, s)
    66  	}
    67  }
    68  
    69  // Set the parameter key=value pair (without any attempted
    70  // value unmarshalling).
    71  func (bs *Bindings) SetString(value string) error {
    72  	pv := strings.SplitN(value, "=", 2)
    73  	if len(pv) != 2 {
    74  		return fmt.Errorf("bad binding: '%s'", value)
    75  	}
    76  
    77  	bs.SetKeyValue(pv[0], pv[1])
    78  
    79  	return nil
    80  }
    81  
    82  func (bs *Bindings) Sub(ctx *Ctx, s string) (string, error) {
    83  	c := ctx.subst()
    84  	b := subber.WithProcs(proc(ctx, bangBangSub), proc(ctx, atAtSub))
    85  	b.DefaultSerialization = "json"
    86  	m := (*subst.Bindings)(bs)
    87  	b.Procs = append(b.Procs, m.UnmarshalBind)
    88  	s, err := b.Sub(c, *m, s)
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  	return s, nil
    93  }
    94  
    95  func guessSerialization(s string) string {
    96  	var x interface{}
    97  	if err := json.Unmarshal([]byte(s), &x); err == nil {
    98  		return "json"
    99  	}
   100  	return "text"
   101  }
   102  
   103  func (bs *Bindings) SerialSub(ctx *Ctx, serialization string, payload interface{}) (string, error) {
   104  
   105  	// We have a payload that could be a non-string, a string of
   106  	// JSON, or a string of not-JSON.  p.Serialization allows us
   107  	// to distinguish the last two cases; however, to support some
   108  	// backwards compatibility in an era of casual typing, we also
   109  	// have guessSerialization(), which can offer a serialization
   110  	// if p.Serialization is zero.
   111  
   112  	var s string
   113  	var structured bool
   114  	if str, is := payload.(string); is {
   115  		switch serialization {
   116  		case "":
   117  			serialization = guessSerialization(str)
   118  			ctx.Inddf("    Guessing serialization: %s", serialization)
   119  			structured = serialization == "json"
   120  		case "json", "string":
   121  			structured = serialization == "json"
   122  		}
   123  		s = str
   124  	} else {
   125  		structured = true
   126  		js, err := subst.JSONMarshal(&payload)
   127  		if err != nil {
   128  			return "", err
   129  		}
   130  		s = string(js)
   131  	}
   132  
   133  	if structured {
   134  		return bs.Sub(ctx, s)
   135  	}
   136  
   137  	return bs.StringSub(ctx, s)
   138  
   139  }
   140  
   141  func (b *Bindings) SubX(ctx *Ctx, src interface{}, dst *interface{}) error {
   142  	js, err := subst.JSONMarshal(&src)
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	s, err := b.SerialSub(ctx, "json", string(js))
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	return json.Unmarshal([]byte(s), &dst)
   153  }
   154  
   155  func (b *Bindings) Bind(ctx *Ctx, x interface{}) (interface{}, error) {
   156  	bs := (*subst.Bindings)(b)
   157  	return bs.Bind(nil, x)
   158  }
   159  
   160  func (b *Bindings) Copy() (*Bindings, error) {
   161  	acc := make(Bindings)
   162  	for p, v := range *b {
   163  		acc[p] = v
   164  	}
   165  	return &acc, nil
   166  }
   167  
   168  // SetKeyValue to set the binding key to the given (native) value.
   169  func (bs *Bindings) SetKeyValue(key string, value interface{}) {
   170  	(*bs)[key] = value
   171  }
   172  
   173  // Set the parameter key=value pair assuming the value is either
   174  // JSON-serialized or not.
   175  //
   176  // If we can't deserialize the value, we use the literal string (for
   177  // backwards compatibility).
   178  func (bs *Bindings) Set(value string) error {
   179  	parts := strings.SplitN(value, "=", 2)
   180  	if len(parts) != 2 {
   181  		return fmt.Errorf("bad binding: '%s'", value)
   182  	}
   183  
   184  	var v string
   185  	if err := json.Unmarshal([]byte(parts[1]), &v); err != nil {
   186  		v = value
   187  	}
   188  
   189  	k, v := parts[0], parts[1]
   190  
   191  	var val interface{}
   192  	if err := json.Unmarshal([]byte(v), &val); err != nil {
   193  		val = v
   194  	}
   195  
   196  	bs.SetKeyValue(k, val)
   197  
   198  	return nil
   199  }
   200  
   201  func (bs *Bindings) String() string {
   202  	acc := make([]string, 0, len(*bs))
   203  	for k, v := range *bs {
   204  		js, err := subst.JSONMarshal(&v)
   205  		if err != nil {
   206  			js = []byte(fmt.Sprintf("%#v", v))
   207  		}
   208  		acc = append(acc, fmt.Sprintf("%s=%s", k, js))
   209  	}
   210  	return strings.Join(acc, ",")
   211  }
   212  
   213  func (bs *Bindings) Clean(ctx *Ctx, clear bool) {
   214  	// Always remove temporary bindings.
   215  	for p := range *bs {
   216  		if strings.HasPrefix(p, "?*") {
   217  			delete(*bs, p)
   218  		}
   219  	}
   220  
   221  	if clear {
   222  		ctx.Indf("    Clearing bindings (%d) by request", len(*bs))
   223  		for p := range *bs {
   224  			if !strings.HasPrefix(p, "?!") {
   225  				delete(*bs, p)
   226  			}
   227  		}
   228  	}
   229  }