1use std::collections::BTreeMap;
2
3use anyhow::Context;
4use chrono::{DateTime, Utc};
5use oci_client::client::{Config, ImageLayer};
6use serde::{Deserialize, Serialize};
7use sha2::Digest;
8
9use crate::{
10 Component, COMPONENT_OS, MODULE_OS, WASM_ARCHITECTURE, WASM_LAYER_MEDIA_TYPE,
11 WASM_MANIFEST_CONFIG_MEDIA_TYPE,
12};
13
14pub trait ToConfig {
16 fn to_config(&self) -> anyhow::Result<Config>;
18}
19
20#[derive(Serialize, Deserialize, Debug)]
22#[serde(rename_all = "camelCase")]
23pub struct WasmConfig {
24 pub created: DateTime<Utc>,
26 pub author: Option<String>,
28 pub architecture: String,
30 pub os: String,
35 pub layer_digests: Vec<String>,
40 pub component: Option<Component>,
43}
44
45pub struct AnnotatedWasmConfig<'a> {
46 pub config: &'a WasmConfig,
47 pub annotations: BTreeMap<String, String>,
48}
49
50impl WasmConfig {
51 pub async fn from_component(
55 path: impl AsRef<std::path::Path>,
56 author: Option<String>,
57 ) -> anyhow::Result<(Self, ImageLayer)> {
58 let raw = tokio::fs::read(path).await.context("Unable to read file")?;
59 Self::from_raw_component(raw, author)
60 }
61
62 pub fn from_raw_component(
64 raw: Vec<u8>,
65 author: Option<String>,
66 ) -> anyhow::Result<(Self, ImageLayer)> {
67 let component = Component::from_raw_component(&raw)?;
68 let config = Self {
69 created: Utc::now(),
70 author,
71 architecture: WASM_ARCHITECTURE.to_string(),
72 os: COMPONENT_OS.to_string(),
73 layer_digests: vec![sha256_digest(&raw)],
74 component: Some(component),
75 };
76 Ok((
77 config,
78 ImageLayer {
79 data: raw,
80 media_type: WASM_LAYER_MEDIA_TYPE.to_string(),
81 annotations: None,
82 },
83 ))
84 }
85
86 pub async fn from_module(
90 path: impl AsRef<std::path::Path>,
91 author: Option<String>,
92 ) -> anyhow::Result<(Self, ImageLayer)> {
93 let raw = tokio::fs::read(path).await.context("Unable to read file")?;
94 Self::from_raw_module(raw, author)
95 }
96
97 pub fn from_raw_module(
99 raw: Vec<u8>,
100 author: Option<String>,
101 ) -> anyhow::Result<(Self, ImageLayer)> {
102 let config = Self {
103 created: Utc::now(),
104 author,
105 architecture: WASM_ARCHITECTURE.to_string(),
106 os: MODULE_OS.to_string(),
107 layer_digests: vec![sha256_digest(&raw)],
108 component: None,
109 };
110 Ok((
111 config,
112 ImageLayer {
113 data: raw,
114 media_type: WASM_LAYER_MEDIA_TYPE.to_string(),
115 annotations: None,
116 },
117 ))
118 }
119
120 #[must_use]
122 pub fn with_annotations(
123 &'_ self,
124 annotations: BTreeMap<String, String>,
125 ) -> AnnotatedWasmConfig<'_> {
126 AnnotatedWasmConfig {
127 config: self,
128 annotations,
129 }
130 }
131}
132
133impl ToConfig for AnnotatedWasmConfig<'_> {
134 fn to_config(&self) -> anyhow::Result<Config> {
136 let mut config = self.config.to_config()?;
137 config.annotations = Some(self.annotations.clone());
138 Ok(config)
139 }
140}
141
142impl ToConfig for WasmConfig {
143 fn to_config(&self) -> anyhow::Result<Config> {
145 serde_json::to_vec(self)
146 .map(|data| Config {
147 data,
148 media_type: WASM_MANIFEST_CONFIG_MEDIA_TYPE.to_string(),
149 annotations: None,
150 })
151 .map_err(Into::into)
152 }
153}
154
155impl TryFrom<String> for WasmConfig {
159 type Error = anyhow::Error;
160
161 fn try_from(value: String) -> Result<Self, Self::Error> {
162 serde_json::from_str(&value).map_err(Into::into)
163 }
164}
165
166impl TryFrom<Vec<u8>> for WasmConfig {
167 type Error = anyhow::Error;
168
169 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
170 serde_json::from_slice(&value).map_err(Into::into)
171 }
172}
173
174impl TryFrom<&str> for WasmConfig {
175 type Error = anyhow::Error;
176
177 fn try_from(value: &str) -> Result<Self, Self::Error> {
178 serde_json::from_str(value).map_err(Into::into)
179 }
180}
181
182impl TryFrom<&[u8]> for WasmConfig {
183 type Error = anyhow::Error;
184
185 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
186 serde_json::from_slice(value).map_err(Into::into)
187 }
188}
189
190fn sha256_digest(bytes: &[u8]) -> String {
191 format!("sha256:{:x}", sha2::Sha256::digest(bytes))
192}