encoder.go 6.3 KB

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