Skip to content

Commit

Permalink
Deduplicate App::get_metadata, AppComponent::get_metadata
Browse files Browse the repository at this point in the history
Also add AppComponent::require_metadata for interface consistency.

Signed-off-by: Lann Martin <[email protected]>
  • Loading branch information
lann committed Sep 16, 2022
1 parent 48dacb2 commit 1e442b8
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 21 deletions.
34 changes: 14 additions & 20 deletions crates/app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use spin_core::{wasmtime, Engine, EngineBuilder, StoreBuilder};

use host_component::DynamicHostComponents;
use locked::{ContentPath, LockedApp, LockedComponent, LockedComponentSource, LockedTrigger};
use values::MetadataExt;

pub use async_trait::async_trait;
pub use host_component::DynamicHostComponent;
Expand Down Expand Up @@ -141,20 +142,15 @@ impl<'a> App<'a> {
/// `Err` only if there _is_ a value for the `key` but the typed
/// deserialization failed.
pub fn get_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result<Option<T>> {
self.locked
.metadata
.get(key)
.map(|value| Ok(T::deserialize(value)?))
.transpose()
self.locked.metadata.get_typed(key)
}

/// Deserializes typed metadata for this app.
///
/// Like [`App::get_metadata`], but returns an `Err` if there is no metadata
/// for the given `key`.
/// Like [`App::get_metadata`], but returns an error if there is
/// no metadata for the given `key`.
pub fn require_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result<T> {
self.get_metadata(key)?
.ok_or_else(|| Error::MetadataError(format!("missing required {key:?}")))
self.locked.metadata.require_typed(key)
}

/// Returns an iterator of custom config [`Variable`]s defined for this app.
Expand Down Expand Up @@ -223,17 +219,15 @@ impl<'a> AppComponent<'a> {
/// `Err` only if there _is_ a value for the `key` but the typed
/// deserialization failed.
pub fn get_metadata<T: Deserialize<'a>>(&self, key: &str) -> Result<Option<T>> {
self.locked
.metadata
.get(key)
.map(|value| {
T::deserialize(value).map_err(|err| {
Error::MetadataError(format!(
"failed to deserialize {key:?} = {value:?}: {err:?}"
))
})
})
.transpose()
self.locked.metadata.get_typed(key)
}

/// Deserializes typed metadata for this component.
///
/// Like [`AppComponent::get_metadata`], but returns an error if there is
/// no metadata for the given `key`.
pub fn require_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result<T> {
self.locked.metadata.require_typed(key)
}

/// Returns an iterator of custom config values for this component.
Expand Down
31 changes: 30 additions & 1 deletion crates/app/src/values.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Dynamically-typed value helpers.

use serde::Serialize;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::Error;

/// A String-keyed map with dynamically-typed values.
pub type ValuesMap = serde_json::Map<String, Value>;

Expand Down Expand Up @@ -69,3 +71,30 @@ impl ValuesMapBuilder {
std::mem::take(&mut self.0)
}
}

pub(crate) trait MetadataExt {
fn get_value(&self, key: impl AsRef<str>) -> Option<&Value>;

fn get_typed<'a, T: Deserialize<'a>>(
&'a self,
key: impl AsRef<str>,
) -> Result<Option<T>, Error> {
let key = key.as_ref();
self.get_value(key)
.map(|value| T::deserialize(value))
.transpose()
.map_err(|err| Error::MetadataError(format!("invalid value for {key:?}: {err:?}")))
}

fn require_typed<'a, T: Deserialize<'a>>(&'a self, key: impl AsRef<str>) -> Result<T, Error> {
let key = key.as_ref();
self.get_typed(key)?
.ok_or_else(|| Error::MetadataError(format!("missing required {key:?}")))
}
}

impl MetadataExt for ValuesMap {
fn get_value(&self, key: impl AsRef<str>) -> Option<&Value> {
self.get(key.as_ref())
}
}

0 comments on commit 1e442b8

Please sign in to comment.