Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+216
View File
@@ -0,0 +1,216 @@
use super::package::packages;
use dever::compiler::model::{Node, Program, Signature, Statement};
mod boundary;
mod effects;
mod settings;
const APP: &str = "type Problem { error Missing(message: Text)\n error Denied(code: Int) }\nread(value: Int) (answer: Int) { answer = port.read(value) }";
const PORT: &str = "read(value: Int) (answer: Int) fails app.Problem";
const ADAPTER: &str = "port.read(value: Int) (answer: Int) { answer = helper(value) }\nhelper(value: Int) (answer: Int) { answer = value + 1 }";
fn signature<'program>(program: &'program Program, name: &str) -> &'program Signature {
&program
.functions
.iter()
.find(|function| function.signature.name == name)
.unwrap()
.signature
}
#[test]
fn port_alias_binds_directly_and_preserves_the_declared_error_set() {
let program = packages(&[
("shop.account.app", APP),
("shop.account.port", PORT),
("shop.account.adapter", ADAPTER),
("shop.account.api", "cmd read = app.read"),
])
.unwrap();
assert_eq!(program.functions.len(), 3);
let app = &program.functions[program.commands[0].function];
let Statement::Assign { value, .. } = &app.clauses[0].body[0] else {
panic!()
};
let Node::Call(index, _) = value.kind else {
panic!()
};
assert_eq!(program.functions[index].signature.name, "port.read");
assert_eq!(app.signature.failures.len(), 2);
assert_eq!(
app.signature.failures,
program.functions[index].signature.failures
);
assert!(!app.signature.effects.unmediated_io);
assert!(program.api_snapshot().contains("Problem.Denied"));
}
#[test]
fn topics_dtos_static_handlers_and_adapter_clauses_share_existing_type_checks() {
let program = packages(&[
(
"shop.account.app",
"type Problem { error Missing(message: Text) }\nread(value: Int) (answer: Int) { row = port.mail.Row { value = value }\nanswer = apply(port.mail.read, row) }\napply(action: handler(row: port.mail.Row) (answer: Int), row: port.mail.Row) (answer: Int) { answer = action(row) }",
),
(
"shop.account.port.mail",
"type Row { value: Int }\nread(row: Row) (answer: Int) fails app.Problem",
),
(
"shop.account.adapter.local",
"port.mail.read(row: port.mail.Row) (answer: Int) { answer = row.value }",
),
]);
// App 的公开 handler 参数不能泄漏 Port DTO;在领域内经普通 App 函数也不能绕过。
assert_eq!(program.unwrap_err().code, "C006");
let program = packages(&[
("shop.account.app", APP),
("shop.account.port", PORT),
("shop.account.adapter", "port.read(value: Int < 0) (answer: Int) { fail(app.Problem.Missing(\"negative\")) }\nport.read(value: other) (answer: Int) { answer = value }"),
]).unwrap();
assert_eq!(signature(&program, "read").failures.len(), 2);
assert!(packages(&[
("shop.account.app", "type Problem { error Missing(message: Text) }\nread(value: Int) (answer: Int) { row = port.mail.Row { value = value }\nanswer = port.mail.read(row) }"),
("shop.account.port.mail", "type Row { value: Int }\nread(row: Row) (answer: Int) fails app.Problem"),
("shop.account.adapter.local", "port.mail.read(row: port.mail.Row) (answer: Int) { answer = row.value }"),
]).is_ok());
assert!(
packages(&[
(
"shop.account.app",
&APP.replace("port.read(value)", "sum(each(port.read, [value]))")
),
("shop.account.port", PORT),
("shop.account.adapter", ADAPTER),
])
.is_ok()
);
}
#[test]
fn ports_reject_missing_mismatched_and_partial_implementations() {
for adapter in [
"port.read(wrong: Int) (answer: Int) { answer = wrong }",
"port.read(value: Text) (answer: Int) { answer = 1 }",
"port.read(value: Int) (wrong: Int) { wrong = value }",
"port.read(value: Int, action: handler() ()) (answer: Int) { answer = value }",
"port.unknown(value: Int) (answer: Int) { answer = value }",
"helper(value: Int) (answer: Int) { answer = value }",
] {
assert!(
packages(&[
("shop.account.app", APP),
("shop.account.port", PORT),
("shop.account.adapter", adapter)
])
.is_err(),
"{adapter}"
);
}
assert!(packages(&[("shop.account.app", APP), ("shop.account.port", PORT)]).is_err());
assert!(
packages(&[
("shop.account.app", APP),
(
"shop.account.port",
&format!("{PORT}\nwrite(value: Int) () fails app.Problem")
),
("shop.account.adapter.first", ADAPTER),
(
"shop.account.adapter.second",
"port.write(value: Int) () {}"
),
])
.unwrap_err()
.message
.contains("complete Port")
);
assert!(
packages(&[
("shop.account.app", APP),
("shop.account.port", PORT),
("shop.account.adapter.first", ADAPTER),
("shop.account.adapter.second", ADAPTER),
])
.is_ok()
);
}
#[test]
fn port_declarations_require_plain_types_and_public_explicit_errors() {
for port in [
"read(value: Int) (answer: Int) { answer = value }",
"read(value: Int < 0) (answer: Int) fails app.Problem",
"read(value: other) (answer: Int) fails app.Problem",
"read(value: handler() ()) (answer: Int) fails app.Problem",
"read(value: Int) (answer: Int > 0) fails app.Problem",
"read(value: Int) (answer: Int) pure fails app.Problem",
"read(value: Int) (answer: Int) recover(\"ignore\") fails app.Problem",
"read(value: Int) (answer: Int) fails Int",
"read(value: Int) (answer: Int) fails app.Problem?",
"read(value: Int) (answer: Int) fails app.Record",
"read(value: Int) (answer: Int) fails app.State",
"type Local { error Failed }\nread(value: Int) (answer: Int) fails Local",
"public read(value: Int) (answer: Int) fails app.Problem",
] {
let app = format!("{APP}\ntype Record {{ value: Int }}\ntype State {{ Ready }}");
assert!(
packages(&[
("shop.account.app", &app),
("shop.account.port", port),
("shop.account.adapter", ADAPTER)
])
.is_err(),
"{port}"
);
}
assert!(packages(&[("main", "read(value: Int) (answer: Int) fails Problem")]).is_err());
assert_eq!(
packages(&[
("shop.account.app", APP),
("shop.account.port", &format!("{PORT}\n{PORT}")),
("shop.account.adapter", ADAPTER)
])
.unwrap_err()
.code,
"C002"
);
}
#[test]
fn implementations_cannot_propagate_undeclared_errors() {
let adapter = "type Private { error Other }\nport.read(value: Int) (answer: Int) { fail(Private.Other()) }";
assert_eq!(
packages(&[
("shop.account.app", APP),
("shop.account.port", PORT),
("shop.account.adapter", adapter)
])
.unwrap_err()
.code,
"C012"
);
let app = "type Problem { error Missing(message: Text)\nerror Denied(code: Int) }\ntype ReadResult { Ready(value: Int)\nerror Failed(problem: Problem) }\nread(value: Int) (answer: ReadResult) { answer = result(port.read(value)) }";
assert!(
packages(&[
("shop.account.app", app),
("shop.account.port", PORT),
("shop.account.adapter", ADAPTER)
])
.is_ok()
);
let app = app.replace(
"answer = result(port.read(value))",
"lost = result(port.read(value))\nanswer = ReadResult.Ready(value)",
);
assert_eq!(
packages(&[
("shop.account.app", &app),
("shop.account.port", PORT),
("shop.account.adapter", ADAPTER)
])
.unwrap_err()
.code,
"C012"
);
}