github.com/Jeffail/benthos/v3@v3.65.0/internal/codec/writer.go (about)

     1  package codec
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  
    11  	"github.com/Jeffail/benthos/v3/internal/docs"
    12  	"github.com/Jeffail/benthos/v3/lib/types"
    13  )
    14  
    15  // WriterDocs is a static field documentation for output codecs.
    16  var WriterDocs = docs.FieldCommon(
    17  	"codec", "The way in which the bytes of messages should be written out into the output data stream. It's possible to write lines using a custom delimiter with the `delim:x` codec, where x is the character sequence custom delimiter.", "lines", "delim:\t", "delim:foobar",
    18  ).HasAnnotatedOptions(
    19  	"all-bytes", "Only applicable to file based outputs. Writes each message to a file in full, if the file already exists the old content is deleted.",
    20  	"append", "Append each message to the output stream without any delimiter or special encoding.",
    21  	"lines", "Append each message to the output stream followed by a line break.",
    22  	"delim:x", "Append each message to the output stream followed by a custom delimiter.",
    23  )
    24  
    25  //------------------------------------------------------------------------------
    26  
    27  // Writer is a codec type that reads message parts from a source.
    28  type Writer interface {
    29  	Write(context.Context, types.Part) error
    30  	Close(context.Context) error
    31  
    32  	// TODO V4: Remove this, we only have it in place in order to satisfy the
    33  	// relatively dodgy empty line at end of batch behaviour.
    34  	EndBatch() error
    35  }
    36  
    37  // WriterConfig contains custom configuration specific to a codec describing how
    38  // handles should be provided.
    39  type WriterConfig struct {
    40  	Append     bool
    41  	Truncate   bool
    42  	CloseAfter bool
    43  }
    44  
    45  // WriterConstructor creates a writer from an io.WriteCloser.
    46  type WriterConstructor func(io.WriteCloser) (Writer, error)
    47  
    48  // GetWriter returns a constructor that creates write codecs.
    49  func GetWriter(codec string) (WriterConstructor, WriterConfig, error) {
    50  	switch codec {
    51  	case "all-bytes":
    52  		return func(w io.WriteCloser) (Writer, error) {
    53  			return &allBytesWriter{w}, nil
    54  		}, allBytesConfig, nil
    55  	case "append":
    56  		return func(w io.WriteCloser) (Writer, error) {
    57  			return newCustomDelimWriter(w, "")
    58  		}, customDelimConfig, nil
    59  	case "lines":
    60  		return newLinesWriter, linesWriterConfig, nil
    61  	}
    62  	if strings.HasPrefix(codec, "delim:") {
    63  		by := strings.TrimPrefix(codec, "delim:")
    64  		if by == "" {
    65  			return nil, WriterConfig{}, errors.New("custom delimiter codec requires a non-empty delimiter")
    66  		}
    67  		return func(w io.WriteCloser) (Writer, error) {
    68  			return newCustomDelimWriter(w, by)
    69  		}, customDelimConfig, nil
    70  	}
    71  	return nil, WriterConfig{}, fmt.Errorf("codec was not recognised: %v", codec)
    72  }
    73  
    74  //------------------------------------------------------------------------------
    75  
    76  var allBytesConfig = WriterConfig{
    77  	Truncate:   true,
    78  	CloseAfter: true,
    79  }
    80  
    81  type allBytesWriter struct {
    82  	o io.WriteCloser
    83  }
    84  
    85  func (a *allBytesWriter) Write(ctx context.Context, msg types.Part) error {
    86  	_, err := a.o.Write(msg.Get())
    87  	return err
    88  }
    89  
    90  func (a *allBytesWriter) EndBatch() error {
    91  	return nil
    92  }
    93  
    94  func (a *allBytesWriter) Close(ctx context.Context) error {
    95  	return a.o.Close()
    96  }
    97  
    98  //------------------------------------------------------------------------------
    99  
   100  var linesWriterConfig = WriterConfig{
   101  	Append: true,
   102  }
   103  
   104  type linesWriter struct {
   105  	w io.WriteCloser
   106  }
   107  
   108  func newLinesWriter(w io.WriteCloser) (Writer, error) {
   109  	return &linesWriter{w: w}, nil
   110  }
   111  
   112  func (l *linesWriter) Write(ctx context.Context, p types.Part) error {
   113  	partBytes := p.Get()
   114  	if _, err := l.w.Write(partBytes); err != nil {
   115  		return err
   116  	}
   117  	if !bytes.HasSuffix(partBytes, []byte("\n")) {
   118  		_, err := l.w.Write([]byte("\n"))
   119  		return err
   120  	}
   121  	return nil
   122  }
   123  
   124  func (l *linesWriter) EndBatch() error {
   125  	_, err := l.w.Write([]byte("\n"))
   126  	return err
   127  }
   128  
   129  func (l *linesWriter) Close(ctx context.Context) error {
   130  	return l.w.Close()
   131  }
   132  
   133  //------------------------------------------------------------------------------
   134  
   135  var customDelimConfig = WriterConfig{
   136  	Append: true,
   137  }
   138  
   139  type customDelimWriter struct {
   140  	w     io.WriteCloser
   141  	delim []byte
   142  }
   143  
   144  func newCustomDelimWriter(w io.WriteCloser, delim string) (Writer, error) {
   145  	delimBytes := []byte(delim)
   146  	return &customDelimWriter{w: w, delim: delimBytes}, nil
   147  }
   148  
   149  func (d *customDelimWriter) Write(ctx context.Context, p types.Part) error {
   150  	partBytes := p.Get()
   151  	if _, err := d.w.Write(partBytes); err != nil {
   152  		return err
   153  	}
   154  	if !bytes.HasSuffix(partBytes, d.delim) {
   155  		_, err := d.w.Write(d.delim)
   156  		return err
   157  	}
   158  	return nil
   159  }
   160  
   161  func (d *customDelimWriter) EndBatch() error {
   162  	_, err := d.w.Write(d.delim)
   163  	return err
   164  }
   165  
   166  func (d *customDelimWriter) Close(ctx context.Context) error {
   167  	return d.w.Close()
   168  }