wick_config/
config.rs

1#![allow(missing_docs)] // delete when we move away from the `property` crate.
2
3/// Specific component-level configuration and types.
4pub mod components;
5
6pub(crate) mod app_config;
7mod cache;
8pub(crate) mod common;
9pub(crate) mod component_config;
10pub(crate) mod configuration_tree;
11pub(crate) mod import_cache;
12pub(crate) mod lockdown_config;
13pub(crate) mod permissions;
14pub(crate) mod test_config;
15pub(crate) mod types_config;
16
17use std::collections::HashMap;
18use std::path::{Path, PathBuf};
19
20pub use app_config::*;
21use asset_container::Asset;
22pub use bindings::BoundIdentifier;
23pub use common::*;
24pub use component_config::*;
25pub use configuration_tree::*;
26pub use lockdown_config::*;
27pub use permissions::{Permissions, PermissionsBuilder};
28pub use test_config::*;
29use tracing::debug;
30pub use types_config::*;
31use wick_asset_reference::{AssetReference, FetchOptions};
32use wick_interface_types::Field;
33use wick_packet::validation::expect_configuration_matches;
34use wick_packet::{Entity, RuntimeConfig};
35
36use self::template_config::Renderable;
37use crate::load::resolve_configuration;
38use crate::lockdown::Lockdown;
39use crate::{Error, Imports, RootConfig};
40
41#[derive(Debug, Clone, property::Property)]
42#[property(get(public), set(public), mut(public, suffix = "_mut"))]
43/// A builder for [WickConfiguration].
44pub struct UninitializedConfiguration {
45  /// The manifest to use as a base.
46  pub(crate) manifest: WickConfiguration,
47  /// The root configuration to use when rendering internal configuration templates.
48  pub(crate) root_config: Option<RuntimeConfig>,
49  /// The environment this configuration can use when rendering internal configuration templates.
50  pub(crate) env: Option<HashMap<String, String>>,
51  /// The lockdown configuration that will validate a configuration is safe to run.
52  pub(crate) lockdown_config: Option<LockdownConfiguration>,
53}
54
55impl UninitializedConfiguration {
56  #[must_use]
57  /// Create a new builder with the given manifest.
58  pub const fn new(manifest: WickConfiguration) -> Self {
59    Self {
60      manifest,
61      root_config: None,
62      env: None,
63      lockdown_config: None,
64    }
65  }
66
67  /// Return the inner, uninitialized [WickConfiguration].
68  #[must_use]
69  #[allow(clippy::missing_const_for_fn)]
70  pub fn into_inner(self) -> WickConfiguration {
71    self.manifest
72  }
73
74  /// Build, initialize and return a [WickConfiguration].
75  pub fn finish(mut self) -> Result<WickConfiguration, Error> {
76    debug!(root_config=?self.root_config, env=?self.env.as_ref().map(|c|format!("{} variables",c.len())), "initializing configuration");
77
78    expect_configuration_matches(
79      self
80        .manifest
81        .source()
82        .map_or("<unknown>", |p| p.to_str().unwrap_or("<invalid>")),
83      self.root_config.as_ref(),
84      self.manifest.config(),
85    )
86    .map_err(Error::ConfigurationInvalid)?;
87    self.manifest.set_env(self.env);
88    self.manifest.set_root_config(self.root_config);
89    self.manifest.initialize()?;
90    self.manifest.validate()?;
91    Ok(self.manifest)
92  }
93}
94
95impl Imports for UninitializedConfiguration {
96  fn imports(&self) -> &[Binding<ImportDefinition>] {
97    self.manifest.imports()
98  }
99}
100
101impl Lockdown for UninitializedConfiguration {
102  fn lockdown(&self, id: Option<&str>, lockdown: &LockdownConfiguration) -> Result<(), crate::lockdown::LockdownError> {
103    self.manifest.lockdown(id, lockdown)?;
104    Ok(())
105  }
106}
107
108/// A catch-all enum for root-level Wick configurations.
109#[derive(Debug, Clone, derive_asset_container::AssetManager, serde::Serialize)]
110#[asset(asset(AssetReference))]
111#[serde(untagged)]
112
113pub enum WickConfiguration {
114  /// A [component_config::ComponentConfiguration] configuration.
115  Component(ComponentConfiguration),
116  /// An [app_config::AppConfiguration] configuration.
117  App(AppConfiguration),
118  /// A [types_config::TypesConfiguration] configuration.
119  Types(TypesConfiguration),
120  /// A [test_config::TestConfiguration] configuration.
121  Tests(TestConfiguration),
122  /// A [lockdown_config::LockdownConfiguration] configuration.
123  Lockdown(LockdownConfiguration),
124}
125
126impl WickConfiguration {
127  /// Fetch a configuration and all referenced assets from a path.
128  ///
129  /// # Example
130  ///
131  /// ```rust
132  /// # tokio_test::block_on(async {
133  /// use wick_config::WickConfiguration;
134  /// use wick_asset_reference::FetchOptions;
135  ///
136  /// let opts = FetchOptions::default();
137  /// let env : HashMap<String,String> = std::env::vars().collect();
138  /// let root_config = None;
139  ///
140  /// let manifest = WickConfiguration::fetch_tree("path/to/manifest.yaml", root_config, env, opts).await?;
141  /// # Ok::<_,anyhow::Error>(())
142  /// # });
143  /// ```
144  pub async fn fetch_tree(
145    path: impl Into<AssetReference> + Send,
146    root_config: Option<RuntimeConfig>,
147    root_env: Option<HashMap<String, String>>,
148    options: FetchOptions,
149  ) -> Result<ConfigurationTreeNode<WickConfiguration>, Error> {
150    let mut config = Self::fetch(path, options.clone()).await?;
151    let source = config.manifest.source().map(ToOwned::to_owned);
152    config
153      .manifest
154      .render_config(source.as_deref(), root_config.as_ref(), root_env.as_ref())?;
155
156    let renderer = |runtime_config: Option<RuntimeConfig>, mut child: UninitializedConfiguration| {
157      child.set_root_config(runtime_config);
158      child.finish()
159    };
160
161    let children = fetch_children(&config, options, &renderer).await?;
162    Ok(ConfigurationTreeNode::new(
163      Entity::LOCAL.into(),
164      config.finish()?,
165      children,
166    ))
167  }
168
169  /// Fetch a configuration and all referenced assets from a path.
170  ///
171  /// # Example
172  ///
173  /// ```rust
174  /// # tokio_test::block_on(async {
175  /// use wick_config::WickConfiguration;
176  /// use wick_asset_reference::FetchOptions;
177  ///
178  /// let opts = FetchOptions::default();
179  ///
180  /// let manifest = WickConfiguration::fetch_uninitialized_tree("path/to/manifest.yaml", opts).await?;
181  /// # Ok::<_,anyhow::Error>(())
182  /// # });
183  /// ```
184  pub async fn fetch_uninitialized_tree(
185    path: impl Into<AssetReference> + Send,
186    options: FetchOptions,
187  ) -> Result<ConfigurationTreeNode<UninitializedConfiguration>, Error> {
188    let config = Self::fetch(path, options.clone()).await?;
189
190    let children = fetch_children(&config, options, &|_, b| Ok(b)).await?;
191
192    Ok(ConfigurationTreeNode::new(Entity::LOCAL.into(), config, children))
193  }
194
195  /// Fetch a configuration from a path.
196  ///
197  /// # Example
198  ///
199  /// ```rust
200  /// # tokio_test::block_on(async {
201  /// use wick_config::WickConfiguration;
202  /// use wick_asset_reference::FetchOptions;
203  ///
204  /// let opts = FetchOptions::default();
205  ///
206  /// let manifest = WickConfiguration::fetch("path/to/manifest.yaml", opts).await?;
207  /// # Ok::<_,anyhow::Error>(())
208  /// # });
209  /// ```
210  ///
211  pub async fn fetch(
212    asset: impl Into<AssetReference> + Send,
213    options: FetchOptions,
214  ) -> Result<UninitializedConfiguration, Error> {
215    let asset: AssetReference = asset.into();
216
217    let cache_result = cache::CONFIG_CACHE.lock().get(&asset).cloned();
218
219    let config = if let Some(config) = cache_result {
220      tracing::trace!(cache_hit = true, asset=%asset.location(), "config::fetch");
221      config
222    } else {
223      tracing::trace!(cache_hit = false, asset=%asset.location(), "config::fetch");
224      let bytes = asset.fetch(options.clone()).await?;
225      let source = asset.path().unwrap_or_else(|e| PathBuf::from(format!("<ERROR:{}>", e)));
226      let config = WickConfiguration::load_from_bytes(&bytes, &Some(source))?;
227      config.manifest.update_baseurls();
228      match &config.manifest {
229        WickConfiguration::Component(c) => {
230          c.setup_cache(options).await?;
231        }
232        WickConfiguration::App(c) => {
233          c.setup_cache(options).await?;
234        }
235        WickConfiguration::Types(_) => {}
236        WickConfiguration::Tests(_) => {}
237        WickConfiguration::Lockdown(_) => {}
238      }
239
240      cache::CONFIG_CACHE.lock().insert(asset.clone(), config.clone());
241      config
242    };
243
244    Ok(config)
245  }
246
247  /// Load a configuration from raw bytes. Pass in an optional source to track where the bytes came from.
248  ///
249  /// # Example
250  ///
251  /// ```rust
252  /// # tokio_test::block_on(async {
253  /// use wick_config::WickConfiguration;
254  /// use std::path::PathBuf;
255  ///
256  /// let path = PathBuf::from("path/to/manifest.yaml");
257  ///
258  /// let bytes = std::fs::read(&path)?;
259  ///
260  /// let manifest = WickConfiguration::load_from_bytes(&bytes, &Some(path))?;
261  /// # Ok::<_,anyhow::Error>(())
262  /// # });
263  /// ```
264  pub fn load_from_bytes(bytes: &[u8], source: &Option<PathBuf>) -> Result<UninitializedConfiguration, Error> {
265    let string = &String::from_utf8(bytes.to_vec()).map_err(|_| Error::Utf8)?;
266
267    resolve_configuration(string, source)
268  }
269
270  /// Load a configuration from a string. Pass in an optional source to track where the string came from.
271  ///
272  /// # Example
273  ///
274  /// ```rust
275  /// # tokio_test::block_on(async {
276  /// use std::path::PathBuf;
277  /// use wick_config::WickConfiguration;
278  ///
279  /// let path = PathBuf::from("path/to/manifest.yaml");
280  ///
281  /// let string = std::fs::read_to_string(&path)?;
282  ///
283  /// let manifest = WickConfiguration::from_yaml(&string, &Some(path))?;
284  /// # Ok::<_,anyhow::Error>(())
285  /// # });
286  /// ```
287  ///
288  pub fn from_yaml(src: &str, source: &Option<PathBuf>) -> Result<UninitializedConfiguration, Error> {
289    resolve_configuration(src, source)
290  }
291
292  /// Convert a WickConfiguration into V1 configuration yaml source.
293  ///
294  /// # Example
295  ///
296  /// ```rust
297  /// # tokio_test::block_on(async {
298  /// use wick_config::WickConfiguration;
299  /// use wick_asset_reference::FetchOptions;
300  ///
301  /// let opts = FetchOptions::default();
302  ///
303  /// let manifest = WickConfiguration::fetch_all("path/to/manifest.yaml", opts).await?;
304  /// let manifest = manifest.finish()?;
305  ///
306  /// let v1_yaml = manifest.into_v1_yaml()?;
307  /// # Ok::<_,anyhow::Error>(())
308  /// # });
309  /// ```
310  #[cfg(feature = "v1")]
311  pub fn into_v1_yaml(self) -> Result<String, Error> {
312    Ok(serde_yaml::to_string(&self.into_v1()?).unwrap())
313  }
314
315  /// Convert a WickConfiguration into a V1 configuration JSON value.
316  ///
317  /// # Example
318  ///
319  /// ```rust
320  /// # tokio_test::block_on(async {
321  /// use wick_config::WickConfiguration;
322  /// use wick_asset_reference::FetchOptions;
323  ///
324  /// let opts = FetchOptions::default();
325  ///
326  /// let manifest = WickConfiguration::fetch_all("path/to/manifest.yaml", opts).await?;
327  /// let manifest = manifest.finish()?;
328  ///
329  /// let v1_json = manifest.into_v1_json()?;
330  /// # Ok::<_,anyhow::Error>(())
331  /// # });
332  /// ```
333  #[cfg(feature = "v1")]
334  pub fn into_v1_json(self) -> Result<serde_json::Value, Error> {
335    Ok(serde_json::to_value(&self.into_v1()?).unwrap())
336  }
337
338  #[cfg(feature = "v1")]
339  fn into_v1(self) -> Result<crate::v1::WickConfig, Error> {
340    match self {
341      WickConfiguration::Component(c) => Ok(crate::v1::WickConfig::ComponentConfiguration(c.try_into()?)),
342      WickConfiguration::App(c) => Ok(crate::v1::WickConfig::AppConfiguration(c.try_into()?)),
343      WickConfiguration::Types(c) => Ok(crate::v1::WickConfig::TypesConfiguration(c.try_into()?)),
344      WickConfiguration::Tests(c) => Ok(crate::v1::WickConfig::TestConfiguration(c.try_into()?)),
345      WickConfiguration::Lockdown(c) => Ok(crate::v1::WickConfig::LockdownConfiguration(c.try_into()?)),
346    }
347  }
348
349  /// Get the name (if any) associated with the inner configuration.
350  #[must_use]
351  pub fn name(&self) -> Option<&str> {
352    match self {
353      WickConfiguration::Component(v) => v.name().map(|s| s.as_str()),
354      WickConfiguration::App(v) => Some(v.name()),
355      WickConfiguration::Types(v) => v.name().map(|s| s.as_str()),
356      WickConfiguration::Tests(v) => v.name().map(|s| s.as_str()),
357      WickConfiguration::Lockdown(_) => None,
358    }
359  }
360
361  /// Get the metadata (if any) associated with the inner configuration.
362  #[must_use]
363  pub fn metadata(&self) -> Option<&Metadata> {
364    match self {
365      WickConfiguration::Component(v) => v.metadata(),
366      WickConfiguration::App(v) => v.metadata(),
367      WickConfiguration::Types(v) => v.metadata(),
368      WickConfiguration::Tests(_v) => None,
369      WickConfiguration::Lockdown(_v) => None,
370    }
371  }
372
373  /// Validate this configuration is good.
374  pub fn validate(&self) -> Result<(), Error> {
375    match self {
376      WickConfiguration::Component(v) => v.validate(),
377      WickConfiguration::App(v) => v.validate(),
378      WickConfiguration::Types(v) => v.validate(),
379      WickConfiguration::Tests(v) => v.validate(),
380      WickConfiguration::Lockdown(v) => v.validate(),
381    }
382  }
383
384  /// Get the runtime configuration (if any) associated with the inner configuration.
385  #[must_use]
386  fn config(&self) -> &[Field] {
387    match self {
388      WickConfiguration::Component(v) => v.config(),
389      WickConfiguration::App(_v) => Default::default(),
390      WickConfiguration::Types(_v) => Default::default(),
391      WickConfiguration::Tests(_v) => Default::default(),
392      WickConfiguration::Lockdown(_v) => Default::default(),
393    }
394  }
395
396  /// Set the environment variables for a [WickConfiguration].
397  fn set_env(&mut self, env: Option<HashMap<String, String>>) -> &mut Self {
398    match self {
399      WickConfiguration::App(ref mut v) => {
400        v.env = env;
401      }
402      WickConfiguration::Component(_) => (),
403      WickConfiguration::Types(_) => (),
404      WickConfiguration::Tests(_) => (),
405      WickConfiguration::Lockdown(v) => v.env = env,
406    }
407    self
408  }
409
410  /// Get the kind of the inner configuration.
411  pub const fn kind(&self) -> ConfigurationKind {
412    match self {
413      WickConfiguration::Component(_) => ConfigurationKind::Component,
414      WickConfiguration::App(_) => ConfigurationKind::App,
415      WickConfiguration::Types(_) => ConfigurationKind::Types,
416      WickConfiguration::Tests(_) => ConfigurationKind::Tests,
417      WickConfiguration::Lockdown(_) => ConfigurationKind::Lockdown,
418    }
419  }
420
421  /// Get the resources for the configuration, if any
422  #[must_use]
423  pub fn resources(&self) -> &[Binding<ResourceDefinition>] {
424    match self {
425      WickConfiguration::Component(c) => c.resources(),
426      WickConfiguration::App(c) => c.resources(),
427      WickConfiguration::Types(_) => &[],
428      WickConfiguration::Tests(_) => &[],
429      WickConfiguration::Lockdown(_) => &[],
430    }
431  }
432
433  /// Get the version (if any) associated with the inner configuration.
434  #[must_use]
435  pub fn version(&self) -> Option<&str> {
436    match self {
437      WickConfiguration::Component(v) => v.version(),
438      WickConfiguration::App(v) => v.version(),
439      WickConfiguration::Types(v) => v.version(),
440      WickConfiguration::Tests(_) => None,
441      WickConfiguration::Lockdown(_) => None,
442    }
443  }
444
445  /// Get the package configuration (if any) associated with the inner configuration.
446  #[must_use]
447  pub fn package(&self) -> Option<&PackageConfig> {
448    match self {
449      WickConfiguration::Component(v) => v.package(),
450      WickConfiguration::App(v) => v.package(),
451      WickConfiguration::Types(v) => v.package(),
452      WickConfiguration::Tests(_) => None,
453      WickConfiguration::Lockdown(_) => None,
454    }
455  }
456
457  /// Unwrap the inner [ComponentConfiguration], returning an error if it is anything else.
458  #[allow(clippy::missing_const_for_fn)]
459  pub fn try_component_config(self) -> Result<ComponentConfiguration, Error> {
460    match self {
461      WickConfiguration::Component(v) => Ok(v),
462      _ => Err(Error::UnexpectedConfigurationKind(
463        ConfigurationKind::Component,
464        self.kind(),
465      )),
466    }
467  }
468
469  /// Unwrap the inner [AppConfiguration], returning an error if it is anything else.
470  #[allow(clippy::missing_const_for_fn)]
471  pub fn try_app_config(self) -> Result<AppConfiguration, Error> {
472    match self {
473      WickConfiguration::App(v) => Ok(v),
474      _ => Err(Error::UnexpectedConfigurationKind(ConfigurationKind::App, self.kind())),
475    }
476  }
477
478  /// Unwrap the inner [TestConfiguration], returning an error if it is anything else.
479  #[allow(clippy::missing_const_for_fn)]
480  pub fn try_test_config(self) -> Result<TestConfiguration, Error> {
481    match self {
482      WickConfiguration::Tests(v) => Ok(v),
483      _ => Err(Error::UnexpectedConfigurationKind(
484        ConfigurationKind::Tests,
485        self.kind(),
486      )),
487    }
488  }
489
490  /// Unwrap the inner [TypesConfiguration], returning an error if it is anything else.
491  #[allow(clippy::missing_const_for_fn)]
492  pub fn try_types_config(self) -> Result<TypesConfiguration, Error> {
493    match self {
494      WickConfiguration::Types(v) => Ok(v),
495      _ => Err(Error::UnexpectedConfigurationKind(
496        ConfigurationKind::Types,
497        self.kind(),
498      )),
499    }
500  }
501
502  /// Unwrap the inner [LockdownConfiguration], returning an error if it is anything else.
503  #[allow(clippy::missing_const_for_fn)]
504  pub fn try_lockdown_config(self) -> Result<LockdownConfiguration, Error> {
505    match self {
506      WickConfiguration::Lockdown(v) => Ok(v),
507      _ => Err(Error::UnexpectedConfigurationKind(
508        ConfigurationKind::Lockdown,
509        self.kind(),
510      )),
511    }
512  }
513
514  /// Initialize the configuration.
515  fn initialize(&mut self) -> Result<&Self, Error> {
516    match self {
517      WickConfiguration::Component(v) => {
518        v.initialize()?;
519      }
520      WickConfiguration::App(v) => {
521        v.initialize()?;
522      }
523      WickConfiguration::Types(_) => (),
524      WickConfiguration::Tests(v) => {
525        v.initialize()?;
526      }
527      WickConfiguration::Lockdown(v) => {
528        v.initialize()?;
529      }
530    }
531    self.update_baseurls();
532    Ok(self)
533  }
534
535  /// Set the source of the configuration if it is not already set on load.
536  pub fn set_source(&mut self, src: &Path) {
537    match self {
538      WickConfiguration::Component(v) => v.set_source(src),
539      WickConfiguration::App(v) => v.set_source(src),
540      WickConfiguration::Types(v) => v.set_source(src),
541      WickConfiguration::Tests(v) => v.set_source(src),
542      WickConfiguration::Lockdown(v) => v.set_source(src),
543    }
544  }
545
546  fn update_baseurls(&self) {
547    match self {
548      WickConfiguration::Component(v) => v.update_baseurls(),
549      WickConfiguration::App(v) => v.update_baseurls(),
550      WickConfiguration::Types(v) => v.update_baseurls(),
551      WickConfiguration::Tests(v) => v.update_baseurls(),
552      WickConfiguration::Lockdown(v) => v.update_baseurls(),
553    }
554  }
555
556  /// Get the source of the configuration.
557  #[must_use]
558  pub fn source(&self) -> Option<&Path> {
559    match self {
560      WickConfiguration::Component(v) => v.source.as_deref(),
561      WickConfiguration::App(v) => v.source.as_deref(),
562      WickConfiguration::Types(v) => v.source.as_deref(),
563      WickConfiguration::Tests(v) => v.source.as_deref(),
564      WickConfiguration::Lockdown(v) => v.source.as_deref(),
565    }
566  }
567}
568
569impl Renderable for WickConfiguration {
570  fn render_config(
571    &mut self,
572    source: Option<&Path>,
573    root_config: Option<&RuntimeConfig>,
574    env: Option<&HashMap<String, String>>,
575  ) -> Result<(), crate::error::ManifestError> {
576    match self {
577      WickConfiguration::Component(c) => c.render_config(source, root_config, env),
578      WickConfiguration::App(c) => c.render_config(source, root_config, env),
579      WickConfiguration::Types(c) => c.render_config(source, root_config, env),
580      WickConfiguration::Tests(c) => c.render_config(source, root_config, env),
581      WickConfiguration::Lockdown(c) => c.render_config(source, root_config, env),
582    }
583  }
584}
585
586impl Lockdown for WickConfiguration {
587  fn lockdown(&self, id: Option<&str>, lockdown: &LockdownConfiguration) -> Result<(), crate::lockdown::LockdownError> {
588    match self {
589      WickConfiguration::Component(v) => v.lockdown(id, lockdown),
590      WickConfiguration::App(v) => v.lockdown(id, lockdown),
591      WickConfiguration::Types(_) => Ok(()),
592      WickConfiguration::Tests(_) => Ok(()),
593      WickConfiguration::Lockdown(_) => Ok(()),
594    }
595  }
596}
597
598impl Imports for WickConfiguration {
599  fn imports(&self) -> &[Binding<ImportDefinition>] {
600    match self {
601      WickConfiguration::Component(c) => c.import(),
602      WickConfiguration::App(c) => c.import(),
603      WickConfiguration::Types(_) => &[],
604      WickConfiguration::Tests(_) => &[],
605      WickConfiguration::Lockdown(_) => &[],
606    }
607  }
608}
609
610impl RootConfig for WickConfiguration {
611  fn root_config(&self) -> Option<&RuntimeConfig> {
612    match self {
613      WickConfiguration::App(v) => v.root_config.as_ref(),
614      WickConfiguration::Component(v) => v.root_config.as_ref(),
615      WickConfiguration::Types(_) => None,
616      WickConfiguration::Tests(_) => None,
617      WickConfiguration::Lockdown(_) => None,
618    }
619  }
620
621  fn set_root_config(&mut self, config: Option<RuntimeConfig>) {
622    match self {
623      WickConfiguration::App(v) => {
624        v.root_config = config;
625      }
626      WickConfiguration::Component(v) => {
627        v.root_config = config;
628      }
629      WickConfiguration::Types(_) => (),
630      WickConfiguration::Tests(_) => (),
631      WickConfiguration::Lockdown(_) => (),
632    }
633  }
634}
635
636#[derive(Debug, Clone, Copy)]
637/// The kind of configuration loaded.
638#[must_use]
639
640pub enum ConfigurationKind {
641  /// An [app_config::AppConfiguration] configuration.
642  App,
643  /// A [component_config::ComponentConfiguration] configuration.
644  Component,
645  /// A [types_config::TypesConfiguration] configuration.
646  Types,
647  /// A [test_config::TestConfiguration] configuration.
648  Tests,
649  /// A [lockdown_config::LockdownConfiguration] configuration.
650  Lockdown,
651}
652
653impl std::fmt::Display for ConfigurationKind {
654  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655    match self {
656      ConfigurationKind::App => write!(f, "wick/app"),
657      ConfigurationKind::Component => write!(f, "wick/component"),
658      ConfigurationKind::Types => write!(f, "wick/types"),
659      ConfigurationKind::Tests => write!(f, "wick/tests"),
660      ConfigurationKind::Lockdown => write!(f, "wick/lockdown"),
661    }
662  }
663}