8
8
use function base64_decode ;
9
9
use function bin2hex ;
10
10
use Exception ;
11
+ use function get_class ;
12
+ use Google \Protobuf \Descriptor ;
13
+ use Google \Protobuf \DescriptorPool ;
14
+ use Google \Protobuf \FieldDescriptor ;
15
+ use Google \Protobuf \Internal \GPBLabel ;
16
+ use Google \Protobuf \Internal \GPBType ;
11
17
use Google \Protobuf \Internal \Message ;
12
18
use InvalidArgumentException ;
19
+ use function json_decode ;
20
+ use function json_encode ;
21
+ use const JSON_UNESCAPED_SLASHES ;
22
+ use const JSON_UNESCAPED_UNICODE ;
23
+ use function lcfirst ;
13
24
use OpenTelemetry \SDK \Common \Export \TransportInterface ;
25
+ use function property_exists ;
14
26
use function sprintf ;
27
+ use function ucwords ;
15
28
16
29
/**
17
30
* @internal
@@ -83,10 +96,9 @@ public function serialize(Message $message): string
83
96
case self ::PROTOBUF :
84
97
return $ message ->serializeToString ();
85
98
case self ::JSON :
86
- //@todo https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#json-protobuf-encoding
87
- return $ message ->serializeToJsonString ();
99
+ return self ::postProcessJsonEnumValues ($ message , $ message ->serializeToJsonString ());
88
100
case self ::NDJSON :
89
- return $ message ->serializeToJsonString () . "\n" ;
101
+ return self :: postProcessJsonEnumValues ( $ message, $ message ->serializeToJsonString () ) . "\n" ;
90
102
default :
91
103
throw new AssertionError ();
92
104
}
@@ -112,4 +124,69 @@ public function hydrate(Message $message, string $payload): void
112
124
throw new AssertionError ();
113
125
}
114
126
}
127
+
128
+ /**
129
+ * Workaround until protobuf exposes `FormatEnumsAsIntegers` option.
130
+ *
131
+ * [JSON Protobuf Encoding](https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding):
132
+ * > Values of enum fields MUST be encoded as integer values.
133
+ *
134
+ * @see https://github.com/open-telemetry/opentelemetry-php/issues/978
135
+ * @see https://github.com/protocolbuffers/protobuf/pull/12707
136
+ */
137
+ private static function postProcessJsonEnumValues (Message $ message , string $ payload ): string
138
+ {
139
+ $ pool = DescriptorPool::getGeneratedPool ();
140
+ $ desc = $ pool ->getDescriptorByClassName (get_class ($ message ));
141
+ if (!$ desc instanceof Descriptor) {
142
+ return $ payload ;
143
+ }
144
+
145
+ $ data = json_decode ($ payload );
146
+ unset($ payload );
147
+ self ::traverseDescriptor ($ data , $ desc );
148
+
149
+ return json_encode ($ data , JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
150
+ }
151
+
152
+ private static function traverseDescriptor (object $ data , Descriptor $ desc ): void
153
+ {
154
+ for ($ i = 0 , $ n = $ desc ->getFieldCount (); $ i < $ n ; $ i ++) {
155
+ // @phan-suppress-next-line PhanParamTooManyInternal
156
+ $ field = $ desc ->getField ($ i );
157
+ $ name = lcfirst (strtr (ucwords ($ field ->getName (), '_ ' ), ['_ ' => '' ]));
158
+ if (!property_exists ($ data , $ name )) {
159
+ continue ;
160
+ }
161
+
162
+ if ($ field ->getLabel () === GPBLabel::REPEATED ) {
163
+ foreach ($ data ->$ name as $ key => $ value ) {
164
+ $ data ->$ name [$ key ] = self ::traverseFieldDescriptor ($ value , $ field );
165
+ }
166
+ } else {
167
+ $ data ->$ name = self ::traverseFieldDescriptor ($ data ->$ name , $ field );
168
+ }
169
+ }
170
+ }
171
+
172
+ private static function traverseFieldDescriptor ($ data , FieldDescriptor $ field )
173
+ {
174
+ switch ($ field ->getType ()) {
175
+ case GPBType::MESSAGE :
176
+ self ::traverseDescriptor ($ data , $ field ->getMessageType ());
177
+
178
+ break ;
179
+ case GPBType::ENUM :
180
+ $ enum = $ field ->getEnumType ();
181
+ for ($ i = 0 , $ n = $ enum ->getValueCount (); $ i < $ n ; $ i ++) {
182
+ if ($ data === $ enum ->getValue ($ i )->getName ()) {
183
+ return $ enum ->getValue ($ i )->getNumber ();
184
+ }
185
+ }
186
+
187
+ break ;
188
+ }
189
+
190
+ return $ data ;
191
+ }
115
192
}
0 commit comments