harmos_operations/storage/
write_file.rs1use super::IO_POLICY;
22use harmos_domain::MemorySize;
23use harmos_domain::model::storage::codec::{Nested, product};
24use harmos_domain::model::storage::{FileBatch, FileMetadata, FileReference};
25use harmos_domain::operations::ReplaySafety;
26#[cfg(feature = "runtime")]
27use harmos_sdk::operations::access::GrantAccess;
28#[cfg(feature = "runtime")]
29use harmos_storage::{ErasedCatalog, StorageManager};
30
31product!(WriteFile {
36 metadata: Nested<FileMetadata> => 1,
37});
38
39#[harmos::operation(
45 harmos_path = crate,
46 resources = [StorageManager<ErasedCatalog>, GrantAccess],
47 codec = manual,
48 id = "storage.write_file",
49 response = FileReference,
50 policy = IO_POLICY,
51 input = FileBatch,
52 visibility = Shared,
53 replay = ReplaySafety::Unknown,
54 grpc(package = "harmos.storage.v1", service = "Storage", method = "WriteFile"),
55 grant = Self::grant_for,
56)]
57#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct WriteFile {
59 pub metadata: FileMetadata,
60}
61
62impl WriteFile {
69 fn grant_for(request: &WriteFile) -> Option<harmos_domain::plugin::Grant> {
70 Some(harmos_domain::plugin::Grant::of(<Self as harmos_domain::OperationDeclaration>::ID, request.metadata.id.as_str().to_owned()))
71 }
72}
73
74impl MemorySize for WriteFile {
75 fn memory_size(&self) -> usize {
76 std::mem::size_of::<Self>().saturating_add(self.metadata.memory_size().saturating_sub(std::mem::size_of_val(&self.metadata)))
77 }
78}
79
80#[cfg(feature = "runtime")]
81mod execution {
82 use super::*;
83
84 use crate::{Error, Result};
88 use harmos_sdk::operations::{Operation, OperationDeclaration};
89 use harmos_storage::FileReference;
90
91 impl Operation for super::WriteFile {
92 type Error = Error;
93
94 async fn run(self, context: harmos_sdk::Context<'_, Self>) -> Result<harmos_sdk::operations::Response<FileReference>> {
95 let storage = context.resource::<harmos_storage::StorageManager<harmos_storage::ErasedCatalog>>();
96 let grants = context.resource::<harmos_sdk::operations::access::GrantAccess>();
97 let input = context.streams();
98
99 let host = grants;
100 let ctx = &host.0;
101 let grant = harmos_domain::plugin::Grant::of(WriteFile::ID, self.metadata.id.as_str().to_owned());
102 ctx.admit(&grant).map_err(Error::Access)?;
103 storage.retry_transient_persistence();
104 let mut file = storage.create_file(self.metadata.clone()).await?;
105
106 let mut offset = 0u64;
107 while let Some(batch) = input.recv().await {
108 let (batch, _claims) = batch.into_owned_request()?;
109 ctx.admit(&grant).map_err(Error::Access)?;
110 if batch.offset != offset || batch.bytes.is_empty() {
111 return Err(Error::InvalidInput {
112 field: "file batch",
113 reason: "nonempty batches must have contiguous byte offsets",
114 });
115 }
116 let next = offset.checked_add(batch.bytes.len() as u64).ok_or(Error::InvalidInput {
117 field: "file batch offset",
118 reason: "byte offset overflow",
119 })?;
120 file.write_all(batch.bytes).await?;
121 offset = next;
122 }
123
124 ctx.admit(&grant).map_err(Error::Access)?;
125 let reference = file.finish().await?;
126 storage.await_durable(&reference.position).await?;
127 Ok(reference.into())
128 }
129
130 fn classify(error: &Error) -> harmos_sdk::operations::FailureKind {
131 crate::storage::classify(error)
132 }
133 }
134}
135
136