-
Notifications
You must be signed in to change notification settings - Fork 12
Honor EdgeZero app config store default #879
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
631d4eb
495a92d
391fbb5
953f0df
29cd794
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,8 @@ use edgezero_core::router::RouterService; | |
| use error_stack::Report; | ||
| use trusted_server_core::auction::endpoints::handle_auction; | ||
| use trusted_server_core::auction::{AuctionOrchestrator, build_orchestrator}; | ||
| #[cfg(any(test, target_arch = "wasm32"))] | ||
| use trusted_server_core::config_payload::CONFIG_BLOB_KEY; | ||
| #[cfg(target_arch = "wasm32")] | ||
| use trusted_server_core::config_payload::settings_from_config_blob; | ||
| use trusted_server_core::ec::EcContext; | ||
|
|
@@ -78,31 +80,92 @@ fn load_startup_settings() -> Result<Settings, Report<TrustedServerError>> { | |
| Settings::from_toml(include_str!("../../../trusted-server.example.toml")) | ||
| } | ||
|
|
||
| /// Older Cloudflare bindings used this JSON property before config stores adopted | ||
| /// the manifest-derived default. | ||
| /// | ||
| /// Remove this fallback only when support for those bindings is deliberately retired. | ||
| #[cfg(any(test, target_arch = "wasm32"))] | ||
| const LEGACY_CONFIG_BLOB_KEY: &str = "app_config"; | ||
|
|
||
| #[cfg(any(test, target_arch = "wasm32"))] | ||
| #[derive(Debug, Eq, PartialEq)] | ||
| enum CloudflareConfigEnvelopeError { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ♻️ refactor — This error type carries its message in an inherent method rather than through the project's error convention. CLAUDE.md is explicit: define errors with Proposed fix (apply manually — it rewrites the type, deletes the #[cfg(any(test, target_arch = "wasm32"))]
#[derive(Debug, Eq, PartialEq, derive_more::Display)]
enum CloudflareConfigEnvelopeError {
#[display(
"Cloudflare TRUSTED_SERVER_CONFIG missing string values at `{primary_key}` and legacy `{legacy_key}`"
)]
Missing {
primary_key: &'static str,
legacy_key: &'static str,
},
#[display("Cloudflare TRUSTED_SERVER_CONFIG value at `{key}` must be a string")]
NonString { key: &'static str },
}
#[cfg(any(test, target_arch = "wasm32"))]
impl core::error::Error for CloudflareConfigEnvelopeError {}The call site then becomes |
||
| Missing { | ||
| primary_key: &'static str, | ||
| legacy_key: &'static str, | ||
| }, | ||
| NonString { | ||
| key: &'static str, | ||
| }, | ||
| } | ||
|
|
||
| #[cfg(any(test, target_arch = "wasm32"))] | ||
| impl CloudflareConfigEnvelopeError { | ||
| fn configuration_message(&self) -> String { | ||
| match self { | ||
| Self::Missing { | ||
| primary_key, | ||
| legacy_key, | ||
| } => { | ||
| format!( | ||
| "Cloudflare TRUSTED_SERVER_CONFIG missing string values at `{primary_key}` and legacy `{legacy_key}`" | ||
| ) | ||
| } | ||
| Self::NonString { key } => { | ||
| format!("Cloudflare TRUSTED_SERVER_CONFIG value at `{key}` must be a string") | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(target_arch = "wasm32")] | ||
| fn settings_from_cloudflare_config_json() -> Result<Settings, Report<TrustedServerError>> { | ||
| let raw_config = CLOUDFLARE_CONFIG_JSON.get().ok_or_else(|| { | ||
| Report::new(TrustedServerError::Configuration { | ||
| message: "Cloudflare TRUSTED_SERVER_CONFIG is required".to_string(), | ||
| }) | ||
| .attach("set TRUSTED_SERVER_CONFIG to JSON containing the app_config blob envelope") | ||
| .attach(format!( | ||
| "set TRUSTED_SERVER_CONFIG to JSON containing the `{CONFIG_BLOB_KEY}` blob envelope" | ||
| )) | ||
| })?; | ||
| let value: serde_json::Value = serde_json::from_str(raw_config).map_err(|error| { | ||
| Report::new(TrustedServerError::Configuration { | ||
| message: "invalid Cloudflare TRUSTED_SERVER_CONFIG JSON".to_string(), | ||
| }) | ||
| .attach(format!("failed to parse TRUSTED_SERVER_CONFIG: {error}")) | ||
| })?; | ||
| let envelope = value | ||
| .get("app_config") | ||
| .and_then(serde_json::Value::as_str) | ||
| .ok_or_else(|| { | ||
| Report::new(TrustedServerError::Configuration { | ||
| message: "Cloudflare TRUSTED_SERVER_CONFIG missing app_config".to_string(), | ||
| }) | ||
| })?; | ||
| let envelope = cloudflare_config_envelope(&value).map_err(|error| { | ||
| Report::new(TrustedServerError::Configuration { | ||
| message: error.configuration_message(), | ||
| }) | ||
| })?; | ||
| settings_from_config_blob(envelope) | ||
| } | ||
|
|
||
| #[cfg(any(test, target_arch = "wasm32"))] | ||
| fn cloudflare_config_envelope( | ||
| value: &serde_json::Value, | ||
| ) -> Result<&str, CloudflareConfigEnvelopeError> { | ||
| match value.get(CONFIG_BLOB_KEY) { | ||
| Some(envelope) => envelope | ||
| .as_str() | ||
| .ok_or(CloudflareConfigEnvelopeError::NonString { | ||
| key: CONFIG_BLOB_KEY, | ||
| }), | ||
| None => match value.get(LEGACY_CONFIG_BLOB_KEY) { | ||
| Some(envelope) => envelope | ||
| .as_str() | ||
| .ok_or(CloudflareConfigEnvelopeError::NonString { | ||
| key: LEGACY_CONFIG_BLOB_KEY, | ||
| }), | ||
| None => Err(CloudflareConfigEnvelopeError::Missing { | ||
| primary_key: CONFIG_BLOB_KEY, | ||
| legacy_key: LEGACY_CONFIG_BLOB_KEY, | ||
| }), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| /// Build the application state from explicit settings. | ||
| /// | ||
| /// # Errors | ||
|
|
@@ -614,3 +677,95 @@ fn build_router(state: &Arc<AppState>) -> RouterService { | |
| router.build() | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn config_value(entries: &[(&str, &str)]) -> serde_json::Value { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ♻️ refactor — This helper hand-assembles a
Proposed fix (apply manually — deleting the helper and rewriting its three call sites spans several non-contiguous ranges, so it cannot be one #[test]
fn cloudflare_config_prefers_manifest_default_key() {
let value = serde_json::json!({
LEGACY_CONFIG_BLOB_KEY: "legacy-envelope",
CONFIG_BLOB_KEY: "manifest-envelope",
});
// …
}
#[test]
fn cloudflare_config_accepts_legacy_app_config_key() {
let value = serde_json::json!({ LEGACY_CONFIG_BLOB_KEY: "legacy-envelope" });
// …
}
#[test]
fn cloudflare_config_does_not_mask_malformed_manifest_value() {
let value = serde_json::json!({
LEGACY_CONFIG_BLOB_KEY: "legacy-envelope",
CONFIG_BLOB_KEY: true,
});
// …
}The third one also drops the |
||
| serde_json::Value::Object( | ||
| entries | ||
| .iter() | ||
| .map(|(key, value)| { | ||
| ( | ||
| (*key).to_string(), | ||
| serde_json::Value::String((*value).to_string()), | ||
| ) | ||
| }) | ||
| .collect(), | ||
| ) | ||
| } | ||
|
|
||
| #[test] | ||
| fn cloudflare_config_prefers_manifest_default_key() { | ||
| let value = config_value(&[ | ||
| (LEGACY_CONFIG_BLOB_KEY, "legacy-envelope"), | ||
| (CONFIG_BLOB_KEY, "manifest-envelope"), | ||
| ]); | ||
|
|
||
| assert_eq!( | ||
| cloudflare_config_envelope(&value), | ||
| Ok("manifest-envelope"), | ||
| "manifest-derived key should take precedence" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn cloudflare_config_accepts_legacy_app_config_key() { | ||
| let value = config_value(&[(LEGACY_CONFIG_BLOB_KEY, "legacy-envelope")]); | ||
|
|
||
| assert_eq!( | ||
| cloudflare_config_envelope(&value), | ||
| Ok("legacy-envelope"), | ||
| "legacy app_config key should remain compatible" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn cloudflare_config_reports_missing_keys() { | ||
| let value = serde_json::json!({}); | ||
|
|
||
| assert_eq!( | ||
| cloudflare_config_envelope(&value), | ||
| Err(CloudflareConfigEnvelopeError::Missing { | ||
| primary_key: CONFIG_BLOB_KEY, | ||
| legacy_key: LEGACY_CONFIG_BLOB_KEY, | ||
| }), | ||
| "missing config should name both accepted keys" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn cloudflare_config_does_not_mask_malformed_manifest_value() { | ||
|
ChristianPavilonis marked this conversation as resolved.
|
||
| let mut value = config_value(&[(LEGACY_CONFIG_BLOB_KEY, "legacy-envelope")]); | ||
| value[CONFIG_BLOB_KEY] = serde_json::Value::Bool(true); | ||
|
|
||
| assert_eq!( | ||
| cloudflare_config_envelope(&value), | ||
| Err(CloudflareConfigEnvelopeError::NonString { | ||
| key: CONFIG_BLOB_KEY, | ||
| }), | ||
| "malformed manifest-derived value should not fall back" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn cloudflare_config_reports_malformed_legacy_value() { | ||
| let value = serde_json::json!({ LEGACY_CONFIG_BLOB_KEY: false }); | ||
| let error = cloudflare_config_envelope(&value) | ||
| .expect_err("should reject a malformed legacy config value"); | ||
|
|
||
| assert_eq!( | ||
| error, | ||
| CloudflareConfigEnvelopeError::NonString { | ||
| key: LEGACY_CONFIG_BLOB_KEY, | ||
| }, | ||
| "malformed legacy value should name the legacy key" | ||
| ); | ||
| assert_eq!( | ||
| error.configuration_message(), | ||
| "Cloudflare TRUSTED_SERVER_CONFIG value at `app_config` must be a string", | ||
| "configuration error should name the malformed legacy key" | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,36 @@ | ||
| use std::env; | ||
| use std::path::PathBuf; | ||
|
|
||
| use edgezero_core::manifest::ManifestLoader; | ||
|
|
||
| fn main() { | ||
| println!("cargo:rerun-if-changed=build.rs"); | ||
|
|
||
| // Keep every adapter's compiled default synchronized with the repository manifest. | ||
| let manifest_path = PathBuf::from( | ||
| env::var("CARGO_MANIFEST_DIR").expect("should receive CARGO_MANIFEST_DIR from Cargo"), | ||
| ) | ||
| .join("../..") | ||
| .join("edgezero.toml"); | ||
| println!("cargo:rerun-if-changed={}", manifest_path.display()); | ||
|
|
||
| let manifest = match ManifestLoader::from_path(&manifest_path) { | ||
| Ok(manifest) => manifest, | ||
| Err(error) => { | ||
| println!( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 praise — This build script fails closed on manifest problems and says exactly which file it could not use. Two details make it worth calling out. The error path emits The alternative — reading the manifest at runtime, or trusting a hand-copied constant — would have moved both failure modes to the edge. Catching them at compile time is the right trade for a value that can never change without a rebuild anyway. |
||
| "cargo::error=should load EdgeZero manifest at {}: {error}", | ||
| manifest_path.display() | ||
| ); | ||
| std::process::exit(1); | ||
| } | ||
| }; | ||
| let Some(config_store) = manifest.manifest().stores.config.as_ref() else { | ||
| println!( | ||
| "cargo::error=should declare [stores.config] in EdgeZero manifest at {}", | ||
| manifest_path.display() | ||
| ); | ||
| std::process::exit(1); | ||
| }; | ||
| let default_store_id = config_store.default_id(); | ||
| println!("cargo:rustc-env=TRUSTED_SERVER_DEFAULT_CONFIG_STORE_ID={default_store_id}"); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.