github.com/m3db/m3@v1.5.0/src/metrics/pipeline/type.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package pipeline 22 23 import ( 24 "bytes" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "sort" 29 "strings" 30 31 "github.com/m3db/m3/src/metrics/aggregation" 32 "github.com/m3db/m3/src/metrics/generated/proto/pipelinepb" 33 "github.com/m3db/m3/src/metrics/transformation" 34 xbytes "github.com/m3db/m3/src/metrics/x/bytes" 35 ) 36 37 var ( 38 errNilAggregationOpProto = errors.New("nil aggregation op proto message") 39 errNilTransformationOpProto = errors.New("nil transformation op proto message") 40 errNilRollupOpProto = errors.New("nil rollup op proto message") 41 errNilPipelineProto = errors.New("nil pipeline proto message") 42 errNoOpInUnionMarshaler = errors.New("no operation in union JSON value") 43 ) 44 45 const ( 46 templateMetricNameVar = ".MetricName" 47 templateOpen = "{{" 48 templateClose = "}}" 49 templateMetricNameExactMatch = templateOpen + " " + templateMetricNameVar + " " + templateClose 50 ) 51 52 var ( 53 templateMetricNameExactMatchBytes = []byte(templateMetricNameExactMatch) 54 templateAllowed = []string{templateMetricNameExactMatch} 55 ) 56 57 func maybeContainsTemplate(str string) bool { 58 return strings.Contains(str, templateOpen) || strings.Contains(str, templateClose) 59 } 60 61 // OpType defines the type of an operation. 62 type OpType int 63 64 // List of supported operation types. 65 const ( 66 UnknownOpType OpType = iota 67 AggregationOpType 68 TransformationOpType 69 RollupOpType 70 ) 71 72 // AggregationOp is an aggregation operation. 73 type AggregationOp struct { 74 // Type of aggregation performed. 75 Type aggregation.Type 76 } 77 78 // NewAggregationOpFromProto creates a new aggregation op from proto. 79 func NewAggregationOpFromProto(pb *pipelinepb.AggregationOp) (AggregationOp, error) { 80 var agg AggregationOp 81 if pb == nil { 82 return agg, errNilAggregationOpProto 83 } 84 aggType, err := aggregation.NewTypeFromProto(pb.Type) 85 if err != nil { 86 return agg, err 87 } 88 agg.Type = aggType 89 return agg, nil 90 } 91 92 // Clone clones the aggregation operation. 93 func (op AggregationOp) Clone() AggregationOp { 94 return op 95 } 96 97 // Equal determines whether two aggregation operations are equal. 98 func (op AggregationOp) Equal(other AggregationOp) bool { 99 return op.Type == other.Type 100 } 101 102 // Proto returns the proto message for the given aggregation operation. 103 func (op AggregationOp) Proto() (*pipelinepb.AggregationOp, error) { 104 pbOpType, err := op.Type.Proto() 105 if err != nil { 106 return nil, err 107 } 108 return &pipelinepb.AggregationOp{Type: pbOpType}, nil 109 } 110 111 func (op AggregationOp) String() string { 112 return op.Type.String() 113 } 114 115 // MarshalText returns the text encoding of an aggregation operation. 116 func (op AggregationOp) MarshalText() ([]byte, error) { 117 return op.Type.MarshalText() 118 } 119 120 // UnmarshalText unmarshals text-encoded data into an aggregation operation. 121 func (op *AggregationOp) UnmarshalText(data []byte) error { 122 return op.Type.UnmarshalText(data) 123 } 124 125 // TransformationOp is a transformation operation. 126 type TransformationOp struct { 127 // Type of transformation performed. 128 Type transformation.Type 129 } 130 131 // NewTransformationOpFromProto creates a new transformation op from proto. 132 func NewTransformationOpFromProto(pb *pipelinepb.TransformationOp) (TransformationOp, error) { 133 var tf TransformationOp 134 if err := tf.FromProto(*pb); err != nil { 135 return TransformationOp{}, err 136 } 137 return tf, nil 138 } 139 140 // Equal determines whether two transformation operations are equal. 141 func (op TransformationOp) Equal(other TransformationOp) bool { 142 return op.Type == other.Type 143 } 144 145 // Clone clones the transformation operation. 146 func (op TransformationOp) Clone() TransformationOp { 147 return op 148 } 149 150 // Proto returns the proto message for the given transformation op. 151 func (op TransformationOp) Proto() (*pipelinepb.TransformationOp, error) { 152 var pbOp pipelinepb.TransformationOp 153 if err := op.ToProto(&pbOp); err != nil { 154 return nil, err 155 } 156 return &pbOp, nil 157 } 158 159 func (op TransformationOp) String() string { 160 return op.Type.String() 161 } 162 163 // ToProto converts the transformation op to a protobuf message in place. 164 func (op TransformationOp) ToProto(pb *pipelinepb.TransformationOp) error { 165 return op.Type.ToProto(&pb.Type) 166 } 167 168 // FromProto converts the protobuf message to a transformation in place. 169 func (op *TransformationOp) FromProto(pb pipelinepb.TransformationOp) error { 170 return op.Type.FromProto(pb.Type) 171 } 172 173 // UnmarshalText extracts this type from its textual representation. 174 func (op *TransformationOp) UnmarshalText(text []byte) error { 175 return op.Type.UnmarshalText(text) 176 } 177 178 // MarshalText serializes this type to its textual representation. 179 func (op TransformationOp) MarshalText() (text []byte, err error) { 180 return op.Type.MarshalText() 181 } 182 183 // RollupType is the rollup type. 184 // Note: Must match the protobuf enum definition since this is a direct cast. 185 type RollupType int 186 187 const ( 188 // GroupByRollupType defines the group by rollup op type (default). 189 GroupByRollupType RollupType = iota 190 // ExcludeByRollupType defines the exclude by rollup op type. 191 ExcludeByRollupType 192 ) 193 194 // RollupOp is a rollup operation. 195 type RollupOp struct { 196 // Dimensions along which the rollup is performed. 197 Tags [][]byte 198 // New metric name generated as a result of the rollup. 199 newName []byte 200 // Type is the rollup type. 201 Type RollupType 202 // Types of aggregation performed within each unique dimension combination. 203 AggregationID aggregation.ID 204 newNameTemplated bool 205 } 206 207 // NewRollupOpFromProto creates a new rollup op from proto. 208 // NB: the rollup tags are always sorted on construction. 209 func NewRollupOpFromProto(pb *pipelinepb.RollupOp) (RollupOp, error) { 210 var rollup RollupOp 211 if pb == nil { 212 return rollup, errNilRollupOpProto 213 } 214 215 aggregationID, err := aggregation.NewIDFromProto(pb.AggregationTypes) 216 if err != nil { 217 return rollup, err 218 } 219 220 return NewRollupOp(RollupType(pb.Type), pb.NewName, pb.Tags, aggregationID) 221 } 222 223 // NewRollupOp creates a new rollup op. 224 func NewRollupOp( 225 rollupType RollupType, 226 rollupNewName string, 227 rollupTags []string, 228 rollupAggregationID aggregation.ID, 229 ) (RollupOp, error) { 230 var rollup RollupOp 231 232 tags := make([]string, len(rollupTags)) 233 copy(tags, rollupTags) 234 sort.Strings(tags) 235 236 var newNameTemplated bool 237 if maybeContainsTemplate(rollupNewName) { 238 // This metric might have a templated metric name. 239 newNameTemplated = true 240 241 // Right now only support "{{ .MetricName }}" to be able to generate 242 // the resulting metric name without using a Go template and only 243 // a single instance of it. 244 if n := strings.Count(rollupNewName, templateMetricNameExactMatch); n > 1 { 245 return rollup, fmt.Errorf( 246 "rollup contained template variable metric name more than once: "+ 247 "input=%s, count_var_metric_name=%v", rollupNewName, n) 248 } 249 250 // Replace and see if all template tags resolved. 251 replacedNewName := strings.Replace(rollupNewName, templateMetricNameExactMatch, "", 1) 252 253 // Make sure fully replaced all instances of template usage, otherwise 254 // there are some other variables not supported or invalid use of 255 // template variable tags. 256 if maybeContainsTemplate(replacedNewName) { 257 return rollup, fmt.Errorf( 258 "rollup contained template tags but variables not resolved: "+ 259 "input=%s, allowed=%v", rollupNewName, templateAllowed) 260 } 261 } 262 263 return RollupOp{ 264 Type: rollupType, 265 Tags: xbytes.ArraysFromStringArray(tags), 266 AggregationID: rollupAggregationID, 267 newName: []byte(rollupNewName), 268 newNameTemplated: newNameTemplated, 269 }, nil 270 } 271 272 // NewName returns the new rollup name based on an existing name if 273 // the new name uses a template, or otherwise the literal new name. 274 func (op RollupOp) NewName(currName []byte) []byte { 275 if !op.newNameTemplated { 276 // No templated name, just return the "literal" new name. 277 return op.newName 278 } 279 280 out := make([]byte, 0, len(op.newName)+len(currName)) 281 idx := bytes.Index(op.newName, templateMetricNameExactMatchBytes) 282 if idx == -1 { 283 return op.newName 284 } 285 286 out = append(out, op.newName[0:idx]...) 287 out = append(out, currName...) 288 out = append(out, op.newName[idx+len(templateMetricNameExactMatchBytes):]...) 289 return out 290 } 291 292 // SameTransform returns true if the two rollup operations have the same rollup transformation 293 // (i.e., same new rollup metric name and same set of rollup tags). 294 func (op RollupOp) SameTransform(other RollupOp) bool { 295 if len(op.Tags) != len(other.Tags) { 296 return false 297 } 298 if !bytes.Equal(op.newName, other.newName) { 299 return false 300 } 301 // Sort the tags and compare. 302 clonedTags := xbytes.ArraysToStringArray(op.Tags) 303 sort.Strings(clonedTags) 304 otherClonedTags := xbytes.ArraysToStringArray(other.Tags) 305 sort.Strings(otherClonedTags) 306 for i := 0; i < len(clonedTags); i++ { 307 if clonedTags[i] != otherClonedTags[i] { 308 return false 309 } 310 } 311 return true 312 } 313 314 // Equal returns true if two rollup operations are equal. 315 func (op RollupOp) Equal(other RollupOp) bool { 316 if !op.AggregationID.Equal(other.AggregationID) { 317 return false 318 } 319 if op.Type != other.Type { 320 return false 321 } 322 return op.SameTransform(other) 323 } 324 325 // Clone clones the rollup operation. 326 func (op RollupOp) Clone() RollupOp { 327 newName := make([]byte, len(op.newName)) 328 copy(newName, op.newName) 329 return RollupOp{ 330 Type: op.Type, 331 Tags: xbytes.ArrayCopy(op.Tags), 332 AggregationID: op.AggregationID, 333 newName: newName, 334 newNameTemplated: op.newNameTemplated, 335 } 336 } 337 338 // Proto returns the proto message for the given rollup op. 339 func (op RollupOp) Proto() (*pipelinepb.RollupOp, error) { 340 aggTypes, err := op.AggregationID.Types() 341 if err != nil { 342 return nil, err 343 } 344 pbAggTypes, err := aggTypes.Proto() 345 if err != nil { 346 return nil, err 347 } 348 return &pipelinepb.RollupOp{ 349 Type: pipelinepb.RollupOp_Type(op.Type), 350 NewName: string(op.newName), 351 Tags: xbytes.ArraysToStringArray(op.Tags), 352 AggregationTypes: pbAggTypes, 353 }, nil 354 } 355 356 func (op RollupOp) String() string { 357 var b bytes.Buffer 358 b.WriteString("{") 359 fmt.Fprintf(&b, "name: %s, ", op.newName) 360 fmt.Fprintf(&b, "type: %v, ", op.Type) 361 b.WriteString("tags: [") 362 for i, t := range op.Tags { 363 fmt.Fprintf(&b, "%s", t) 364 if i < len(op.Tags)-1 { 365 b.WriteString(", ") 366 } 367 } 368 b.WriteString("], ") 369 fmt.Fprintf(&b, "aggregation: %v", op.AggregationID) 370 b.WriteString("}") 371 return b.String() 372 } 373 374 // MarshalJSON returns the JSON encoding of a rollup operation. 375 func (op RollupOp) MarshalJSON() ([]byte, error) { 376 return json.Marshal(newRollupMarshaler(op)) 377 } 378 379 // UnmarshalJSON unmarshals JSON-encoded data into a rollup operation. 380 func (op *RollupOp) UnmarshalJSON(data []byte) error { 381 var converted rollupMarshaler 382 err := json.Unmarshal(data, &converted) 383 if err != nil { 384 return err 385 } 386 *op, err = converted.RollupOp() 387 return err 388 } 389 390 // UnmarshalYAML unmarshals YAML-encoded data into a rollup operation. 391 func (op *RollupOp) UnmarshalYAML(unmarshal func(interface{}) error) error { 392 var converted rollupMarshaler 393 err := unmarshal(&converted) 394 if err != nil { 395 return err 396 } 397 *op, err = converted.RollupOp() 398 return err 399 } 400 401 // MarshalYAML returns the YAML representation of this type. 402 func (op RollupOp) MarshalYAML() (interface{}, error) { 403 return newRollupMarshaler(op), nil 404 } 405 406 type rollupMarshaler struct { 407 Type RollupType `json:"type" yaml:"type"` 408 NewName string `json:"newName" yaml:"newName"` 409 Tags []string `json:"tags" yaml:"tags"` 410 AggregationID aggregation.ID `json:"aggregation,omitempty" yaml:"aggregation"` 411 } 412 413 func newRollupMarshaler(op RollupOp) rollupMarshaler { 414 return rollupMarshaler{ 415 Type: op.Type, 416 NewName: string(op.newName), 417 Tags: xbytes.ArraysToStringArray(op.Tags), 418 AggregationID: op.AggregationID, 419 } 420 } 421 422 func (m rollupMarshaler) RollupOp() (RollupOp, error) { 423 return NewRollupOp(m.Type, m.NewName, m.Tags, m.AggregationID) 424 } 425 426 // OpUnion is a union of different types of operation. 427 type OpUnion struct { 428 Rollup RollupOp 429 Type OpType 430 Aggregation AggregationOp 431 Transformation TransformationOp 432 } 433 434 // NewOpUnionFromProto creates a new operation union from proto. 435 func NewOpUnionFromProto(pb pipelinepb.PipelineOp) (OpUnion, error) { 436 var ( 437 u OpUnion 438 err error 439 ) 440 switch pb.Type { 441 case pipelinepb.PipelineOp_AGGREGATION: 442 u.Type = AggregationOpType 443 u.Aggregation, err = NewAggregationOpFromProto(pb.Aggregation) 444 case pipelinepb.PipelineOp_TRANSFORMATION: 445 u.Type = TransformationOpType 446 u.Transformation, err = NewTransformationOpFromProto(pb.Transformation) 447 case pipelinepb.PipelineOp_ROLLUP: 448 u.Type = RollupOpType 449 u.Rollup, err = NewRollupOpFromProto(pb.Rollup) 450 default: 451 err = fmt.Errorf("unknown op type in proto: %v", pb.Type) 452 } 453 return u, err 454 } 455 456 // Equal determines whether two operation unions are equal. 457 func (u OpUnion) Equal(other OpUnion) bool { 458 if u.Type != other.Type { 459 return false 460 } 461 switch u.Type { 462 case AggregationOpType: 463 return u.Aggregation.Equal(other.Aggregation) 464 case TransformationOpType: 465 return u.Transformation.Equal(other.Transformation) 466 case RollupOpType: 467 return u.Rollup.Equal(other.Rollup) 468 } 469 return true 470 } 471 472 // Clone clones an operation union. 473 func (u OpUnion) Clone() OpUnion { 474 clone := OpUnion{Type: u.Type} 475 switch u.Type { 476 case AggregationOpType: 477 clone.Aggregation = u.Aggregation.Clone() 478 case TransformationOpType: 479 clone.Transformation = u.Transformation.Clone() 480 case RollupOpType: 481 clone.Rollup = u.Rollup.Clone() 482 } 483 return clone 484 } 485 486 // Proto creates a proto message for the given operation. 487 func (u OpUnion) Proto() (*pipelinepb.PipelineOp, error) { 488 var ( 489 pbOp pipelinepb.PipelineOp 490 err error 491 ) 492 switch u.Type { 493 case AggregationOpType: 494 pbOp.Type = pipelinepb.PipelineOp_AGGREGATION 495 pbOp.Aggregation, err = u.Aggregation.Proto() 496 case TransformationOpType: 497 pbOp.Type = pipelinepb.PipelineOp_TRANSFORMATION 498 pbOp.Transformation, err = u.Transformation.Proto() 499 case RollupOpType: 500 pbOp.Type = pipelinepb.PipelineOp_ROLLUP 501 pbOp.Rollup, err = u.Rollup.Proto() 502 default: 503 err = fmt.Errorf("unknown op type: %v", u.Type) 504 } 505 return &pbOp, err 506 } 507 508 func (u OpUnion) String() string { 509 var b bytes.Buffer 510 b.WriteString("{") 511 switch u.Type { 512 case AggregationOpType: 513 fmt.Fprintf(&b, "aggregation: %s", u.Aggregation.String()) 514 case TransformationOpType: 515 fmt.Fprintf(&b, "transformation: %s", u.Transformation.String()) 516 case RollupOpType: 517 fmt.Fprintf(&b, "rollup: %s", u.Rollup.String()) 518 default: 519 fmt.Fprintf(&b, "unknown op type: %v", u.Type) 520 } 521 b.WriteString("}") 522 return b.String() 523 } 524 525 // MarshalJSON returns the JSON encoding of an operation union. 526 func (u OpUnion) MarshalJSON() ([]byte, error) { 527 converted, err := newUnionMarshaler(u) 528 if err != nil { 529 return nil, err 530 } 531 return json.Marshal(converted) 532 } 533 534 // UnmarshalJSON unmarshals JSON-encoded data into an operation union. 535 func (u *OpUnion) UnmarshalJSON(data []byte) error { 536 var converted unionMarshaler 537 if err := json.Unmarshal(data, &converted); err != nil { 538 return err 539 } 540 union, err := converted.OpUnion() 541 if err != nil { 542 return err 543 } 544 *u = union 545 return nil 546 } 547 548 // MarshalJSON returns the JSON encoding of an operation union. 549 func (u OpUnion) MarshalYAML() (interface{}, error) { 550 return newUnionMarshaler(u) 551 } 552 553 // UnmarshalYAML unmarshals YAML-encoded data into an operation union. 554 func (u *OpUnion) UnmarshalYAML(unmarshal func(interface{}) error) error { 555 var converted unionMarshaler 556 if err := unmarshal(&converted); err != nil { 557 return err 558 } 559 union, err := converted.OpUnion() 560 if err != nil { 561 return err 562 } 563 *u = union 564 return nil 565 } 566 567 // unionMarshaler is a helper type to facilitate marshaling and unmarshaling operation unions. 568 type unionMarshaler struct { 569 Aggregation *AggregationOp `json:"aggregation,omitempty" yaml:"aggregation"` 570 Transformation *TransformationOp `json:"transformation,omitempty" yaml:"transformation"` 571 Rollup *RollupOp `json:"rollup,omitempty" yaml:"rollup"` 572 } 573 574 func newUnionMarshaler(u OpUnion) (unionMarshaler, error) { 575 var converted unionMarshaler 576 switch u.Type { 577 case AggregationOpType: 578 converted.Aggregation = &u.Aggregation 579 case TransformationOpType: 580 converted.Transformation = &u.Transformation 581 case RollupOpType: 582 converted.Rollup = &u.Rollup 583 default: 584 return unionMarshaler{}, fmt.Errorf("unknown op type: %v", u.Type) 585 } 586 return converted, nil 587 } 588 589 func (m unionMarshaler) OpUnion() (OpUnion, error) { 590 if m.Aggregation != nil { 591 return OpUnion{Type: AggregationOpType, Aggregation: *m.Aggregation}, nil 592 } 593 if m.Transformation != nil { 594 return OpUnion{Type: TransformationOpType, Transformation: *m.Transformation}, nil 595 } 596 if m.Rollup != nil { 597 return OpUnion{Type: RollupOpType, Rollup: *m.Rollup}, nil 598 } 599 return OpUnion{}, errNoOpInUnionMarshaler 600 } 601 602 // Pipeline is a pipeline of operations. 603 type Pipeline struct { 604 // a list of pipeline operations. 605 operations []OpUnion 606 } 607 608 // NewPipeline creates a new pipeline. 609 func NewPipeline(ops []OpUnion) Pipeline { 610 return Pipeline{operations: ops} 611 } 612 613 // NewPipelineFromProto creates a new pipeline from proto. 614 func NewPipelineFromProto(pb *pipelinepb.Pipeline) (Pipeline, error) { 615 if pb == nil { 616 return Pipeline{}, errNilPipelineProto 617 } 618 operations := make([]OpUnion, 0, len(pb.Ops)) 619 for _, pbOp := range pb.Ops { 620 operation, err := NewOpUnionFromProto(pbOp) 621 if err != nil { 622 return Pipeline{}, err 623 } 624 operations = append(operations, operation) 625 } 626 return Pipeline{operations: operations}, nil 627 } 628 629 // Len returns the number of steps in a pipeline. 630 func (p Pipeline) Len() int { return len(p.operations) } 631 632 // IsEmpty determines whether a pipeline is empty. 633 func (p Pipeline) IsEmpty() bool { return p.Len() == 0 } 634 635 // At returns the operation at a given step. 636 func (p Pipeline) At(i int) OpUnion { return p.operations[i] } 637 638 // Equal determines whether two pipelines are equal. 639 func (p Pipeline) Equal(other Pipeline) bool { 640 if len(p.operations) != len(other.operations) { 641 return false 642 } 643 for i := 0; i < len(p.operations); i++ { 644 if !p.operations[i].Equal(other.operations[i]) { 645 return false 646 } 647 } 648 return true 649 } 650 651 // SubPipeline returns a sub-pipeline containing operations between step `startInclusive` 652 // and step `endExclusive` of the current pipeline. 653 func (p Pipeline) SubPipeline(startInclusive int, endExclusive int) Pipeline { 654 return Pipeline{operations: p.operations[startInclusive:endExclusive]} 655 } 656 657 // Clone clones the pipeline. 658 func (p Pipeline) Clone() Pipeline { 659 clone := make([]OpUnion, len(p.operations)) 660 for i, op := range p.operations { 661 clone[i] = op.Clone() 662 } 663 return Pipeline{operations: clone} 664 } 665 666 // Proto returns the proto message for a given pipeline. 667 func (p Pipeline) Proto() (*pipelinepb.Pipeline, error) { 668 pbOps := make([]pipelinepb.PipelineOp, 0, len(p.operations)) 669 for _, op := range p.operations { 670 pbOp, err := op.Proto() 671 if err != nil { 672 return nil, err 673 } 674 pbOps = append(pbOps, *pbOp) 675 } 676 return &pipelinepb.Pipeline{Ops: pbOps}, nil 677 } 678 679 func (p Pipeline) String() string { 680 var b bytes.Buffer 681 b.WriteString("{operations: [") 682 for i, op := range p.operations { 683 b.WriteString(op.String()) 684 if i < len(p.operations)-1 { 685 b.WriteString(", ") 686 } 687 } 688 b.WriteString("]}") 689 return b.String() 690 } 691 692 // MarshalJSON returns the JSON encoding of a pipeline. 693 func (p Pipeline) MarshalJSON() ([]byte, error) { 694 return json.Marshal(p.operations) 695 } 696 697 // UnmarshalJSON unmarshals JSON-encoded data into a pipeline. 698 func (p *Pipeline) UnmarshalJSON(data []byte) error { 699 var operations []OpUnion 700 if err := json.Unmarshal(data, &operations); err != nil { 701 return err 702 } 703 p.operations = operations 704 return nil 705 } 706 707 // UnmarshalYAML unmarshals YAML-encoded data into a pipeline. 708 func (p *Pipeline) UnmarshalYAML(unmarshal func(interface{}) error) error { 709 var operations []OpUnion 710 if err := unmarshal(&operations); err != nil { 711 return err 712 } 713 p.operations = operations 714 return nil 715 } 716 717 // MarshalYAML returns the YAML representation. 718 func (p Pipeline) MarshalYAML() (interface{}, error) { 719 return p.operations, nil 720 }