blob: ae327101af173f90e39f2b74c51a302db6e3d2a7 [file] [log] [blame]
// Copyright 2020 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <grpc/support/port_platform.h>
#include "src/core/lib/json/json_object_loader.h"
#include <algorithm>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/strip.h"
namespace grpc_core {
void ErrorList::PushField(absl::string_view ext) {
// Skip leading '.' for top-level field names.
if (fields_.empty()) absl::ConsumePrefix(&ext, ".");
fields_.emplace_back(std::string(ext));
}
void ErrorList::PopField() { fields_.pop_back(); }
void ErrorList::AddError(absl::string_view error) {
field_errors_[absl::StrJoin(fields_, "")].emplace_back(error);
}
bool ErrorList::FieldHasErrors() const {
return field_errors_.find(absl::StrJoin(fields_, "")) != field_errors_.end();
}
absl::Status ErrorList::status() const {
if (field_errors_.empty()) return absl::OkStatus();
std::vector<std::string> errors;
for (const auto& p : field_errors_) {
if (p.second.size() > 1) {
errors.emplace_back(absl::StrCat("field:", p.first, " errors:[",
absl::StrJoin(p.second, "; "), "]"));
} else {
errors.emplace_back(
absl::StrCat("field:", p.first, " error:", p.second[0]));
}
}
return absl::InvalidArgumentError(absl::StrCat(
"errors validating JSON: [", absl::StrJoin(errors, "; "), "]"));
}
namespace json_detail {
void LoadScalar::LoadInto(const Json& json, const JsonArgs& /*args*/, void* dst,
ErrorList* errors) const {
// We accept either STRING or NUMBER for numeric values, as per
// https://developers.google.com/protocol-buffers/docs/proto3#json.
if (json.type() != Json::Type::STRING &&
(!IsNumber() || json.type() != Json::Type::NUMBER)) {
errors->AddError(
absl::StrCat("is not a ", IsNumber() ? "number" : "string"));
return;
}
return LoadInto(json.string_value(), dst, errors);
}
bool LoadString::IsNumber() const { return false; }
void LoadString::LoadInto(const std::string& value, void* dst,
ErrorList*) const {
*static_cast<std::string*>(dst) = value;
}
bool LoadDuration::IsNumber() const { return false; }
void LoadDuration::LoadInto(const std::string& value, void* dst,
ErrorList* errors) const {
absl::string_view buf(value);
if (!absl::ConsumeSuffix(&buf, "s")) {
errors->AddError("Not a duration (no s suffix)");
return;
}
buf = absl::StripAsciiWhitespace(buf);
auto decimal_point = buf.find('.');
int nanos = 0;
if (decimal_point != absl::string_view::npos) {
absl::string_view after_decimal = buf.substr(decimal_point + 1);
buf = buf.substr(0, decimal_point);
if (!absl::SimpleAtoi(after_decimal, &nanos)) {
errors->AddError("Not a duration (not a number of nanoseconds)");
return;
}
if (after_decimal.length() > 9) {
// We don't accept greater precision than nanos.
errors->AddError("Not a duration (too many digits after decimal)");
return;
}
for (size_t i = 0; i < (9 - after_decimal.length()); ++i) {
nanos *= 10;
}
}
int seconds;
if (!absl::SimpleAtoi(buf, &seconds)) {
errors->AddError("Not a duration (not a number of seconds)");
return;
}
*static_cast<Duration*>(dst) =
Duration::FromSecondsAndNanoseconds(seconds, nanos);
}
bool LoadNumber::IsNumber() const { return true; }
void LoadBool::LoadInto(const Json& json, const JsonArgs&, void* dst,
ErrorList* errors) const {
if (json.type() == Json::Type::JSON_TRUE) {
*static_cast<bool*>(dst) = true;
} else if (json.type() == Json::Type::JSON_FALSE) {
*static_cast<bool*>(dst) = false;
} else {
errors->AddError("is not a boolean");
}
}
void LoadUnprocessedJsonObject::LoadInto(const Json& json, const JsonArgs&,
void* dst, ErrorList* errors) const {
if (json.type() != Json::Type::OBJECT) {
errors->AddError("is not an object");
return;
}
*static_cast<Json::Object*>(dst) = json.object_value();
}
void LoadVector::LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const {
if (json.type() != Json::Type::ARRAY) {
errors->AddError("is not an array");
return;
}
const auto& array = json.array_value();
for (size_t i = 0; i < array.size(); ++i) {
ScopedField field(errors, absl::StrCat("[", i, "]"));
LoadOne(array[i], args, dst, errors);
}
}
void LoadMap::LoadInto(const Json& json, const JsonArgs& args, void* dst,
ErrorList* errors) const {
if (json.type() != Json::Type::OBJECT) {
errors->AddError("is not an object");
return;
}
for (const auto& pair : json.object_value()) {
ScopedField field(errors, absl::StrCat("[\"", pair.first, "\"]"));
LoadOne(pair.second, args, pair.first, dst, errors);
}
}
bool LoadObject(const Json& json, const JsonArgs& args, const Element* elements,
size_t num_elements, void* dst, ErrorList* errors) {
if (json.type() != Json::Type::OBJECT) {
errors->AddError("is not an object");
return false;
}
for (size_t i = 0; i < num_elements; ++i) {
const Element& element = elements[i];
if (element.enable_key != nullptr && !args.IsEnabled(element.enable_key)) {
continue;
}
ScopedField field(errors, absl::StrCat(".", element.name));
const auto& it = json.object_value().find(element.name);
if (it == json.object_value().end()) {
if (element.optional) continue;
errors->AddError("field not present");
continue;
}
char* field_dst = static_cast<char*>(dst) + element.member_offset;
element.loader->LoadInto(it->second, args, field_dst, errors);
}
return true;
}
const Json* GetJsonObjectField(const Json::Object& json,
absl::string_view field, ErrorList* errors,
bool required) {
auto it = json.find(std::string(field));
if (it == json.end()) {
if (required) errors->AddError("field not present");
return nullptr;
}
return &it->second;
}
} // namespace json_detail
} // namespace grpc_core