-
Notifications
You must be signed in to change notification settings - Fork 289
Merge spin locked app into spin app #3231
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
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /// Type alias for a [`Result`]s with [`Error`]. | ||
| pub type Result<T> = std::result::Result<T, Error>; | ||
|
|
||
| /// Errors returned by methods in this crate. | ||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum Error { | ||
| /// An error propagated from the `spin_core` crate. | ||
| #[error(transparent)] | ||
| Core(anyhow::Error), | ||
| /// An error from a `DynamicHostComponent`. | ||
| #[error("host component error: {0:#}")] | ||
| HostComponent(#[source] anyhow::Error), | ||
| /// An error from a `Loader` implementation. | ||
| #[error(transparent)] | ||
| Loader(anyhow::Error), | ||
| /// An error indicating missing or unexpected metadata. | ||
| #[error("metadata error: {0}")] | ||
| Metadata(String), | ||
| /// An error indicating failed JSON (de)serialization. | ||
| #[error("json error: {0}")] | ||
| Json(#[from] serde_json::Error), | ||
| /// A validation error that can be presented directly to the user. | ||
| #[error(transparent)] | ||
| Validation(anyhow::Error), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,15 +11,22 @@ use std::sync::Arc; | |
|
|
||
| use serde::Deserialize; | ||
| use serde_json::Value; | ||
| use spin_locked_app::MetadataExt; | ||
|
|
||
| use locked::{ContentPath, LockedApp, LockedComponent, LockedComponentSource, LockedTrigger}; | ||
| use crate::locked::{ | ||
| ContentPath, LockedApp, LockedComponent, LockedComponentSource, LockedTrigger, | ||
| }; | ||
|
|
||
| pub use spin_locked_app::locked; | ||
| pub use spin_locked_app::values; | ||
| pub use spin_locked_app::{Error, MetadataKey, Result}; | ||
| pub use crate::locked::Variable; | ||
|
|
||
| pub use locked::Variable; | ||
| use crate::error::{Error, Result}; | ||
| use crate::metadata::MetadataExt as _; | ||
|
|
||
| mod error; | ||
| pub mod locked; | ||
| mod metadata; | ||
| pub mod values; | ||
|
|
||
| pub use metadata::MetadataKey; | ||
|
|
||
| /// MetadataKey for extracting the application name. | ||
| pub const APP_NAME_KEY: MetadataKey = MetadataKey::new("name"); | ||
|
|
@@ -121,7 +128,7 @@ impl App { | |
| return Ok(None); | ||
| }; | ||
| let metadata = T::deserialize(value).map_err(|err| { | ||
| Error::MetadataError(format!( | ||
| Error::Metadata(format!( | ||
| "invalid metadata value for {trigger_type:?}: {err:?}" | ||
| )) | ||
| })?; | ||
|
|
@@ -186,7 +193,7 @@ impl App { | |
| ) -> Result<LockedApp> { | ||
| self.validate_retained_components_exist(retained_components)?; | ||
| for validator in validators { | ||
| validator(&self, retained_components).map_err(Error::ValidationError)?; | ||
| validator(&self, retained_components).map_err(Error::Validation)?; | ||
| } | ||
| let (component_ids, trigger_ids): (HashSet<String>, HashSet<String>) = self | ||
| .triggers() | ||
|
|
@@ -211,7 +218,7 @@ impl App { | |
| .collect::<HashSet<_>>(); | ||
| for c in retained_components { | ||
| if !app_components.contains(*c) { | ||
| return Err(Error::ValidationError(anyhow::anyhow!( | ||
| return Err(Error::Validation(anyhow::anyhow!( | ||
| "Specified component \"{c}\" not found in application" | ||
| ))); | ||
| } | ||
|
|
@@ -310,10 +317,10 @@ impl<'a> AppTrigger<'a> { | |
| let id = &self.locked.id; | ||
| let common_config: CommonTriggerConfig = self.typed_config()?; | ||
| let component_id = common_config.component.ok_or_else(|| { | ||
| Error::MetadataError(format!("trigger {id:?} missing 'component' config field")) | ||
| Error::Metadata(format!("trigger {id:?} missing 'component' config field")) | ||
| })?; | ||
| self.app.get_component(&component_id).ok_or_else(|| { | ||
| Error::MetadataError(format!( | ||
| Error::Metadata(format!( | ||
| "missing component {component_id:?} configured for trigger {id:?}" | ||
| )) | ||
| }) | ||
|
|
@@ -337,29 +344,45 @@ pub fn retain_components( | |
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use spin_factors_test::build_locked_app; | ||
| use std::collections::HashSet; | ||
|
|
||
| use super::*; | ||
|
|
||
| pub fn locked_app() -> LockedApp { | ||
|
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. Thanks - I think this is (unfortunately) the least worst option and I appreciate you persevering with it. I do think you can simplify the implementation a bit, e.g.:
I think this is terser and clearer -
|
||
| LockedApp { | ||
| spin_lock_version: Default::default(), | ||
| must_understand: Default::default(), | ||
| metadata: Default::default(), | ||
| host_requirements: Default::default(), | ||
| variables: Default::default(), | ||
| triggers: vec![LockedTrigger { | ||
| id: "trigger".into(), | ||
| trigger_type: "dummy".into(), | ||
| trigger_config: toml::from_str(r#"component = "empty""#).unwrap(), | ||
| }], | ||
| components: vec![LockedComponent { | ||
| id: "empty".to_owned(), | ||
| metadata: Default::default(), | ||
| source: LockedComponentSource { | ||
| content_type: "application/wasm".to_owned(), | ||
| content: Default::default(), | ||
| }, | ||
| env: Default::default(), | ||
| files: Default::default(), | ||
| config: Default::default(), | ||
| dependencies: Default::default(), | ||
| host_requirements: Default::default(), | ||
| }], | ||
| } | ||
| } | ||
|
|
||
| fn does_nothing_validator(_: &App, _: &[&str]) -> anyhow::Result<()> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_retain_components_filtering_for_only_component_works() { | ||
| let manifest = toml::toml! { | ||
| spin_manifest_version = 2 | ||
|
|
||
| [application] | ||
| name = "test-app" | ||
|
|
||
| [[trigger.test-trigger]] | ||
| component = "empty" | ||
|
|
||
| [component.empty] | ||
| source = "does-not-exist.wasm" | ||
| }; | ||
| let mut locked_app = build_locked_app(&manifest).await.unwrap(); | ||
| let mut locked_app = locked_app(); | ||
| locked_app = retain_components(locked_app, &["empty"], &[&does_nothing_validator]).unwrap(); | ||
| let components = locked_app | ||
| .components | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intentional that tests are being removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was moved into the
spin-factor-testcrate to avoid the cyclic dependency issue the compiler frowns at.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
factors-testseems like a rather strange place for it - I thought retaining components was a pure LockedApp operation whereasfactors-testseems to be more about instantiation and runtime configuration. What was the cyclic dependency that you ran into?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test inside the module
test_retain_components_filtering_for_only_component_workscallsbuild_locked_appfromspin-factor-test, which returnsLockedApp.LockedApppreviously lived insidespin-locked_app; hence, bothspin-appandspin-factor-testdepend onspin-locked-app, no conflict.With the merge,
LockedApplives insidespin-app, that meansspin-factor-test, which hostsbuild_locked_app, would have to depend onspin-appto importLockedApp(which the function returns alongside other functions), whilespin-appdepends onspin-factor-testforbuild_locked_appThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like the tail wagging the dog.
build_locked_appis a tiny ancillary function that basically wrapsspin_loader::from_file. I don't think we should be locating tests based on where we happen to have existing helpers - I'd strongly prefer to keep the test with the thing it's testing and figure out how to support it in that location.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you've moved the
retain_componentstest tospin_loader, which still seems like you're letting the ancillarybuild_locked_appcontrol where you put the test. The test should go alongsideretain_componentsand then you should figure out what supporting infrastructure it needs in that location. If that involves reworking or jettisoningbuild_locked_appthen so be it -build_locked_appis not the important thing in the test and should not be driving the design.Looking at existing code, I'm a bit puzzled at why the compiler is getting mad now anyway. The
appcrate already has a dev-dependency onfactors-test, andfactors-testalready has a dependency onapp. Is it becausefactors-testreally only depended on theLockedAppre-export and the compiler realised it wasn't a true circle?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think because of the cyclic issue even though
spin-apphasspin-factors-testas adev-dependence, the compiler keeps having issues knowing whichLockedAppretain_componentsreturns.the crate `spin_app` is compiled multiple times, possibly with different configurationsFull error: