1
#include "source/extensions/content_parsers/json/json_content_parser_impl.h"
2

            
3
#include "source/common/json/json_loader.h"
4

            
5
#include "absl/strings/str_cat.h"
6

            
7
namespace Envoy {
8
namespace Extensions {
9
namespace ContentParsers {
10
namespace Json {
11

            
12
namespace {
13
constexpr absl::string_view DefaultNamespace = "envoy.content_parsers.json";
14
}
15

            
16
JsonContentParserImpl::Rule::Rule(const ProtoRule& rule, uint32_t stop_processing_after_matches)
17
185
    : rule_(rule), stop_processing_after_matches_(stop_processing_after_matches) {
18
314
  for (const auto& selector : rule_.selectors()) {
19
314
    selector_path_.push_back(selector.key());
20
314
  }
21
185
}
22

            
23
JsonContentParserImpl::JsonContentParserImpl(
24
135
    const envoy::extensions::content_parsers::json::v3::JsonContentParser& config) {
25
135
  rules_.reserve(config.rules().size());
26
185
  for (const auto& rule_config : config.rules()) {
27
185
    rules_.emplace_back(rule_config.rule(), rule_config.stop_processing_after_matches());
28
185
  }
29
135
}
30

            
31
204
ContentParser::ParseResult JsonContentParserImpl::parse(absl::string_view data) {
32
204
  ContentParser::ParseResult result;
33

            
34
  // Try to parse JSON
35
204
  auto json_or = Envoy::Json::Factory::loadFromString(std::string(data));
36
204
  if (!json_or.ok()) {
37
40
    ENVOY_LOG(trace, "Failed to parse JSON: {}", json_or.status().message());
38
40
    result.error_message = std::string(json_or.status().message());
39
40
    any_parse_error_ = true;
40
40
    return result;
41
40
  }
42
164
  Envoy::Json::ObjectSharedPtr json_obj = json_or.value();
43

            
44
  // Apply each rule and track stop processing condition.
45
  // We stop processing when all rules have limits and all those limits have been reached.
46
  // If any rule has no limit (stop_processing_after_matches == 0), we never stop.
47
164
  bool all_rules_have_limits = true;
48
164
  bool all_limited_rules_satisfied = true;
49

            
50
449
  for (size_t i = 0; i < rules_.size(); ++i) {
51
285
    auto& rule = rules_[i];
52

            
53
    // Track if any rule has no limit
54
285
    if (rule.stop_processing_after_matches_ == 0) {
55
273
      all_rules_have_limits = false;
56
273
    }
57

            
58
    // Skip rules that have already reached their match limit
59
285
    if (rule.stop_processing_after_matches_ > 0 &&
60
285
        rule.match_count_ >= rule.stop_processing_after_matches_) {
61
6
      continue;
62
6
    }
63

            
64
279
    auto value_or = extractValueFromJson(json_obj, rule.selector_path_);
65

            
66
279
    if (value_or.ok()) {
67
      // Selector found. Execute on_present immediately (if configured).
68
180
      const auto& value = value_or.value();
69

            
70
180
      if (rule.rule_.has_on_present()) {
71
180
        result.immediate_actions.push_back(keyValuePairToAction(rule.rule_.on_present(), value));
72
180
      }
73

            
74
      // Track that this rule matched
75
180
      rule.ever_matched_ = true;
76

            
77
      // Increment match count for this rule
78
180
      rule.match_count_++;
79
180
    } else {
80
      // Selector not found
81
99
      ENVOY_LOG(trace, "Selector not found: {}", value_or.status().message());
82
99
      rule.selector_not_found_ = true;
83
99
    }
84

            
85
    // After processing, check if this rule with a limit is still not satisfied
86
279
    if (rule.stop_processing_after_matches_ > 0 &&
87
279
        rule.match_count_ < rule.stop_processing_after_matches_) {
88
1
      all_limited_rules_satisfied = false;
89
1
    }
90
279
  }
91

            
92
164
  result.stop_processing = all_rules_have_limits && all_limited_rules_satisfied;
93
164
  return result;
94
204
}
95

            
96
74
std::vector<ContentParser::MetadataAction> JsonContentParserImpl::getAllDeferredActions() {
97
74
  std::vector<ContentParser::MetadataAction> actions;
98

            
99
113
  for (const auto& rule : rules_) {
100
    // Only process rules that never matched (on_present never fired)
101
113
    if (rule.ever_matched_) {
102
100
      continue;
103
100
    }
104

            
105
    // Priority: on_error over on_missing.
106
    // If any_parse_error_ is true but on_error is not configured, fall through to on_missing.
107
    // This allows users to handle missing values even when parse errors occur,
108
    // if they choose not to configure on_error handling.
109
13
    if (any_parse_error_ && rule.rule_.has_on_error()) {
110
4
      actions.push_back(keyValuePairToAction(rule.rule_.on_error(), absl::nullopt));
111
9
    } else if (rule.selector_not_found_ && rule.rule_.has_on_missing()) {
112
4
      actions.push_back(keyValuePairToAction(rule.rule_.on_missing(), absl::nullopt));
113
4
    }
114
13
  }
115

            
116
74
  return actions;
117
74
}
118

            
119
absl::StatusOr<Envoy::Json::ValueType>
120
JsonContentParserImpl::extractValueFromJson(const Envoy::Json::ObjectSharedPtr& json_obj,
121
279
                                            const std::vector<std::string>& path) const {
122
279
  if (path.empty()) {
123
    return absl::InvalidArgumentError("Path is empty");
124
  }
125

            
126
279
  Envoy::Json::ObjectSharedPtr current = json_obj;
127

            
128
  // Traverse path except last element
129
362
  for (size_t i = 0; i < path.size() - 1; ++i) {
130
178
    auto child_or = current->getObject(path[i]);
131
178
    if (!child_or.ok()) {
132
95
      return absl::NotFoundError(
133
95
          absl::StrCat("Key '", path[i], "' not found or not an object at path index ", i));
134
95
    }
135
83
    current = child_or.value();
136
83
  }
137

            
138
184
  const std::string& final_key = path.back();
139

            
140
  // Try to extract value as different types
141
184
  auto string_val = current->getString(final_key);
142
184
  if (string_val.ok()) {
143
84
    return Envoy::Json::ValueType{string_val.value()};
144
84
  }
145

            
146
100
  auto int_val = current->getInteger(final_key);
147
100
  if (int_val.ok()) {
148
89
    return Envoy::Json::ValueType{int_val.value()};
149
89
  }
150

            
151
11
  auto double_val = current->getDouble(final_key);
152
11
  if (double_val.ok()) {
153
3
    return Envoy::Json::ValueType{double_val.value()};
154
3
  }
155

            
156
8
  auto bool_val = current->getBoolean(final_key);
157
8
  if (bool_val.ok()) {
158
3
    return Envoy::Json::ValueType{bool_val.value()};
159
3
  }
160

            
161
  // Try to extract as nested object and stringify
162
5
  auto obj_val = current->getObject(final_key);
163
5
  if (obj_val.ok()) {
164
1
    return Envoy::Json::ValueType{obj_val.value()->asJsonString()};
165
1
  }
166

            
167
4
  return absl::NotFoundError(absl::StrCat("Key '", final_key, "' not found"));
168
5
}
169

            
170
ContentParser::MetadataAction JsonContentParserImpl::keyValuePairToAction(
171
    const KeyValuePair& kv_pair,
172
188
    const absl::optional<Envoy::Json::ValueType>& extracted_value) const {
173
188
  ContentParser::MetadataAction action;
174

            
175
  // Namespace: Parser is responsible for applying the default namespace.
176
  // The filter expects namespace to always be populated.
177
188
  action.namespace_ = kv_pair.metadata_namespace().empty() ? std::string(DefaultNamespace)
178
188
                                                           : kv_pair.metadata_namespace();
179
188
  action.key = kv_pair.key();
180
188
  action.preserve_existing = kv_pair.preserve_existing_metadata_value();
181

            
182
  // Populate the metadata value:
183
  // 1. If kv_pair has an explicit 'value' field (kValue), use that constant value.
184
  //    This allows hardcoded values for on_error/on_missing fallbacks, or for
185
  //    on_present when only presence detection is needed (ignoring extracted value).
186
  // 2. Otherwise, if we extracted a value from JSON, convert it using the specified type.
187
  // 3. If neither, action.value remains default-constructed (empty).
188
188
  if (kv_pair.value_type_case() == KeyValuePair::kValue) {
189
8
    action.value = kv_pair.value();
190
180
  } else if (extracted_value.has_value()) {
191
179
    action.value = jsonValueToProtobufValue(extracted_value.value(), kv_pair.type());
192
179
  }
193

            
194
188
  return action;
195
188
}
196

            
197
Protobuf::Value JsonContentParserImpl::jsonValueToProtobufValue(const Envoy::Json::ValueType& value,
198
179
                                                                ValueType type) const {
199
179
  Protobuf::Value pb_value;
200

            
201
179
  switch (type) {
202
84
  case ValueType::JsonToMetadata_ValueType_STRING:
203
    // Always convert to string
204
84
    absl::visit(
205
84
        [&pb_value](const auto& v) {
206
84
          using T = std::decay_t<decltype(v)>;
207
84
          if constexpr (std::is_same_v<T, std::string>) {
208
80
            pb_value.set_string_value(v);
209
80
          } else if constexpr (std::is_same_v<T, int64_t>) {
210
2
            pb_value.set_string_value(absl::StrCat(v));
211
2
          } else if constexpr (std::is_same_v<T, double>) {
212
1
            pb_value.set_string_value(absl::StrCat(v));
213
1
          } else if constexpr (std::is_same_v<T, bool>) {
214
1
            pb_value.set_string_value(v ? "true" : "false");
215
1
          }
216
84
        },
217
84
        value);
218
84
    break;
219

            
220
87
  case ValueType::JsonToMetadata_ValueType_NUMBER:
221
    // Convert to number
222
87
    absl::visit(
223
87
        [&pb_value](const auto& v) {
224
87
          using T = std::decay_t<decltype(v)>;
225
87
          if constexpr (std::is_same_v<T, int64_t>) {
226
82
            pb_value.set_number_value(static_cast<double>(v));
227
82
          } else if constexpr (std::is_same_v<T, double>) {
228
1
            pb_value.set_number_value(v);
229
1
          } else if constexpr (std::is_same_v<T, bool>) {
230
1
            pb_value.set_number_value(v ? 1.0 : 0.0);
231
3
          } else if constexpr (std::is_same_v<T, std::string>) {
232
            // Try to parse string as number
233
3
            double num;
234
3
            if (absl::SimpleAtod(v, &num)) {
235
1
              pb_value.set_number_value(num);
236
2
            } else {
237
              // If conversion fails, leave pb_value unset (kind_case == 0)
238
2
              ENVOY_LOG_MISC(debug, "Failed to convert string '{}' to NUMBER type", v);
239
2
            }
240
3
          }
241
87
        },
242
87
        value);
243
87
    break;
244

            
245
8
  case ValueType::JsonToMetadata_ValueType_PROTOBUF_VALUE:
246
8
  default:
247
    // Preserve original type
248
8
    absl::visit(
249
8
        [&pb_value](const auto& v) {
250
8
          using T = std::decay_t<decltype(v)>;
251
8
          if constexpr (std::is_same_v<T, std::string>) {
252
2
            pb_value.set_string_value(v);
253
4
          } else if constexpr (std::is_same_v<T, int64_t>) {
254
4
            pb_value.set_number_value(static_cast<double>(v));
255
4
          } else if constexpr (std::is_same_v<T, double>) {
256
1
            pb_value.set_number_value(v);
257
1
          } else if constexpr (std::is_same_v<T, bool>) {
258
1
            pb_value.set_bool_value(v);
259
1
          }
260
8
        },
261
8
        value);
262
8
    break;
263
179
  }
264

            
265
179
  return pb_value;
266
179
}
267

            
268
JsonContentParserFactory::JsonContentParserFactory(
269
    const envoy::extensions::content_parsers::json::v3::JsonContentParser& config)
270
106
    : config_(config) {}
271

            
272
103
ContentParser::ParserPtr JsonContentParserFactory::createParser() {
273
103
  return std::make_unique<JsonContentParserImpl>(config_);
274
103
}
275

            
276
105
const std::string& JsonContentParserFactory::statsPrefix() const {
277
105
  CONSTRUCT_ON_FIRST_USE(std::string, "json.");
278
105
}
279

            
280
} // namespace Json
281
} // namespace ContentParsers
282
} // namespace Extensions
283
} // namespace Envoy