encoder.go 6.4 KB

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