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 }