extern crate rustc_ast;
use crate::lint_utils::{is_in_contract_module_ast, is_in_domain_path, use_tree_to_strings};
use rustc_ast::{Item, ItemKind, Ty, TyKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
dylint_linting::declare_early_lint! {
#[doc = include_str!("../../docs/de03_domain_layer/de0308_no_http_in_domain/README.md")]
pub DE0308_NO_HTTP_IN_DOMAIN,
Deny,
"domain modules should not reference HTTP types or status codes (DE0308)"
}
const HTTP_PATTERNS: &[&str] = &["http", "axum", "hyper"];
fn matches_http_pattern(path: &str) -> Option<&'static str> {
for pattern in HTTP_PATTERNS {
if path == *pattern || path.starts_with(&format!("{pattern}::")) {
return Some(pattern);
}
}
None
}
fn check_use_item(cx: &EarlyContext<'_>, item: &Item, tree: &rustc_ast::UseTree) {
for path_str in use_tree_to_strings(tree) {
if let Some(pattern) = matches_http_pattern(&path_str) {
cx.span_lint(DE0308_NO_HTTP_IN_DOMAIN, item.span, |diag| {
diag.primary_message(format!(
"domain module imports HTTP type `{pattern}` (DE0308)"
));
diag.help("domain should be transport-agnostic; handle HTTP in api/ layer");
});
return;
}
}
}
fn check_type_in_domain(cx: &rustc_lint::EarlyContext<'_>, ty: &Ty) {
match &ty.kind {
TyKind::Path(_, path) => {
let path_str = path
.segments
.iter()
.map(|seg| seg.ident.name.as_str())
.collect::<Vec<_>>()
.join("::");
if matches_http_pattern(&path_str).is_some() {
cx.span_lint(DE0308_NO_HTTP_IN_DOMAIN, ty.span, |diag| {
diag.primary_message(format!(
"domain module uses HTTP type `{}` (DE0308)",
path_str
));
diag.help("domain should be transport-agnostic; handle HTTP in api/ layer");
});
return;
}
for segment in &path.segments {
if let Some(args) = &segment.args
&& let rustc_ast::GenericArgs::AngleBracketed(ref angle_args) = **args
{
for arg in &angle_args.args {
if let rustc_ast::AngleBracketedArg::Arg(rustc_ast::GenericArg::Type(
inner_ty,
)) = arg
{
check_type_in_domain(cx, inner_ty);
}
}
}
}
}
TyKind::Ref(_, mut_ty) => {
check_type_in_domain(cx, &mut_ty.ty);
}
TyKind::Slice(inner_ty) => {
check_type_in_domain(cx, inner_ty);
}
TyKind::Array(inner_ty, _) => {
check_type_in_domain(cx, inner_ty);
}
TyKind::Ptr(mut_ty) => {
check_type_in_domain(cx, &mut_ty.ty);
}
TyKind::Tup(types) => {
for inner_ty in types {
check_type_in_domain(cx, inner_ty);
}
}
TyKind::TraitObject(bounds, _) => {
for bound in bounds {
if let rustc_ast::GenericBound::Trait(trait_ref) = bound {
let path = &trait_ref.trait_ref.path;
let path_str = path
.segments
.iter()
.map(|seg| seg.ident.name.as_str())
.collect::<Vec<_>>()
.join("::");
if matches_http_pattern(&path_str).is_some() {
cx.span_lint(DE0308_NO_HTTP_IN_DOMAIN, ty.span, |diag| {
diag.primary_message(format!(
"domain module uses HTTP trait `{}` (DE0308)",
path_str
));
diag.help(
"domain should be transport-agnostic; handle HTTP in api/ layer",
);
});
return;
}
}
}
}
TyKind::ImplTrait(_, bounds) => {
for bound in bounds {
if let rustc_ast::GenericBound::Trait(trait_ref) = bound {
let path = &trait_ref.trait_ref.path;
let path_str = path
.segments
.iter()
.map(|seg| seg.ident.name.as_str())
.collect::<Vec<_>>()
.join("::");
if matches_http_pattern(&path_str).is_some() {
cx.span_lint(DE0308_NO_HTTP_IN_DOMAIN, ty.span, |diag| {
diag.primary_message(format!(
"domain module uses HTTP trait `{}` (DE0308)",
path_str
));
diag.help(
"domain should be transport-agnostic; handle HTTP in api/ layer",
);
});
return;
}
}
}
}
_ => {}
}
}
impl EarlyLintPass for De0308NoHttpInDomain {
fn check_item(&mut self, cx: &rustc_lint::EarlyContext<'_>, item: &Item) {
if !is_in_domain_path(cx.sess().source_map(), item.span)
|| is_in_contract_module_ast(cx, item)
{
return;
}
match &item.kind {
ItemKind::Use(use_tree) => {
check_use_item(cx, item, use_tree);
}
ItemKind::Struct(_, _, variant_data) => {
for field in variant_data.fields() {
check_type_in_domain(cx, &field.ty);
}
}
ItemKind::Enum(_, _, enum_def) => {
for variant in &enum_def.variants {
for field in variant.data.fields() {
check_type_in_domain(cx, &field.ty);
}
}
}
ItemKind::Fn(fn_item) => {
for param in &fn_item.sig.decl.inputs {
check_type_in_domain(cx, ¶m.ty);
}
if let rustc_ast::FnRetTy::Ty(ret_ty) = &fn_item.sig.decl.output {
check_type_in_domain(cx, ret_ty);
}
}
ItemKind::TyAlias(ty_alias) => {
if let Some(ty) = &ty_alias.ty {
check_type_in_domain(cx, ty);
}
}
_ => {}
}
}
}