Files
core/rust/src/compiler/check/upload/mod.rs
T
2026-11-03 03:32:07 +00:00

81 lines
3.0 KiB
Rust

//! Upload 只在 HTTP App 与同领域 Port/Adapter 之间移动,不放宽事务和并发检查。
mod ownership;
use super::Context;
use crate::compiler::model::*;
use crate::syntax::{ast, diagnostic::Diagnostic};
pub(super) use ownership::check;
pub(super) fn nested(kind: &Type) -> bool {
match kind {
Type::Upload => true,
Type::Optional(inner)
| Type::List(inner)
| Type::Stream(inner)
| Type::AsyncStream(inner)
| Type::Channel(inner) => nested(inner),
Type::Map(key, value) | Type::MapEntry(key, value) => nested(key) || nested(value),
Type::Outputs(fields) => fields.iter().any(|(_, kind)| nested(kind)),
_ => false,
}
}
impl Context<'_> {
pub(super) fn upload(
&mut self,
operation: upload::Operation,
arguments: &[ast::Expression],
expected: Option<&Type>,
source: &ast::Expression,
) -> Result<Expression, Diagnostic> {
let role = package::role(&self.definition_names.packages[self.source]);
let allowed = if operation == upload::Operation::Put {
self.definition_names.packages[self.source] == "dever.storage"
} else {
matches!(role, Some((_, "app" | "adapter")))
};
if !allowed {
return Err(self.error(
source,
"Upload operations require App; storage.put requires its Adapter",
));
}
let [argument] = arguments else {
return Err(self.error(source, "Upload operation requires one Upload"));
};
let value = self.expression(argument, Some(&Type::Upload))?;
let value_type = match operation {
upload::Operation::Size => Type::Int,
upload::Operation::Filename | upload::Operation::ContentType => Type::Text,
upload::Operation::Close => Type::Unit,
upload::Operation::Put => {
let index = self
.definition_names
.resolve("dever.storage.PutResult", self.source, source.span)?
.ok_or_else(|| {
self.error(source, "storage.put requires the official PutResult")
})?;
let valid = matches!(&self.definitions[index].shape, Shape::Choice(variants)
if variants.len() == 2 && variants[0].name == "Stored" && !variants[0].error
&& variants[0].fields == [("key".into(), Type::Uuid)]
&& variants[1].name == "Failed" && variants[1].error
&& variants[1].fields == [("message".into(), Type::Text)]);
if !valid {
return Err(self.error(source, "invalid storage.PutResult contract"));
}
Type::Named(index)
}
};
self.coerce(
Expression {
kind: Node::Upload(operation, vec![value]),
value_type,
span: source.span,
},
expected,
)
}
}