1// Package json provides a JSON formatter implementation. 2package json 3 4import ( 5 "bytes" 6 gojson "encoding/json" 7 "io" 8 9 "github.com/golang/protobuf/jsonpb" //nolint:staticcheck 10 "github.com/golang/protobuf/proto" //nolint:staticcheck 11 "github.com/golang/protobuf/ptypes" 12 "github.com/ktr0731/evans/format" 13 "github.com/ktr0731/evans/present" 14 "github.com/ktr0731/evans/present/json" 15 "github.com/pkg/errors" 16 _ "google.golang.org/genproto/googleapis/rpc/errdetails" // For calling RegisterType. 17 "google.golang.org/grpc/metadata" 18 "google.golang.org/grpc/status" 19) 20 21// responseFormatter is a formatter that formats *usecase.GRPCResponse into a JSON object. 22type responseFormatter struct { 23 w io.Writer 24 s struct { 25 Status struct { 26 Code string `json:"code"` 27 Number uint32 `json:"number"` 28 Message string `json:"message"` 29 Details []interface{} `json:"details,omitempty"` 30 } `json:"status,omitempty"` 31 Header *metadata.MD `json:"header,omitempty"` 32 Messages []map[string]interface{} `json:"messages,omitempty"` 33 Trailer *metadata.MD `json:"trailer,omitempty"` 34 } 35 p present.Presenter 36 pbMarshaler *jsonpb.Marshaler 37} 38 39func NewResponseFormatter(w io.Writer) format.ResponseFormatterInterface { 40 return &responseFormatter{w: w, p: json.NewPresenter(" "), pbMarshaler: &jsonpb.Marshaler{}} 41} 42 43func (p *responseFormatter) FormatHeader(header metadata.MD) { 44 p.s.Header = &header 45} 46 47func (p *responseFormatter) FormatMessage(v interface{}) error { 48 m, err := p.convertProtoMessageToMap(v.(proto.Message)) 49 if err != nil { 50 return err 51 } 52 p.s.Messages = append(p.s.Messages, m) 53 return nil 54} 55 56func (p *responseFormatter) FormatTrailer(trailer metadata.MD) { 57 p.s.Trailer = &trailer 58} 59 60func (p *responseFormatter) FormatStatus(s *status.Status) error { 61 var details []interface{} 62 if len(s.Details()) != 0 { 63 details = make([]interface{}, 0, len(s.Details())) 64 for _, d := range s.Details() { 65 d, ok := d.(proto.Message) 66 if !ok { 67 continue 68 } 69 // Convert to Any to insert @type field. 70 m, err := p.convertProtoMessageAsAnyToMap(d) 71 if err != nil { 72 return err 73 } 74 details = append(details, m) 75 } 76 } 77 78 p.s.Status = struct { 79 Code string `json:"code"` 80 Number uint32 `json:"number"` 81 Message string `json:"message"` 82 Details []interface{} `json:"details,omitempty"` 83 }{ 84 Code: s.Code().String(), 85 Number: uint32(s.Code()), 86 Message: s.Message(), 87 Details: details, 88 } 89 return nil 90} 91 92func (p *responseFormatter) Done() error { 93 s, err := p.p.Format(p.s) 94 if err != nil { 95 return err 96 } 97 _, err = io.WriteString(p.w, s+"\n") 98 return err 99} 100 101func (p *responseFormatter) convertProtoMessageToMap(m proto.Message) (map[string]interface{}, error) { 102 var buf bytes.Buffer 103 err := p.pbMarshaler.Marshal(&buf, m) 104 if err != nil { 105 return nil, err 106 } 107 var res map[string]interface{} 108 if err := gojson.Unmarshal(buf.Bytes(), &res); err != nil { 109 return nil, err 110 } 111 return res, nil 112} 113 114func (p *responseFormatter) convertProtoMessageAsAnyToMap(m proto.Message) (map[string]interface{}, error) { 115 any, err := ptypes.MarshalAny(m) 116 if err != nil { 117 return nil, errors.Wrap(err, "failed to convert a message to *any.Any") 118 } 119 return p.convertProtoMessageToMap(any) 120} 121