encoder.go 5.9 KB

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