1use crate::item_hash::ItemHash;
2use crate::message::execution::base::{Encoding, ExecutableContent, Interface};
3use crate::message::execution::environment::{FunctionEnvironment, FunctionTriggers};
4use crate::toolkit::serde::{default_some_false, default_true};
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct FunctionRuntime {
10 #[serde(rename = "ref")]
11 pub reference: ItemHash,
12 #[serde(default = "default_true")]
13 pub use_latest: bool,
14 pub comment: String,
15}
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct CodeContent {
19 pub encoding: Encoding,
20 pub entrypoint: String,
21 #[serde(rename = "ref")]
23 pub reference: ItemHash,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub interface: Option<Interface>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub args: Option<Vec<String>>,
28 #[serde(default)]
29 pub use_latest: bool,
30}
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct DataContent {
34 pub encoding: Encoding,
35 pub mount: PathBuf,
36 #[serde(rename = "ref")]
37 pub reference: ItemHash,
38 #[serde(
39 default = "default_some_false",
40 skip_serializing_if = "Option::is_none"
41 )]
42 pub use_latest: Option<bool>,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct Export {
47 pub encoding: Encoding,
48 pub mount: PathBuf,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct ProgramContent {
53 #[serde(flatten)]
54 pub base: ExecutableContent,
55 pub code: CodeContent,
57 pub runtime: FunctionRuntime,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub data: Option<DataContent>,
62 pub environment: FunctionEnvironment,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub export: Option<Export>,
67 pub on: FunctionTriggers,
69}
70
71impl ProgramContent {
72 pub fn executable_content(&self) -> &ExecutableContent {
73 &self.base
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::chain::{Address, Chain, Signature};
81 use crate::message::base_message::MessageContentEnum;
82 use crate::message::execution::environment::MachineResources;
83 use crate::message::execution::volume::{BaseVolume, ImmutableVolume, MachineVolume};
84 use crate::message::{ContentSource, Message, MessageType};
85 use crate::timestamp::Timestamp;
86 use crate::{channel, item_hash};
87 use assert_matches::assert_matches;
88 use memsizes::MiB;
89 use std::collections::HashMap;
90
91 const PROGRAM_FIXTURE: &str = include_str!(concat!(
92 env!("CARGO_MANIFEST_DIR"),
93 "/../../fixtures/messages/program/program.json"
94 ));
95
96 const PROGRAM_WITH_EMPTY_ARRAY_AS_METADATA: &str = include_str!(concat!(
97 env!("CARGO_MANIFEST_DIR"),
98 "/../../fixtures/messages/program/program-with-array-as-metadata.json"
99 ));
100
101 #[test]
102 fn test_deserialize_program_message() {
103 let message: Message = serde_json::from_str(PROGRAM_FIXTURE).unwrap();
104
105 assert_eq!(
106 message.sender,
107 Address::from("0x9C2FD74F9CA2B7C4941690316B0Ebc35ce55c885".to_string())
108 );
109 assert_eq!(message.chain, Chain::Ethereum);
110 assert_eq!(
111 message.signature,
112 Signature::from(
113 "0x421c656709851fba752f323a117bc7a07f175a4dd7faf1d8fc1cd9a99028081a6419f9e8b0a7cd454bfef1c52d1f0675a7a59a7d07eb4ebdb22e18bbaf415f881c".to_string()
114 )
115 );
116 assert_matches!(message.message_type, MessageType::Program);
117 assert_matches!(
118 message.content_source,
119 ContentSource::Inline { item_content: _ }
120 );
121 assert_eq!(
122 &message.item_hash.to_string(),
123 "acab01087137c68a5e84734e75145482651accf3bea80fb9b723b761639ecc1c"
124 );
125 assert_eq!(message.time, Timestamp::from(1757026128.773));
126 assert_eq!(message.channel, Some(channel!("ALEPH-CLOUDSOLUTIONS")));
127
128 assert_eq!(
130 &message.content.address,
131 &Address::from("0x9C2FD74F9CA2B7C4941690316B0Ebc35ce55c885".to_string())
132 );
133 assert_eq!(&message.content.time, &Timestamp::from(1757026128.773));
134 assert_eq!(message.sent_at(), &message.content.time);
135
136 let program_content = match message.content() {
138 MessageContentEnum::Program(content) => content,
139 other => {
140 panic!("Expected MessageContentEnum::Program, got {:?}", other);
141 }
142 };
143
144 assert!(!program_content.base.allow_amend);
145 assert_eq!(
146 program_content.base.metadata,
147 Some(HashMap::from([(
148 "name".to_string(),
149 serde_json::Value::String("Hoymiles".to_string())
150 )]))
151 );
152 assert_eq!(program_content.base.variables, Some(HashMap::new()));
153 assert_eq!(
154 program_content.base.resources,
155 MachineResources {
156 vcpus: 2,
157 memory: MiB::from(4096),
158 seconds: 30,
159 published_ports: None,
160 }
161 );
162 assert_matches!(program_content.base.authorized_keys, None);
163 assert_eq!(
164 program_content.environment,
165 FunctionEnvironment {
166 reproducible: false,
167 internet: true,
168 aleph_api: true,
169 shared_cache: false,
170 }
171 );
172 assert_eq!(
173 program_content.base.volumes,
174 vec![MachineVolume::Immutable(ImmutableVolume {
175 base: BaseVolume {
176 comment: None,
177 mount: Some(PathBuf::from("/opt/packages"))
178 },
179 reference: item_hash!(
180 "8df728d560ed6e9103b040a6b5fc5417e0a52e890c12977464ebadf9becf1bf6"
181 ),
182 use_latest: true,
183 })]
184 );
185 assert_eq!(program_content.base.replaces, None);
186 assert_eq!(
187 program_content.code,
188 CodeContent {
189 encoding: Encoding::Zip,
190 entrypoint: "main:app".to_string(),
191 reference: item_hash!(
192 "9a4735bca0d3f7032ddd6659c35387b57b470550c931841e6862ece4e9e6523e"
193 ),
194 interface: None,
195 args: None,
196 use_latest: true,
197 }
198 );
199 assert_eq!(
200 program_content.runtime,
201 FunctionRuntime {
202 reference: item_hash!(
203 "63f07193e6ee9d207b7d1fcf8286f9aee34e6f12f101d2ec77c1229f92964696"
204 ),
205 use_latest: true,
206 comment: "Aleph Alpine Linux with Python 3.12".to_string(),
207 }
208 );
209 assert_eq!(program_content.data, None);
210 assert_eq!(program_content.export, None);
211 assert_eq!(
212 program_content.on,
213 FunctionTriggers {
214 http: true,
215 persistent: Some(false)
216 }
217 );
218
219 assert!(!message.confirmed());
221 assert!(message.confirmed_at().is_none());
222 assert!(message.confirmations.is_empty());
223
224 message.verify_item_hash().unwrap();
225 }
226
227 #[test]
228 fn load_program_with_empty_array_as_metadata() {
232 let message: Message = serde_json::from_str(PROGRAM_WITH_EMPTY_ARRAY_AS_METADATA).unwrap();
233
234 let program_content = match message.content() {
236 MessageContentEnum::Program(content) => content,
237 other => {
238 panic!("Expected MessageContentEnum::Program, got {:?}", other);
239 }
240 };
241
242 assert_matches!(program_content.base.metadata, Some(ref map) if map.is_empty());
243 }
244}