|
1 | 1 | #include "common/protobuf/utility.h" |
2 | 2 |
|
| 3 | +#include <limits> |
3 | 4 | #include <numeric> |
4 | 5 |
|
5 | 6 | #include "envoy/protobuf/message_validator.h" |
6 | 7 |
|
7 | 8 | #include "common/common/assert.h" |
8 | 9 | #include "common/common/fmt.h" |
9 | | -#include "common/json/json_loader.h" |
10 | 10 | #include "common/protobuf/message_validator_impl.h" |
11 | 11 | #include "common/protobuf/protobuf.h" |
12 | 12 |
|
@@ -39,6 +39,79 @@ void blockFormat(YAML::Node node) { |
39 | 39 | } |
40 | 40 | } |
41 | 41 |
|
| 42 | +ProtobufWkt::Value parseYamlNode(const YAML::Node& node) { |
| 43 | + ProtobufWkt::Value value; |
| 44 | + switch (node.Type()) { |
| 45 | + case YAML::NodeType::Null: |
| 46 | + value.set_null_value(ProtobufWkt::NULL_VALUE); |
| 47 | + break; |
| 48 | + case YAML::NodeType::Scalar: { |
| 49 | + if (node.Tag() == "!") { |
| 50 | + value.set_string_value(node.as<std::string>()); |
| 51 | + break; |
| 52 | + } |
| 53 | + bool bool_value; |
| 54 | + if (YAML::convert<bool>::decode(node, bool_value)) { |
| 55 | + value.set_bool_value(bool_value); |
| 56 | + break; |
| 57 | + } |
| 58 | + int64_t int_value; |
| 59 | + if (YAML::convert<int64_t>::decode(node, int_value)) { |
| 60 | + if (std::numeric_limits<int32_t>::min() <= int_value && |
| 61 | + std::numeric_limits<int32_t>::max() >= int_value) { |
| 62 | + // We could convert all integer values to string but it will break some stuff relying on |
| 63 | + // ProtobufWkt::Struct itself, only convert small numbers into number_value here. |
| 64 | + value.set_number_value(int_value); |
| 65 | + } else { |
| 66 | + // Proto3 JSON mapping allows use string for integer, this still has to be converted from |
| 67 | + // int_value to support 0x and 0o literals. |
| 68 | + value.set_string_value(std::to_string(int_value)); |
| 69 | + } |
| 70 | + break; |
| 71 | + } |
| 72 | + double double_value; |
| 73 | + if (YAML::convert<double>::decode(node, double_value)) { |
| 74 | + value.set_number_value(double_value); |
| 75 | + break; |
| 76 | + } |
| 77 | + // Otherwise, fall back on string. |
| 78 | + value.set_string_value(node.as<std::string>()); |
| 79 | + break; |
| 80 | + } |
| 81 | + case YAML::NodeType::Sequence: { |
| 82 | + auto& list_values = *value.mutable_list_value()->mutable_values(); |
| 83 | + for (const auto& it : node) { |
| 84 | + *list_values.Add() = parseYamlNode(it); |
| 85 | + } |
| 86 | + break; |
| 87 | + } |
| 88 | + case YAML::NodeType::Map: { |
| 89 | + auto& struct_fields = *value.mutable_struct_value()->mutable_fields(); |
| 90 | + for (const auto& it : node) { |
| 91 | + struct_fields[it.first.as<std::string>()] = parseYamlNode(it.second); |
| 92 | + } |
| 93 | + break; |
| 94 | + } |
| 95 | + case YAML::NodeType::Undefined: |
| 96 | + throw EnvoyException("Undefined YAML value"); |
| 97 | + } |
| 98 | + return value; |
| 99 | +} |
| 100 | + |
| 101 | +void jsonConvertInternal(const Protobuf::Message& source, |
| 102 | + ProtobufMessage::ValidationVisitor& validation_visitor, |
| 103 | + Protobuf::Message& dest) { |
| 104 | + Protobuf::util::JsonPrintOptions json_options; |
| 105 | + json_options.preserve_proto_field_names = true; |
| 106 | + std::string json; |
| 107 | + const auto status = Protobuf::util::MessageToJsonString(source, &json, json_options); |
| 108 | + if (!status.ok()) { |
| 109 | + throw EnvoyException(fmt::format("Unable to convert protobuf message to JSON string: {} {}", |
| 110 | + status.ToString(), source.DebugString())); |
| 111 | + } |
| 112 | + MessageUtil::loadFromJson(json, dest, validation_visitor); |
| 113 | +} |
| 114 | + |
42 | 115 | } // namespace |
43 | 116 |
|
44 | 117 | namespace ProtobufPercentHelper { |
@@ -140,16 +213,21 @@ void MessageUtil::loadFromJson(const std::string& json, ProtobufWkt::Struct& mes |
140 | 213 |
|
141 | 214 | void MessageUtil::loadFromYaml(const std::string& yaml, Protobuf::Message& message, |
142 | 215 | ProtobufMessage::ValidationVisitor& validation_visitor) { |
143 | | - const auto loaded_object = Json::Factory::loadFromYamlString(yaml); |
144 | | - // Load the message if the loaded object has type Object or Array. |
145 | | - if (loaded_object->isObject() || loaded_object->isArray()) { |
146 | | - const std::string json = loaded_object->asJsonString(); |
147 | | - loadFromJson(json, message, validation_visitor); |
| 216 | + ProtobufWkt::Value value = ValueUtil::loadFromYaml(yaml); |
| 217 | + if (value.kind_case() == ProtobufWkt::Value::kStructValue || |
| 218 | + value.kind_case() == ProtobufWkt::Value::kListValue) { |
| 219 | + jsonConvertInternal(value, validation_visitor, message); |
148 | 220 | return; |
149 | 221 | } |
150 | 222 | throw EnvoyException("Unable to convert YAML as JSON: " + yaml); |
151 | 223 | } |
152 | 224 |
|
| 225 | +void MessageUtil::loadFromYaml(const std::string& yaml, ProtobufWkt::Struct& message) { |
| 226 | + // No need to validate if converting to a Struct, since there are no unknown |
| 227 | + // fields possible. |
| 228 | + return loadFromYaml(yaml, message, ProtobufMessage::getNullValidationVisitor()); |
| 229 | +} |
| 230 | + |
153 | 231 | void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& message, |
154 | 232 | ProtobufMessage::ValidationVisitor& validation_visitor, |
155 | 233 | Api::Api& api) { |
@@ -310,7 +388,20 @@ std::string MessageUtil::getYamlStringFromMessage(const Protobuf::Message& messa |
310 | 388 | const bool block_print, |
311 | 389 | const bool always_print_primitive_fields) { |
312 | 390 | std::string json = getJsonStringFromMessage(message, false, always_print_primitive_fields); |
313 | | - auto node = YAML::Load(json); |
| 391 | + YAML::Node node; |
| 392 | + try { |
| 393 | + node = YAML::Load(json); |
| 394 | + } catch (YAML::ParserException& e) { |
| 395 | + throw EnvoyException(e.what()); |
| 396 | + } catch (YAML::BadConversion& e) { |
| 397 | + throw EnvoyException(e.what()); |
| 398 | + } catch (std::exception& e) { |
| 399 | + // There is a potentially wide space of exceptions thrown by the YAML parser, |
| 400 | + // and enumerating them all may be difficult. Envoy doesn't work well with |
| 401 | + // unhandled exceptions, so we capture them and record the exception name in |
| 402 | + // the Envoy Exception text. |
| 403 | + throw EnvoyException(fmt::format("Unexpected YAML exception: {}", +e.what())); |
| 404 | + } |
314 | 405 | if (block_print) { |
315 | 406 | blockFormat(node); |
316 | 407 | } |
@@ -342,24 +433,6 @@ std::string MessageUtil::getJsonStringFromMessage(const Protobuf::Message& messa |
342 | 433 | return json; |
343 | 434 | } |
344 | 435 |
|
345 | | -namespace { |
346 | | - |
347 | | -void jsonConvertInternal(const Protobuf::Message& source, |
348 | | - ProtobufMessage::ValidationVisitor& validation_visitor, |
349 | | - Protobuf::Message& dest) { |
350 | | - Protobuf::util::JsonPrintOptions json_options; |
351 | | - json_options.preserve_proto_field_names = true; |
352 | | - std::string json; |
353 | | - const auto status = Protobuf::util::MessageToJsonString(source, &json, json_options); |
354 | | - if (!status.ok()) { |
355 | | - throw EnvoyException(fmt::format("Unable to convert protobuf message to JSON string: {} {}", |
356 | | - status.ToString(), source.DebugString())); |
357 | | - } |
358 | | - MessageUtil::loadFromJson(json, dest, validation_visitor); |
359 | | -} |
360 | | - |
361 | | -} // namespace |
362 | | - |
363 | 436 | void MessageUtil::jsonConvert(const Protobuf::Message& source, ProtobufWkt::Struct& dest) { |
364 | 437 | // Any proto3 message can be transformed to Struct, so there is no need to check for unknown |
365 | 438 | // fields. There is one catch; Duration/Timestamp etc. which have non-object canonical JSON |
@@ -424,6 +497,22 @@ std::string MessageUtil::CodeEnumToString(ProtobufUtil::error::Code code) { |
424 | 497 | } |
425 | 498 | } |
426 | 499 |
|
| 500 | +ProtobufWkt::Value ValueUtil::loadFromYaml(const std::string& yaml) { |
| 501 | + try { |
| 502 | + return parseYamlNode(YAML::Load(yaml)); |
| 503 | + } catch (YAML::ParserException& e) { |
| 504 | + throw EnvoyException(e.what()); |
| 505 | + } catch (YAML::BadConversion& e) { |
| 506 | + throw EnvoyException(e.what()); |
| 507 | + } catch (std::exception& e) { |
| 508 | + // There is a potentially wide space of exceptions thrown by the YAML parser, |
| 509 | + // and enumerating them all may be difficult. Envoy doesn't work well with |
| 510 | + // unhandled exceptions, so we capture them and record the exception name in |
| 511 | + // the Envoy Exception text. |
| 512 | + throw EnvoyException(fmt::format("Unexpected YAML exception: {}", +e.what())); |
| 513 | + } |
| 514 | +} |
| 515 | + |
427 | 516 | bool ValueUtil::equal(const ProtobufWkt::Value& v1, const ProtobufWkt::Value& v2) { |
428 | 517 | ProtobufWkt::Value::KindCase kind = v1.kind_case(); |
429 | 518 | if (kind != v2.kind_case()) { |
|
0 commit comments