NAH 2.0.16
Native Application Host - Library API Reference
Loading...
Searching...
No Matches
nah_overrides.h
Go to the documentation of this file.
1/*
2 * NAH Overrides - Override Parsing and Application
3 *
4 * This file provides helpers for parsing and applying NAH_OVERRIDE_*
5 * environment variables. It uses nlohmann/json for JSON parsing.
6 *
7 * Include this header if you want automatic override handling.
8 * Otherwise, implement override parsing yourself and modify the
9 * CompositionResult.contract.environment after calling nah_compose().
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 */
13
14#ifndef NAH_OVERRIDES_H
15#define NAH_OVERRIDES_H
16
17#ifdef __cplusplus
18
19#include "nah_core.h"
20#include <nlohmann/json.hpp>
21#include <cstdlib>
22
23namespace nah {
24namespace overrides {
25
26using json = nlohmann::json;
27using namespace nah::core;
28
29// Portable getenv that avoids MSVC warnings
30namespace detail {
31inline std::string safe_getenv(const char* name) {
32#ifdef _WIN32
33 char* buf = nullptr;
34 size_t sz = 0;
35 if (_dupenv_s(&buf, &sz, name) == 0 && buf != nullptr) {
36 std::string result(buf);
37 free(buf);
38 return result;
39 }
40 return "";
41#else
42 const char* val = std::getenv(name);
43 return val ? val : "";
44#endif
45}
46} // namespace detail
47
48// ============================================================================
49// OVERRIDE PARSING
50// ============================================================================
51
55struct EnvOverrideParseResult {
56 bool present = false;
57 bool ok = false;
58 std::string error;
59 std::unordered_map<std::string, std::string> values;
60};
61
67inline EnvOverrideParseResult parse_env_override() {
68 EnvOverrideParseResult result;
69
70 std::string env_val = detail::safe_getenv("NAH_OVERRIDE_ENVIRONMENT");
71 if (env_val.empty()) {
72 return result; // Not present
73 }
74
75 result.present = true;
76
77 // Validate JSON first (no exceptions)
78 if (!json::accept(env_val)) {
79 result.error = "invalid JSON";
80 return result;
81 }
82
83 // Parse with noexcept overload
84 auto j = json::parse(env_val, nullptr, false);
85
86 if (j.is_discarded()) {
87 result.error = "JSON parse failed";
88 return result;
89 }
90
91 if (!j.is_object()) {
92 result.error = "expected object";
93 return result;
94 }
95
96 for (auto& [key, val] : j.items()) {
97 if (!val.is_string()) {
98 result.error = "value for '" + key + "' must be string";
99 return result;
100 }
101 result.values[key] = val.get<std::string>();
102 }
103
104 result.ok = true;
105 return result;
106}
107
114inline EnvOverrideParseResult parse_env_override(
115 const std::unordered_map<std::string, std::string>& process_env)
116{
117 EnvOverrideParseResult result;
118
119 auto it = process_env.find("NAH_OVERRIDE_ENVIRONMENT");
120 if (it == process_env.end()) {
121 return result; // Not present
122 }
123
124 result.present = true;
125
126 const std::string& env_val = it->second;
127
128 // Validate JSON first (no exceptions)
129 if (!json::accept(env_val)) {
130 result.error = "invalid JSON";
131 return result;
132 }
133
134 // Parse with noexcept overload
135 auto j = json::parse(env_val, nullptr, false);
136
137 if (j.is_discarded()) {
138 result.error = "JSON parse failed";
139 return result;
140 }
141
142 if (!j.is_object()) {
143 result.error = "expected object";
144 return result;
145 }
146
147 for (auto& [key, val] : j.items()) {
148 if (!val.is_string()) {
149 result.error = "value for '" + key + "' must be string";
150 return result;
151 }
152 result.values[key] = val.get<std::string>();
153 }
154
155 result.ok = true;
156 return result;
157}
158
159// ============================================================================
160// OVERRIDE POLICY HELPERS
161// ============================================================================
162
166inline bool is_key_allowed(const std::string& key, const HostEnvironment& host_env) {
167 if (!host_env.overrides.allow_env_overrides) {
168 return false;
169 }
170 if (host_env.overrides.allowed_env_keys.empty()) {
171 return true; // No allowlist = all keys allowed
172 }
173 for (const auto& allowed : host_env.overrides.allowed_env_keys) {
174 if (allowed == key) {
175 return true;
176 }
177 }
178 return false;
179}
180
181// ============================================================================
182// OVERRIDE APPLICATION
183// ============================================================================
184
200inline void apply_overrides(
201 CompositionResult& result,
202 const HostEnvironment& host_env,
203 const std::unordered_map<std::string, std::string>& process_env)
204{
205 if (!result.ok) {
206 return; // Don't apply overrides to failed compositions
207 }
208
209 auto parsed = parse_env_override(process_env);
210
211 if (!parsed.present) {
212 return; // No override to apply
213 }
214
215 if (!parsed.ok) {
216 // Parse failure
217 result.warnings.push_back({
218 warning_to_string(Warning::override_invalid),
219 "warn",
220 {
221 {"target", "NAH_OVERRIDE_ENVIRONMENT"},
222 {"reason", "parse_failure"},
223 {"source_kind", trace_source::PROCESS_ENV},
224 {"source_ref", "NAH_OVERRIDE_ENVIRONMENT"}
225 }
226 });
227 return;
228 }
229
230 // Check global override permission
231 if (!host_env.overrides.allow_env_overrides) {
232 result.warnings.push_back({
233 warning_to_string(Warning::override_denied),
234 "warn",
235 {
236 {"target", "NAH_OVERRIDE_ENVIRONMENT"},
237 {"reason", "overrides_disabled"},
238 {"source_kind", trace_source::PROCESS_ENV},
239 {"source_ref", "NAH_OVERRIDE_ENVIRONMENT"}
240 }
241 });
242 return;
243 }
244
245 // Apply overrides, checking per-key permissions
246 for (const auto& [key, value] : parsed.values) {
247 if (is_key_allowed(key, host_env)) {
248 result.contract.environment[key] = value;
249 } else {
250 result.warnings.push_back({
251 warning_to_string(Warning::override_denied),
252 "warn",
253 {
254 {"target", key},
255 {"reason", "key_not_allowed"},
256 {"source_kind", trace_source::PROCESS_ENV},
257 {"source_ref", "NAH_OVERRIDE_ENVIRONMENT"}
258 }
259 });
260 }
261 }
262}
263
272inline void apply_overrides(CompositionResult& result, const HostEnvironment& host_env)
273{
274 if (!result.ok) {
275 return;
276 }
277
278 auto parsed = parse_env_override();
279
280 if (!parsed.present) {
281 return;
282 }
283
284 if (!parsed.ok) {
285 result.warnings.push_back({
286 warning_to_string(Warning::override_invalid),
287 "warn",
288 {
289 {"target", "NAH_OVERRIDE_ENVIRONMENT"},
290 {"reason", "parse_failure"},
291 {"source_kind", trace_source::PROCESS_ENV},
292 {"source_ref", "NAH_OVERRIDE_ENVIRONMENT"}
293 }
294 });
295 return;
296 }
297
298 if (!host_env.overrides.allow_env_overrides) {
299 result.warnings.push_back({
300 warning_to_string(Warning::override_denied),
301 "warn",
302 {
303 {"target", "NAH_OVERRIDE_ENVIRONMENT"},
304 {"reason", "overrides_disabled"},
305 {"source_kind", trace_source::PROCESS_ENV},
306 {"source_ref", "NAH_OVERRIDE_ENVIRONMENT"}
307 }
308 });
309 return;
310 }
311
312 for (const auto& [key, value] : parsed.values) {
313 if (is_key_allowed(key, host_env)) {
314 result.contract.environment[key] = value;
315 } else {
316 result.warnings.push_back({
317 warning_to_string(Warning::override_denied),
318 "warn",
319 {
320 {"target", key},
321 {"reason", "key_not_allowed"},
322 {"source_kind", trace_source::PROCESS_ENV},
323 {"source_ref", "NAH_OVERRIDE_ENVIRONMENT"}
324 }
325 });
326 }
327 }
328}
329
330} // namespace overrides
331} // namespace nah
332
333#endif // __cplusplus
334
335#endif // NAH_OVERRIDES_H