github.com/GuanceCloud/cliutils@v1.1.21/lineproto/lineproto.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  // Package lineproto wraps influxdb lineprotocol.
     7  // Deprecated: use point package.
     8  package lineproto
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"math"
    14  	"reflect"
    15  	"sort"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/influxdata/influxdb1-client/models"
    20  	influxdb "github.com/influxdata/influxdb1-client/v2"
    21  )
    22  
    23  type (
    24  	Callback   func(models.Point) (models.Point, error)
    25  	CallbackV2 func(point *Point) (*Point, error)
    26  )
    27  
    28  type Option struct {
    29  	Time time.Time
    30  
    31  	DisabledTagKeys   []string
    32  	DisabledFieldKeys []string
    33  
    34  	Precision   string
    35  	ExtraTags   map[string]string
    36  	Callback    Callback
    37  	CallbackV2  CallbackV2
    38  	PrecisionV2 Precision
    39  
    40  	Strict             bool
    41  	EnablePointInKey   bool
    42  	DisableStringField bool // disable string field value
    43  
    44  	MaxTags,
    45  	MaxFields,
    46  	MaxTagKeyLen,
    47  	MaxFieldKeyLen,
    48  	MaxTagValueLen,
    49  	MaxFieldValueLen int
    50  }
    51  
    52  type OptionSetter func(opt *Option)
    53  
    54  func WithTime(time time.Time) OptionSetter {
    55  	return func(opt *Option) {
    56  		opt.Time = time
    57  	}
    58  }
    59  
    60  func WithPrecision(precision string) OptionSetter {
    61  	return func(opt *Option) {
    62  		opt.Precision = precision
    63  	}
    64  }
    65  
    66  func WithPrecisionV2(precision Precision) OptionSetter {
    67  	return func(opt *Option) {
    68  		opt.PrecisionV2 = precision
    69  	}
    70  }
    71  
    72  func WithExtraTags(extraTags map[string]string) OptionSetter {
    73  	return func(opt *Option) {
    74  		opt.ExtraTags = extraTags
    75  	}
    76  }
    77  
    78  func WithDisabledTagKeys(disabledTagKeys []string) OptionSetter {
    79  	return func(opt *Option) {
    80  		opt.DisabledTagKeys = disabledTagKeys
    81  	}
    82  }
    83  
    84  func WithDisabledFieldKeys(disabledFieldKeys []string) OptionSetter {
    85  	return func(opt *Option) {
    86  		opt.DisabledFieldKeys = disabledFieldKeys
    87  	}
    88  }
    89  
    90  func WithStrict(b bool) OptionSetter {
    91  	return func(opt *Option) {
    92  		opt.Strict = b
    93  	}
    94  }
    95  
    96  func WithEnablePointInKey(b bool) OptionSetter {
    97  	return func(opt *Option) {
    98  		opt.EnablePointInKey = b
    99  	}
   100  }
   101  
   102  func WithDisableStringField(disableStringField bool) OptionSetter {
   103  	return func(opt *Option) {
   104  		opt.DisableStringField = disableStringField
   105  	}
   106  }
   107  
   108  func WithCallback(callback Callback) OptionSetter {
   109  	return func(opt *Option) {
   110  		opt.Callback = callback
   111  	}
   112  }
   113  
   114  func WithCallbackV2(callback CallbackV2) OptionSetter {
   115  	return func(opt *Option) {
   116  		opt.CallbackV2 = callback
   117  	}
   118  }
   119  
   120  func WithMaxTags(maxTags int) OptionSetter {
   121  	return func(opt *Option) {
   122  		opt.MaxTags = maxTags
   123  	}
   124  }
   125  
   126  func WithMaxFields(maxFields int) OptionSetter {
   127  	return func(opt *Option) {
   128  		opt.MaxFields = maxFields
   129  	}
   130  }
   131  
   132  func WithMaxTagKeyLen(maxTagKeyLen int) OptionSetter {
   133  	return func(opt *Option) {
   134  		opt.MaxTagKeyLen = maxTagKeyLen
   135  	}
   136  }
   137  
   138  func WithMaxFieldKeyLen(maxFieldKeyLen int) OptionSetter {
   139  	return func(opt *Option) {
   140  		opt.MaxFieldKeyLen = maxFieldKeyLen
   141  	}
   142  }
   143  
   144  func WithMaxTagValueLen(maxTagValueLen int) OptionSetter {
   145  	return func(opt *Option) {
   146  		opt.MaxTagValueLen = maxTagValueLen
   147  	}
   148  }
   149  
   150  func WithMaxFieldValueLen(maxFieldValueLen int) OptionSetter {
   151  	return func(opt *Option) {
   152  		opt.MaxFieldValueLen = maxFieldValueLen
   153  	}
   154  }
   155  
   156  type PointWarning struct {
   157  	WarningType string
   158  	Message     string
   159  }
   160  
   161  const (
   162  	WarnMaxTags               = "warn_exceed_max_tags"
   163  	WarnMaxFields             = "warn_exceed_max_fields"
   164  	WarnMaxTagKeyLen          = "warn_exceed_max_tag_key_len"
   165  	WarnMaxFieldKeyLen        = "warn_exceed_max_field_key_len"
   166  	WarnMaxTagValueLen        = "warn_exceed_max_tag_value_len"
   167  	WarnMaxFieldValueLen      = "warn_exceed_max_field_value_len"
   168  	WarnMaxFieldValueInt      = "warn_exceed_max_field_value_int"
   169  	WarnSameTagFieldKey       = "warn_same_tag_field_key"
   170  	WarnInvalidFieldValueType = "warn_invalid_field_value_type"
   171  )
   172  
   173  var DefaultOption = NewDefaultOption()
   174  
   175  func NewDefaultOption() *Option {
   176  	return &Option{
   177  		Strict:      true,
   178  		Precision:   "n",
   179  		PrecisionV2: Nanosecond,
   180  
   181  		MaxTags:   256,
   182  		MaxFields: 1024,
   183  
   184  		MaxTagKeyLen:   256,
   185  		MaxFieldKeyLen: 256,
   186  
   187  		MaxTagValueLen:   1024,
   188  		MaxFieldValueLen: 32 * 1024, // 32K
   189  	}
   190  }
   191  
   192  func (opt *Option) checkDisabledField(f string) error {
   193  	for _, x := range opt.DisabledFieldKeys {
   194  		if f == x {
   195  			return fmt.Errorf("field key `%s' disabled", f)
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  func (opt *Option) checkDisabledTag(t string) error {
   202  	for _, x := range opt.DisabledTagKeys {
   203  		if t == x {
   204  			return fmt.Errorf("tag key `%s' disabled", t)
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  func ParsePoints(data []byte, opt *Option) ([]*influxdb.Point, error) {
   211  	if len(data) == 0 {
   212  		return nil, fmt.Errorf("empty data")
   213  	}
   214  
   215  	if opt == nil {
   216  		opt = DefaultOption
   217  	}
   218  
   219  	if opt.MaxFields <= 0 {
   220  		opt.MaxFields = 1024
   221  	}
   222  
   223  	if opt.MaxTags <= 0 {
   224  		opt.MaxTags = 256
   225  	}
   226  
   227  	ptTime := opt.Time
   228  	if opt.Time.IsZero() {
   229  		ptTime = time.Now()
   230  	}
   231  
   232  	points, err := models.ParsePointsWithPrecision(data, ptTime, opt.Precision)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	res := []*influxdb.Point{}
   238  	for _, point := range points {
   239  		if opt.ExtraTags != nil {
   240  			for k, v := range opt.ExtraTags {
   241  				if !point.HasTag([]byte(k)) {
   242  					point.AddTag(k, v)
   243  				}
   244  			}
   245  		}
   246  
   247  		if opt.Callback != nil {
   248  			newPoint, err := opt.Callback(point)
   249  			if err != nil {
   250  				return nil, err
   251  			}
   252  			point = newPoint
   253  		}
   254  
   255  		if point == nil {
   256  			return nil, fmt.Errorf("line point is empty")
   257  		}
   258  
   259  		if err := checkPoint(point, opt); err != nil {
   260  			return nil, err
   261  		}
   262  
   263  		res = append(res, influxdb.NewPointFrom(point))
   264  	}
   265  
   266  	return res, nil
   267  }
   268  
   269  func MakeLineProtoPoint(name string,
   270  	tags map[string]string,
   271  	fields map[string]interface{},
   272  	opt *Option,
   273  ) (*influxdb.Point, error) {
   274  	pt, _, err := MakeLineProtoPointWithWarnings(name, tags, fields, opt)
   275  	return pt, err
   276  }
   277  
   278  func MakeLineProtoPointWithWarnings(name string,
   279  	tags map[string]string,
   280  	fields map[string]interface{},
   281  	opt *Option,
   282  ) (pt *influxdb.Point, warnings []*PointWarning, err error) {
   283  	warnings = []*PointWarning{}
   284  
   285  	if name == "" {
   286  		err = fmt.Errorf("empty measurement name")
   287  		return
   288  	}
   289  
   290  	if opt == nil {
   291  		opt = DefaultOption
   292  	}
   293  
   294  	// add extra tags
   295  	if opt.ExtraTags != nil {
   296  		if tags == nil {
   297  			tags = opt.ExtraTags
   298  		} else {
   299  			for k, v := range opt.ExtraTags {
   300  				if _, ok := tags[k]; !ok { // NOTE: do-not-override exist tag
   301  					tags[k] = v
   302  				}
   303  			}
   304  		}
   305  	}
   306  
   307  	if opt.MaxTags <= 0 {
   308  		opt.MaxTags = 256
   309  	}
   310  	if opt.MaxFields <= 0 {
   311  		opt.MaxFields = 1024
   312  	}
   313  
   314  	if err = checkTags(tags, opt, &warnings); err != nil {
   315  		return
   316  	}
   317  
   318  	if err = checkFields(fields, opt, &warnings); err != nil {
   319  		return
   320  	}
   321  
   322  	if err = checkTagFieldSameKey(tags, fields, &warnings); err != nil {
   323  		return
   324  	}
   325  
   326  	if opt.Time.IsZero() {
   327  		pt, err = influxdb.NewPoint(name, tags, fields, time.Now().UTC())
   328  		return
   329  	} else {
   330  		pt, err = influxdb.NewPoint(name, tags, fields, opt.Time)
   331  		return
   332  	}
   333  }
   334  
   335  func MakeLineProtoPointV2(name string,
   336  	tags map[string]string,
   337  	fields map[string]interface{},
   338  	opt *Option,
   339  ) (*Point, error) {
   340  	pt, _, err := MakeLineProtoPointWithWarningsV2(name, tags, fields, opt)
   341  	return pt, err
   342  }
   343  
   344  func MakeLineProtoPointWithWarningsV2(name string,
   345  	tags map[string]string,
   346  	fields map[string]interface{},
   347  	opt *Option,
   348  ) (pt *Point, warnings []*PointWarning, err error) {
   349  	warnings = []*PointWarning{}
   350  
   351  	if name == "" {
   352  		err = fmt.Errorf("empty measurement name")
   353  		return
   354  	}
   355  
   356  	if opt == nil {
   357  		opt = DefaultOption
   358  	}
   359  
   360  	// add extra tags
   361  	if opt.ExtraTags != nil {
   362  		if tags == nil {
   363  			tags = opt.ExtraTags
   364  		} else {
   365  			for k, v := range opt.ExtraTags {
   366  				if _, ok := tags[k]; !ok { // NOTE: do-not-override exist tag
   367  					tags[k] = v
   368  				}
   369  			}
   370  		}
   371  	}
   372  
   373  	if opt.MaxTags <= 0 {
   374  		opt.MaxTags = 256
   375  	}
   376  	if opt.MaxFields <= 0 {
   377  		opt.MaxFields = 1024
   378  	}
   379  
   380  	if err = checkTags(tags, opt, &warnings); err != nil {
   381  		return
   382  	}
   383  
   384  	if err = checkFields(fields, opt, &warnings); err != nil {
   385  		return
   386  	}
   387  
   388  	if err = checkTagFieldSameKey(tags, fields, &warnings); err != nil {
   389  		return
   390  	}
   391  
   392  	if opt.Time.IsZero() {
   393  		pt, err = NewPoint(name, tags, fields, time.Now().UTC())
   394  		return
   395  	} else {
   396  		pt, err = NewPoint(name, tags, fields, opt.Time)
   397  		return
   398  	}
   399  }
   400  
   401  func checkPoint(p models.Point, opt *Option) error {
   402  	// check if same key in tags and fields
   403  	fs, err := p.Fields()
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	if len(fs) > opt.MaxFields {
   409  		return fmt.Errorf("exceed max field count(%d), got %d tags", opt.MaxFields, len(fs))
   410  	}
   411  
   412  	for k := range fs {
   413  		if p.HasTag([]byte(k)) {
   414  			return fmt.Errorf("same key `%s' in tag and field", k)
   415  		}
   416  
   417  		// enable `.' in time serial metric
   418  		if strings.Contains(k, ".") && !opt.EnablePointInKey {
   419  			return fmt.Errorf("invalid field key `%s': found `.'", k)
   420  		}
   421  
   422  		if err := opt.checkDisabledField(k); err != nil {
   423  			return err
   424  		}
   425  	}
   426  
   427  	// check if dup keys in fields
   428  	fi := p.FieldIterator()
   429  	fcnt := 0
   430  	for fi.Next() {
   431  		fcnt++
   432  	}
   433  
   434  	if fcnt != len(fs) {
   435  		return fmt.Errorf("unmached field count, expect %d, got %d", fcnt, len(fs))
   436  	}
   437  
   438  	// add more point checking here...
   439  	tags := p.Tags()
   440  	if len(tags) > opt.MaxTags {
   441  		return fmt.Errorf("exceed max tag count(%d), got %d tags", opt.MaxTags, len(tags))
   442  	}
   443  
   444  	for _, t := range tags {
   445  		if bytes.IndexByte(t.Key, byte('.')) != -1 && !opt.EnablePointInKey {
   446  			return fmt.Errorf("invalid tag key `%s': found `.'", string(t.Key))
   447  		}
   448  
   449  		if err := opt.checkDisabledTag(string(t.Key)); err != nil {
   450  			return err
   451  		}
   452  	}
   453  
   454  	return nil
   455  }
   456  
   457  func checkPointV2(p *Point, opt *Option) error {
   458  	// check if same key in tags and fields
   459  	fs := p.Fields
   460  
   461  	if len(fs) > opt.MaxFields {
   462  		return fmt.Errorf("exceed max field count(%d), got %d tags", opt.MaxFields, len(fs))
   463  	}
   464  
   465  	for k := range fs {
   466  		if _, ok := p.Tags[k]; ok {
   467  			return fmt.Errorf("same key `%s' in tag and field", k)
   468  		}
   469  
   470  		// enable `.' in time serial metric
   471  		if strings.Contains(k, ".") && !opt.EnablePointInKey {
   472  			return fmt.Errorf("invalid field key `%s': found `.'", k)
   473  		}
   474  
   475  		if err := opt.checkDisabledField(k); err != nil {
   476  			return err
   477  		}
   478  	}
   479  
   480  	// add more point checking here...
   481  	tags := p.Tags
   482  	if len(tags) > opt.MaxTags {
   483  		return fmt.Errorf("exceed max tag count(%d), got %d tags", opt.MaxTags, len(tags))
   484  	}
   485  
   486  	for key := range tags {
   487  		if strings.IndexByte(key, '.') != -1 && !opt.EnablePointInKey {
   488  			return fmt.Errorf("invalid tag key `%s': found `.'", key)
   489  		}
   490  
   491  		if err := opt.checkDisabledTag(key); err != nil {
   492  			return err
   493  		}
   494  	}
   495  
   496  	return nil
   497  }
   498  
   499  func checkTagFieldSameKey(tags map[string]string, fields map[string]interface{}, warnings *[]*PointWarning) error {
   500  	if tags == nil || fields == nil {
   501  		return nil
   502  	}
   503  
   504  	for k := range tags {
   505  		// delete same key from fields
   506  		if _, ok := fields[k]; ok {
   507  			*warnings = append(*warnings, &PointWarning{
   508  				WarningType: WarnSameTagFieldKey,
   509  				Message:     fmt.Sprintf("same key `%s' in tag and field, ", k),
   510  			})
   511  
   512  			delete(fields, k)
   513  		}
   514  	}
   515  
   516  	return nil
   517  }
   518  
   519  func trimSuffixAll(s, sfx string) string {
   520  	var x string
   521  	for {
   522  		x = strings.TrimSuffix(s, sfx)
   523  		if x == s {
   524  			break
   525  		}
   526  		s = x
   527  	}
   528  	return x
   529  }
   530  
   531  func checkField(k string, v interface{}, opt *Option, pointWarnings *[]*PointWarning) (interface{}, error) {
   532  	if strings.Contains(k, ".") && !opt.EnablePointInKey {
   533  		return nil, fmt.Errorf("invalid field key `%s': found `.'", k)
   534  	}
   535  
   536  	if err := opt.checkDisabledField(k); err != nil {
   537  		return nil, err
   538  	}
   539  
   540  	switch x := v.(type) {
   541  	case uint64:
   542  		if x > uint64(math.MaxInt64) {
   543  			if opt.Strict {
   544  				return nil, fmt.Errorf("too large int field: key=%s, value=%d(> %d)",
   545  					k, x, uint64(math.MaxInt64))
   546  			}
   547  
   548  			*pointWarnings = append(*pointWarnings, &PointWarning{
   549  				WarningType: WarnMaxFieldValueInt,
   550  				Message:     fmt.Sprintf("too large int field: key=%s, field dropped", k),
   551  			})
   552  
   553  			return nil, nil // drop the field
   554  		} else {
   555  			// Force convert uint64 to int64: to disable line proto like
   556  			//    `abc,tag=1 f1=32u`
   557  			// expected is:
   558  			//    `abc,tag=1 f1=32i`
   559  			return int64(x), nil
   560  		}
   561  
   562  	case int, int8, int16, int32, int64,
   563  		uint, uint8, uint16, uint32,
   564  		bool, float32, float64:
   565  		return v, nil
   566  
   567  	case string:
   568  		if opt.DisableStringField {
   569  			*pointWarnings = append(*pointWarnings, &PointWarning{
   570  				WarningType: WarnInvalidFieldValueType,
   571  				Message:     fmt.Sprintf("field(%s) dropped with string value, when [DisableStringField] enabled", k),
   572  			})
   573  			return nil, nil // drop the field
   574  		}
   575  
   576  		if len(x) > opt.MaxFieldValueLen && opt.MaxFieldValueLen > 0 {
   577  			*pointWarnings = append(*pointWarnings, &PointWarning{
   578  				WarningType: WarnMaxFieldValueLen,
   579  				Message:     fmt.Sprintf("field (%s) exceed max field value length(%d), got %d, value truncated", k, opt.MaxFieldValueLen, len(x)),
   580  			})
   581  
   582  			return x[:opt.MaxFieldValueLen], nil
   583  		}
   584  		return v, nil
   585  
   586  	default:
   587  		if opt.Strict {
   588  			if v == nil {
   589  				*pointWarnings = append(*pointWarnings, &PointWarning{
   590  					WarningType: WarnInvalidFieldValueType,
   591  					Message:     fmt.Sprintf("invalid field (%s) value type: nil value, field dropped", k),
   592  				})
   593  
   594  				return nil, fmt.Errorf("invalid field value type %s, value is nil", k)
   595  			} else {
   596  				*pointWarnings = append(*pointWarnings, &PointWarning{
   597  					WarningType: WarnInvalidFieldValueType,
   598  					Message:     fmt.Sprintf("invalid field (%s) type: %s, field dropped", k, reflect.TypeOf(v).String()),
   599  				})
   600  
   601  				return nil, fmt.Errorf("invalid field(%s) type: %s", k, reflect.TypeOf(v).String())
   602  			}
   603  		}
   604  
   605  		return nil, nil
   606  	}
   607  }
   608  
   609  func checkFields(fields map[string]interface{}, opt *Option, pointWarnings *[]*PointWarning) error {
   610  	// warnings: WarnMaxFields
   611  	warnings := []*PointWarning{}
   612  
   613  	// delete extra key
   614  	if opt.MaxFields > 0 && len(fields) > opt.MaxFields {
   615  		var keys []string
   616  		for k := range fields {
   617  			keys = append(keys, k)
   618  		}
   619  
   620  		sort.Strings(keys)
   621  
   622  		deleteKeys := keys[opt.MaxFields:]
   623  
   624  		for _, k := range deleteKeys {
   625  			delete(fields, k)
   626  		}
   627  
   628  		warnings = append(warnings, &PointWarning{
   629  			WarningType: WarnMaxFields,
   630  			Message:     fmt.Sprintf("exceed max field count(%d), got %d fields, extra fields deleted", opt.MaxFields, len(fields)),
   631  		})
   632  	}
   633  
   634  	for k, v := range fields {
   635  		// trim key
   636  		if opt.MaxFieldKeyLen > 0 && len(k) > opt.MaxFieldKeyLen {
   637  			warnings = append(warnings, &PointWarning{
   638  				WarningType: WarnMaxFieldKeyLen,
   639  				Message:     fmt.Sprintf("exceed max field key length(%d), got %d, key truncated", opt.MaxFieldKeyLen, len(k)),
   640  			})
   641  
   642  			delete(fields, k)
   643  			k = k[:opt.MaxFieldKeyLen]
   644  			fields[k] = v
   645  		}
   646  
   647  		if x, err := checkField(k, v, opt, &warnings); err != nil {
   648  			return err
   649  		} else {
   650  			if x == nil {
   651  				delete(fields, k)
   652  			} else {
   653  				fields[k] = x
   654  			}
   655  		}
   656  	}
   657  
   658  	if pointWarnings != nil {
   659  		*pointWarnings = append(*pointWarnings, warnings...)
   660  	}
   661  
   662  	return nil
   663  }
   664  
   665  func checkTags(tags map[string]string, opt *Option, pointWarnings *[]*PointWarning) error {
   666  	// warnings: WarnMaxTags, WarnMaxTagKeyLen, WarnMaxTagKeyLen
   667  	warnings := []*PointWarning{}
   668  	// delete extra key
   669  	if len(tags) > opt.MaxTags {
   670  		var keys []string
   671  		for k := range tags {
   672  			keys = append(keys, k)
   673  		}
   674  
   675  		sort.Strings(keys)
   676  
   677  		deleteKeys := keys[opt.MaxTags:]
   678  
   679  		for _, k := range deleteKeys {
   680  			delete(tags, k)
   681  		}
   682  
   683  		warnings = append(warnings, &PointWarning{
   684  			WarningType: WarnMaxTags,
   685  			Message:     fmt.Sprintf("exceed max tag count(%d), got %d tags, extra tags deleted", opt.MaxTags, len(tags)),
   686  		})
   687  	}
   688  
   689  	for k, v := range tags {
   690  		if opt.MaxTagKeyLen > 0 && len(k) > opt.MaxTagKeyLen {
   691  			warnings = append(warnings, &PointWarning{
   692  				WarningType: WarnMaxTagKeyLen,
   693  				Message:     fmt.Sprintf("exceed max tag key length(%d), got %d, key truncated", opt.MaxTagKeyLen, len(k)),
   694  			})
   695  
   696  			delete(tags, k)
   697  			k = k[:opt.MaxTagKeyLen]
   698  			tags[k] = v
   699  		}
   700  
   701  		if opt.MaxTagValueLen > 0 && len(v) > opt.MaxTagValueLen {
   702  			tags[k] = v[:opt.MaxTagValueLen]
   703  			warnings = append(warnings, &PointWarning{
   704  				WarningType: WarnMaxTagValueLen,
   705  				Message:     fmt.Sprintf("exceed max tag value length(%d), got %d, value truncated", opt.MaxTagValueLen, len(v)),
   706  			})
   707  		}
   708  
   709  		// check tag key '\', '\n'
   710  		if strings.HasSuffix(k, `\`) || strings.Contains(k, "\n") {
   711  			if !opt.Strict {
   712  				delete(tags, k)
   713  				k = adjustKV(k)
   714  				tags[k] = v
   715  			} else {
   716  				return fmt.Errorf("invalid tag key `%s'", k)
   717  			}
   718  		}
   719  
   720  		// check tag value: '\', '\n'
   721  		if strings.HasSuffix(v, `\`) || strings.Contains(v, "\n") {
   722  			if !opt.Strict {
   723  				tags[k] = adjustKV(v)
   724  			} else {
   725  				return fmt.Errorf("invalid tag value `%s'", v)
   726  			}
   727  		}
   728  
   729  		// not recoverable if `.' exists!
   730  		if strings.Contains(k, ".") && !opt.EnablePointInKey {
   731  			return fmt.Errorf("invalid tag key `%s': found `.'", k)
   732  		}
   733  
   734  		if err := opt.checkDisabledTag(k); err != nil {
   735  			return err
   736  		}
   737  	}
   738  
   739  	if pointWarnings != nil {
   740  		*pointWarnings = append(*pointWarnings, warnings...)
   741  	}
   742  
   743  	return nil
   744  }
   745  
   746  // Remove all `\` suffix on key/val
   747  // Replace all `\n` with ` `.
   748  func adjustKV(x string) string {
   749  	if strings.HasSuffix(x, `\`) {
   750  		x = trimSuffixAll(x, `\`)
   751  	}
   752  
   753  	if strings.Contains(x, "\n") {
   754  		x = strings.ReplaceAll(x, "\n", " ")
   755  	}
   756  
   757  	return x
   758  }