-
Notifications
You must be signed in to change notification settings - Fork 568
/
interface.go
135 lines (124 loc) · 5.55 KB
/
interface.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Package serde contains Pachyderm-specific data structures for marshalling and
// unmarshalling Go structs and maps to structured text formats (currently just
// JSON and YAML).
//
// Similar to https://github.com/ghodss/yaml, all implementations of the Format
// interface marshal and unmarshal data using the following pipeline:
//
// Go struct/map (fully structured)
// <-> JSON document
// <-> map[string]interface{}
// <-> target format document
//
// Despite the redundant round of marshalling and unmarshalling, there are two
// main advantages to this approach:
// - YAML (and any future storage formats) can re-use existing `json:` struct
// tags
// - The intermediate map[string]interface{} can be manipulated, making it
// possible to have flexible converstions between structs and documents. For
// examples, PPS pipelines may include a full TFJob spec, which is converted
// to a string and stored in the 'TFJob' field of Pachyderm's
// CreatePipelineRequest struct.
package serde
import (
"io"
"strings"
"google.golang.org/protobuf/proto"
"github.com/pachyderm/pachyderm/v2/src/internal/errors"
)
// EncoderOption modifies the behavior of new encoders and can be passed to
// GetEncoder.
type EncoderOption func(Encoder)
// WithOrigName is an EncoderOption that, if set, encodes proto messages using
// the name set in the original .proto message definition, rather than the
// munged version of the generated struct's field name
func WithOrigName(origName bool) func(d Encoder) {
return func(d Encoder) {
switch e := d.(type) {
case *YAMLEncoder:
e.origName = origName
case *JSONEncoder:
e.origName = origName
}
}
}
// WithIndent is an EncoderOption that causes the returned encoder to use
// 'numSpaces' spaces as the indentation prefix for embedded messages. If
// applied to a JSON encoder, it also changes the encoder output to be
// multi-line (instead of inline).
func WithIndent(numSpaces int) func(d Encoder) {
return func(d Encoder) {
switch e := d.(type) {
case *YAMLEncoder:
e.e.SetIndent(numSpaces)
case *JSONEncoder:
e.e.SetIndent("", strings.Repeat(" ", numSpaces))
e.indentSpaces = numSpaces // used by EncodeProto shortcut
}
}
}
// GetEncoder dynamically creates and returns an Encoder for the text format
// 'encoding' (currently, 'encoding' must be "yaml" or "json").
// 'defaultEncoding' specifies the text format that should be used if 'encoding'
// is "". 'opts' are the list of options that should be applied to any result,
// if any are applicable. Typically EncoderOptions are encoder-specific (e.g.
// setting json indentation). If an option is passed to GetEncoder for e.g. a
// json encoder but a yaml encoder is requested, then the option will be
// ignored. This makes it possible to pass all options to GetEncoder, but defer
// the decision about what kind of encoding to use until runtime, like so:
//
// enc, _ := GetEncoder(outputFlag, os.Stdout,
//
// ...options to use if json...,
// ...options to use if yaml...,
//
// )
// enc.Encode(obj)
func GetEncoder(encoding string, w io.Writer, opts ...EncoderOption) (Encoder, error) {
switch encoding {
case "yaml":
return NewYAMLEncoder(w, opts...), nil
case "json":
return NewJSONEncoder(w, opts...), nil
default:
return nil, errors.Errorf("unrecognized encoding: %q (must be \"yaml\" or \"json\")", encoding)
}
}
// Encoder is an interface for encoding data to an output stream (every
// implementation should provide the ability to construct an Encoder tied to an
// output stream, to which encoded text should be written)
type Encoder interface {
// Marshall converts 'v' (a struct or Go map) to a structured-text document
// and writes it to this Encoder's output stream
Encode(v interface{}) error
// EncodeProto is similar to Encode, but instead of converting between the canonicalized
// JSON and 'v' using 'encoding/json', it does so using
// 'google.golang.org/protobuf/encoding/protojson'. This allows callers to take advantage
// of more sophisticated timestamp parsing and such.
//
// TODO(msteffen): We can *almost* avoid the Encode/EncodeProto split by checking if 'v'
// implements 'proto.Message', except for one case: the kubernetes client library includes
// structs that are pseudo-protobufs. Structs in the kubernetes go client implement the
// 'proto.Message()' interface but are hand-generated and contain embedded structs, which
// 'jsonpb' can't handle when parsing. If jsonpb is ever extended to be able to parse JSON
// into embedded structs (even though the protobuf compiler itself never generates such
// structs) then we could merge this into Encode() and rely on:
//
// if msg, ok := v.(proto.Message); ok {
// ... use jsonpb ...
// } else {
// ... use encoding/json ...
// }
EncodeProto(proto.Message) error
// EncodeTransform is similar to Encode, but users can manipulate the intermediate
// map[string]interface generated by Format implementations by passing a function. Note that
// 'Encode(v)' is equivalent to 'EncodeTransform(v, nil)'
EncodeTransform(interface{}, func(map[string]interface{}) error) error
// EncodeProtoTransform is similar to EncodeTransform(), but instead of converting between
// the canonicalized JSON and 'v' using 'encoding/json', it does so using
// 'google.golang.org/protobuf/encoding/protojson'. This allows callers to take advantage
// advantage of more sophisticated timestamp parsing and such in the 'jsonpb' library.
//
// TODO(msteffen) same comment re: proto.Message as for EncodeProto()
EncodeProtoTransform(proto.Message, func(map[string]interface{}) error) error
}