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