encoder.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package exporter
  2. import (
  3. "bytes"
  4. "compress/gzip"
  5. "encoding"
  6. "fmt"
  7. "github.com/opencost/opencost/core/pkg/util/json"
  8. "google.golang.org/protobuf/encoding/protojson"
  9. "google.golang.org/protobuf/proto"
  10. )
  11. const (
  12. BingenExt = "bin"
  13. BingenVersionExtFMT = "v%d.bin"
  14. JSONExt = "json"
  15. GZipExt = ".gz"
  16. PBExt = "binpb"
  17. )
  18. // Encoder[T] is a generic interface for encoding an instance of a T type into a byte slice.
  19. type Encoder[T any] interface {
  20. Encode(*T) ([]byte, error)
  21. // FileExt returns the file extension for the encoded data. This can be used by a pathing strategy
  22. // to append the file extension when exporting the data. Returning an empty string will typically
  23. // omit the file extension completely.
  24. FileExt() string
  25. }
  26. // BinaryMarshalerPtr[T] is a generic constraint to ensure types passed to the encoder implement
  27. // encoding.BinaryMarshaler and are pointers to T.
  28. type BinaryMarshalerPtr[T any] interface {
  29. encoding.BinaryMarshaler
  30. *T
  31. }
  32. // BingenEncoder[T, U] is a generic encoder that uses the BinaryMarshaler interface to encode data.
  33. // It supports any type T that implements the encoding.BinaryMarshaler interface.
  34. type BingenEncoder[T any, U BinaryMarshalerPtr[T]] struct {
  35. fileExt string
  36. }
  37. // NewBingenEncoder creates an `Encoder[T]` implementation which supports binary encoding for the `T`
  38. // type.
  39. func NewBingenEncoder[T any, U BinaryMarshalerPtr[T]]() Encoder[T] {
  40. return new(BingenEncoder[T, U])
  41. }
  42. func NewVersionBingenEncoder[T any, U BinaryMarshalerPtr[T]](version uint8) Encoder[T] {
  43. be := new(BingenEncoder[T, U])
  44. be.fileExt = fmt.Sprintf(BingenVersionExtFMT, version)
  45. return be
  46. }
  47. // Encode encodes the provided data of type T into a byte slice using the BinaryMarshaler interface.
  48. func (b *BingenEncoder[T, U]) Encode(data *T) ([]byte, error) {
  49. var bingenData U = data
  50. return bingenData.MarshalBinary()
  51. }
  52. // FileExt returns the file extension for the encoded data. In this case, it returns an empty string
  53. // to indicate that there is no specific file extension for the binary encoded data.
  54. func (b *BingenEncoder[T, U]) FileExt() string {
  55. return b.fileExt
  56. }
  57. // JSONEncoder[T] is a generic encoder that uses the JSON encoding format to encode data.
  58. type JSONEncoder[T any] struct{}
  59. // NewJSONEncoder creates an `Encoder[T]` implementation which supports JSON encoding for the `T`
  60. // type.
  61. func NewJSONEncoder[T any]() Encoder[T] {
  62. return new(JSONEncoder[T])
  63. }
  64. // Encode encodes the provided data of type T into a byte slice using JSON encoding.
  65. func (j *JSONEncoder[T]) Encode(data *T) ([]byte, error) {
  66. return json.Marshal(data)
  67. }
  68. // FileExt returns the file extension for the encoded data. In this case, it returns "json" to indicate
  69. // that the data is in JSON format.
  70. func (j *JSONEncoder[T]) FileExt() string {
  71. return JSONExt
  72. }
  73. type GZipEncoder[T any] struct {
  74. encoder Encoder[T]
  75. }
  76. // NewGZipEncoder creates a new GZip encoder which wraps the provided encoder.
  77. // The encoder is used to encode the data before compressing it with GZip.
  78. func NewGZipEncoder[T any](encoder Encoder[T]) Encoder[T] {
  79. return &GZipEncoder[T]{
  80. encoder: encoder,
  81. }
  82. }
  83. // Encode encodes the provided data of type T into a byte slice using JSON encoding.
  84. func (gz *GZipEncoder[T]) Encode(data *T) ([]byte, error) {
  85. encoded, err := gz.encoder.Encode(data)
  86. if err != nil {
  87. return nil, fmt.Errorf("GZipEncoder: nested encode failure: %w", err)
  88. }
  89. compressed, err := gZipEncode(encoded)
  90. if err != nil {
  91. return nil, fmt.Errorf("GZipEncoder: failed to compress encoded data: %w", err)
  92. }
  93. return compressed, nil
  94. }
  95. func gZipEncode(data []byte) ([]byte, error) {
  96. var buf bytes.Buffer
  97. gzWriter, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
  98. if err != nil {
  99. return nil, err
  100. }
  101. gzWriter.Write(data)
  102. gzWriter.Close()
  103. return buf.Bytes(), nil
  104. }
  105. // FileExt returns the file extension for the encoded data. In this case, it returns the wrapped encoder's
  106. // file extension with ".gz" appended to indicate that the data is compressed with GZip.
  107. func (gz *GZipEncoder[T]) FileExt() string {
  108. return gz.encoder.FileExt() + GZipExt
  109. }
  110. // ProtoMessagePtr [T] is a generic constraint to ensure types passed to the encoder implement
  111. // proto.Message and are pointers to T.
  112. type ProtoMessagePtr[T any] interface {
  113. proto.Message
  114. *T
  115. }
  116. // ProtobufEncoder [T, U] is a generic encoder that uses the proto.Message interface to encode data.
  117. // It supports any type T that implements the proto.Message interface.
  118. type ProtobufEncoder[T any, U ProtoMessagePtr[T]] struct{}
  119. // NewProtobufEncoder creates an `Encoder[T]` implementation which supports binary encoding for the `T`
  120. // type.
  121. func NewProtobufEncoder[T any, U ProtoMessagePtr[T]]() Encoder[T] {
  122. return new(ProtobufEncoder[T, U])
  123. }
  124. // Encode encodes the provided data of type T into a byte slice using the proto.Message interface.
  125. func (p *ProtobufEncoder[T, U]) Encode(data *T) ([]byte, error) {
  126. var message U = data
  127. raw, err := proto.Marshal(message)
  128. if err != nil {
  129. return nil, fmt.Errorf("failed to encode protobuf message: %w", err)
  130. }
  131. return raw, nil
  132. }
  133. // FileExt returns the file extension for the encoded data. In this case, it returns an empty string
  134. // to indicate that there is no specific file extension for the binary encoded data.
  135. func (p *ProtobufEncoder[T, U]) FileExt() string {
  136. return PBExt
  137. }
  138. // ProtoJsonEncoder [T, U] is a generic encoder that uses the proto.Message interface to encode data in json format.
  139. // It supports any type T that implements the proto.Message interface.
  140. type ProtoJsonEncoder[T any, U ProtoMessagePtr[T]] struct{}
  141. // NewProtoJsonEncoder creates an `Encoder[T]` implementation which supports binary encoding for the `T`
  142. // type.
  143. func NewProtoJsonEncoder[T any, U ProtoMessagePtr[T]]() Encoder[T] {
  144. return new(ProtoJsonEncoder[T, U])
  145. }
  146. // Encode encodes the provided data of type T into a byte slice using the proto.Message interface.
  147. func (p *ProtoJsonEncoder[T, U]) Encode(data *T) ([]byte, error) {
  148. var message U = data
  149. raw, err := protojson.Marshal(message)
  150. if err != nil {
  151. return nil, fmt.Errorf("failed to encode protobuf message to json: %w", err)
  152. }
  153. return raw, nil
  154. }
  155. // FileExt returns the file extension for the encoded data. In this case, it returns an empty string
  156. // to indicate that there is no specific file extension for the binary encoded data.
  157. func (p *ProtoJsonEncoder[T, U]) FileExt() string {
  158. return JSONExt
  159. }