From e7376eeaefb5555d906c02a9282eaedf41833d78 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 16 Sep 2024 11:24:16 +0200 Subject: [PATCH] Move federation errors into separate enum (fixes #4926) --- .../apub/src/activities/block/block_user.rs | 7 +- .../apub/src/activities/community/announce.rs | 8 +-- crates/apub/src/activities/deletion/delete.rs | 4 +- .../src/activities/deletion/undo_delete.rs | 4 +- crates/apub/src/activities/mod.rs | 12 ++-- crates/apub/src/http/mod.rs | 6 +- crates/apub/src/lib.rs | 23 ++++--- crates/apub/src/mentions.rs | 8 ++- crates/apub/src/objects/comment.rs | 4 +- crates/apub/src/objects/instance.rs | 9 ++- crates/apub/src/objects/private_message.rs | 4 +- .../src/protocol/activities/voting/vote.rs | 4 +- crates/apub/src/protocol/objects/page.rs | 4 +- crates/utils/src/error.rs | 69 +++++++++++++------ crates/utils/tests/test_errors_used.rs | 1 + 15 files changed, 97 insertions(+), 70 deletions(-) diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index e6c97c424..a65082b90 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -39,10 +39,7 @@ use lemmy_db_schema::{ }, traits::{Bannable, Crud, Followable}, }; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyResult}; use url::Url; impl BlockUser { @@ -135,7 +132,7 @@ impl ActivityHandler for BlockUser { .object .inner() .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)?; + .ok_or(FederationError::UrlWithoutDomain)?; if context.settings().hostname == domain { return Err( anyhow!("Site bans from remote instance can't affect user's home instance").into(), diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index aebf28217..b5c4b43cf 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -26,7 +26,7 @@ use lemmy_db_schema::{ source::{activity::ActivitySendTargets, community::CommunityFollower}, CommunityVisibility, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}; use serde_json::Value; use url::Url; @@ -54,7 +54,7 @@ impl ActivityHandler for RawAnnouncableActivities { // This is only for sending, not receiving so we reject it. if let AnnouncableActivities::Page(_) = activity { - Err(LemmyErrorType::CannotReceivePage)? + Err(FederationError::CannotReceivePage)? } // Need to treat community as optional here because `Delete/PrivateMessage` gets routed through @@ -165,7 +165,7 @@ impl ActivityHandler for AnnounceActivity { // This is only for sending, not receiving so we reject it. if let AnnouncableActivities::Page(_) = object { - Err(LemmyErrorType::CannotReceivePage)? + Err(FederationError::CannotReceivePage)? } let community = object.community(context).await?; @@ -216,7 +216,7 @@ async fn can_accept_activity_in_community( if !community.local && !CommunityFollower::has_local_followers(&mut context.pool(), community.id).await? { - Err(LemmyErrorType::CommunityHasNoFollowers)? + Err(FederationError::CommunityHasNoFollowers)? } // Local only community can't federate if community.visibility != CommunityVisibility::Public { diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index d203aacf2..97b8288f4 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Reportable}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}; use url::Url; #[async_trait::async_trait] @@ -118,7 +118,7 @@ pub(in crate::activities) async fn receive_remove_action( match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { - Err(LemmyErrorType::OnlyLocalAdminCanRemoveCommunity)? + Err(FederationError::OnlyLocalAdminCanRemoveCommunity)? } let form = ModRemoveCommunityForm { mod_person_id: actor.id, diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index b50580852..9b66acbc0 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}; use url::Url; #[async_trait::async_trait] @@ -100,7 +100,7 @@ impl UndoDelete { match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { - Err(LemmyErrorType::OnlyLocalAdminCanRestoreCommunity)? + Err(FederationError::OnlyLocalAdminCanRestoreCommunity)? } let form = ModRemoveCommunityForm { mod_person_id: actor.id, diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index d81e7cabf..9cbe7abfd 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -42,7 +42,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; use serde::Serialize; use tracing::info; use url::{ParseError, Url}; @@ -81,7 +81,7 @@ pub(crate) async fn verify_person_in_community( ) -> LemmyResult<()> { let person = person_id.dereference(context).await?; if person.banned { - Err(LemmyErrorType::PersonIsBannedFromSite( + Err(FederationError::PersonIsBannedFromSite( person.actor_id.to_string(), ))? } @@ -89,7 +89,7 @@ pub(crate) async fn verify_person_in_community( let community_id = community.id; let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id).await?; if is_banned { - Err(LemmyErrorType::PersonIsBannedFromCommunity)? + Err(FederationError::PersonIsBannedFromCommunity)? } else { Ok(()) } @@ -126,7 +126,7 @@ pub(crate) async fn verify_mod_action( pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { if ![to, cc].iter().any(|set| set.contains(&public())) { - Err(LemmyErrorType::ObjectIsNotPublic)? + Err(FederationError::ObjectIsNotPublic)? } else { Ok(()) } @@ -138,7 +138,7 @@ where { let b: ObjectId = b.into(); if a != &b { - Err(LemmyErrorType::InvalidCommunity)? + Err(FederationError::InvalidCommunity)? } else { Ok(()) } @@ -146,7 +146,7 @@ where pub(crate) fn check_community_deleted_or_removed(community: &Community) -> LemmyResult<()> { if community.deleted || community.removed { - Err(LemmyErrorType::CannotCreatePostOrCommentInDeletedOrRemovedCommunity)? + Err(FederationError::CannotCreatePostOrCommentInDeletedOrRemovedCommunity)? } else { Ok(()) } diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index a01afe676..d913a14f3 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ source::{activity::SentActivity, community::Community}, CommunityVisibility, }; -use lemmy_utils::error::{LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use std::{ops::Deref, time::Duration}; use tokio::time::timeout; @@ -45,7 +45,7 @@ pub async fn shared_inbox( // consider the activity broken and move on. timeout(INCOMING_ACTIVITY_TIMEOUT, receive_fut) .await - .map_err(|_| LemmyErrorType::InboxTimeout)? + .map_err(|_| FederationError::InboxTimeout)? } /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub @@ -108,7 +108,7 @@ pub(crate) async fn get_activity( .into(); let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id) .await? - .ok_or(LemmyErrorType::CouldntFindActivity)?; + .ok_or(FederationError::CouldntFindActivity)?; let sensitive = activity.sensitive; if sensitive { diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index c8506da52..a04aec655 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ utils::{ActualDbPool, DbPool}, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}, CACHE_DURATION_FEDERATION, }; use moka::future::Cache; @@ -51,17 +51,18 @@ impl UrlVerifier for VerifyUrlData { let local_site_data = local_site_data_cached(&mut (&self.0).into()) .await .expect("read local site data"); + use FederationError::*; check_apub_id_valid(url, &local_site_data).map_err(|err| match err { LemmyError { - error_type: LemmyErrorType::FederationDisabled, + error_type: LemmyErrorType::FederationError(Some(FederationDisabled)), .. } => ActivityPubError::Other("Federation disabled".into()), LemmyError { - error_type: LemmyErrorType::DomainBlocked(domain), + error_type: LemmyErrorType::FederationError(Some(DomainBlocked(domain))), .. } => ActivityPubError::Other(format!("Domain {domain:?} is blocked")), LemmyError { - error_type: LemmyErrorType::DomainNotInAllowList(domain), + error_type: LemmyErrorType::FederationError(Some(DomainNotInAllowList(domain))), .. } => ActivityPubError::Other(format!("Domain {domain:?} is not in allowlist")), _ => ActivityPubError::Other("Failed validating apub id".into()), @@ -81,7 +82,7 @@ impl UrlVerifier for VerifyUrlData { fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> LemmyResult<()> { let domain = apub_id .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)? + .ok_or(FederationError::UrlWithoutDomain)? .to_string(); if !local_site_data @@ -90,7 +91,7 @@ fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> LemmyR .map(|l| l.federation_enabled) .unwrap_or(true) { - Err(LemmyErrorType::FederationDisabled)? + Err(FederationError::FederationDisabled)? } if local_site_data @@ -98,7 +99,7 @@ fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> LemmyR .iter() .any(|i| domain.to_lowercase().eq(&i.domain.to_lowercase())) { - Err(LemmyErrorType::DomainBlocked(domain.clone()))? + Err(FederationError::DomainBlocked(domain.clone()))? } // Only check this if there are instances in the allowlist @@ -108,7 +109,7 @@ fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> LemmyR .iter() .any(|i| domain.to_lowercase().eq(&i.domain.to_lowercase())) { - Err(LemmyErrorType::DomainNotInAllowList(domain))? + Err(FederationError::DomainNotInAllowList(domain))? } Ok(()) @@ -164,7 +165,7 @@ pub(crate) async fn check_apub_id_valid_with_strictness( ) -> LemmyResult<()> { let domain = apub_id .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)? + .ok_or(FederationError::UrlWithoutDomain)? .to_string(); let local_instance = context .settings() @@ -194,10 +195,10 @@ pub(crate) async fn check_apub_id_valid_with_strictness( let domain = apub_id .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)? + .ok_or(FederationError::UrlWithoutDomain)? .to_string(); if !allowed_and_local.contains(&domain) { - Err(LemmyErrorType::FederationDisabledByStrictAllowList)? + Err(FederationError::FederationDisabledByStrictAllowList)? } } Ok(()) diff --git a/crates/apub/src/mentions.rs b/crates/apub/src/mentions.rs index de472bd8a..4c56f1715 100644 --- a/crates/apub/src/mentions.rs +++ b/crates/apub/src/mentions.rs @@ -11,7 +11,11 @@ use lemmy_db_schema::{ traits::Crud, utils::DbPool, }; -use lemmy_utils::{error::LemmyResult, utils::mention::scrape_text_for_mentions, LemmyErrorType}; +use lemmy_utils::{ + error::{FederationError, LemmyResult}, + utils::mention::scrape_text_for_mentions, + LemmyErrorType, +}; use serde::{Deserialize, Serialize}; use serde_json::Value; use url::Url; @@ -57,7 +61,7 @@ pub async fn collect_non_local_mentions( &parent_creator .id() .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)? + .ok_or(FederationError::UrlWithoutDomain)? )), kind: MentionType::Mention, }; diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 466094b7f..18f1c6c61 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -32,7 +32,7 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}, utils::markdown::markdown_to_html, }; use std::ops::Deref; @@ -157,7 +157,7 @@ impl Object for ApubComment { .await .is_ok(); if post.locked && !is_mod_or_admin { - Err(LemmyErrorType::PostIsLocked)? + Err(FederationError::PostIsLocked)? } else { Ok(()) } diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index c67a223e0..6596320bc 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -41,12 +41,11 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_utils::{ - error::{LemmyError, LemmyResult}, + error::{FederationError, LemmyError, LemmyResult}, utils::{ markdown::markdown_to_html, slurs::{check_slurs, check_slurs_opt}, }, - LemmyErrorType, }; use std::ops::Deref; use tracing::debug; @@ -88,7 +87,7 @@ impl Object for ApubSite { } async fn delete(self, _data: &Data) -> LemmyResult<()> { - Err(LemmyErrorType::CantDeleteSite.into()) + Err(FederationError::CantDeleteSite.into()) } #[tracing::instrument(skip_all)] @@ -143,7 +142,7 @@ impl Object for ApubSite { .id .inner() .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)?; + .ok_or(FederationError::UrlWithoutDomain)?; let instance = DbInstance::read_or_create(&mut context.pool(), domain.to_string()).await?; let local_site = LocalSite::read(&mut context.pool()).await.ok(); @@ -218,7 +217,7 @@ pub(in crate::objects) async fn fetch_instance_actor_for_object + C debug!("Failed to dereference site for {}: {}", &instance_id, e); let domain = instance_id .domain() - .ok_or(LemmyErrorType::UrlWithoutDomain)?; + .ok_or(FederationError::UrlWithoutDomain)?; Ok( DbInstance::read_or_create(&mut context.pool(), domain.to_string()) .await? diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index fc9697391..43246b77d 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}, utils::markdown::markdown_to_html, }; use std::ops::Deref; @@ -115,7 +115,7 @@ impl Object for ApubPrivateMessage { check_apub_id_valid_with_strictness(note.id.inner(), false, context).await?; let person = note.attributed_to.dereference(context).await?; if person.banned { - Err(LemmyErrorType::PersonIsBannedFromSite( + Err(FederationError::PersonIsBannedFromSite( person.actor_id.to_string(), ))? } else { diff --git a/crates/apub/src/protocol/activities/voting/vote.rs b/crates/apub/src/protocol/activities/voting/vote.rs index 9fae264a5..883fc85fb 100644 --- a/crates/apub/src/protocol/activities/voting/vote.rs +++ b/crates/apub/src/protocol/activities/voting/vote.rs @@ -6,7 +6,7 @@ use crate::{ }; use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyResult}; use serde::{Deserialize, Serialize}; use strum::Display; use url::Url; @@ -35,7 +35,7 @@ impl TryFrom for VoteType { match value { 1 => Ok(VoteType::Like), -1 => Ok(VoteType::Dislike), - _ => Err(LemmyErrorType::InvalidVoteValue.into()), + _ => Err(FederationError::InvalidVoteValue.into()), } } } diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 9c37c88c3..9d10f9a9e 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -20,7 +20,7 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use itertools::Itertools; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -162,7 +162,7 @@ impl Page { .iter() .find(|a| a.kind == PersonOrGroupType::Person) .map(|a| ObjectId::::from(a.id.clone().into_inner())) - .ok_or_else(|| LemmyErrorType::PageDoesNotSpecifyCreator.into()), + .ok_or_else(|| FederationError::PageDoesNotSpecifyCreator.into()), } } } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index af0629e43..076bc3141 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -1,6 +1,6 @@ use cfg_if::cfg_if; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; +use std::{backtrace::Backtrace, fmt::Debug}; use strum::{Display, EnumIter}; #[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, EnumIter, Hash)] @@ -50,7 +50,6 @@ pub enum LemmyErrorType { CouldntFindRegistrationApplication, CouldntFindCommentReply, CouldntFindPrivateMessage, - CouldntFindActivity, PersonIsBlocked, CommunityIsBlocked, InstanceIsBlocked, @@ -80,20 +79,9 @@ pub enum LemmyErrorType { RegistrationClosed, RegistrationApplicationAnswerRequired, EmailAlreadyExists, - PersonIsBannedFromCommunity, - ObjectIsNotPublic, - InvalidCommunity, - CannotCreatePostOrCommentInDeletedOrRemovedCommunity, - CannotReceivePage, - OnlyLocalAdminCanRemoveCommunity, - OnlyLocalAdminCanRestoreCommunity, NoIdGiven, IncorrectLogin, ObjectNotLocal, - PostIsLocked, - PersonIsBannedFromSite(String), - InvalidVoteValue, - PageDoesNotSpecifyCreator, NoEmailSetup, LocalSiteNotSetup, EmailSmtpServerNeedsAPort, @@ -146,10 +134,6 @@ pub enum LemmyErrorType { Slurs, CouldntFindObject, RegistrationDenied(Option), - FederationDisabled, - DomainBlocked(String), - DomainNotInAllowList(String), - FederationDisabledByStrictAllowList, SiteNameRequired, SiteNameLengthOverflow, PermissiveRegex, @@ -163,16 +147,46 @@ pub enum LemmyErrorType { /// Thrown when an API call is submitted with more than 1000 array elements, see /// [[MAX_API_PARAM_ELEMENTS]] TooManyItems, - CommunityHasNoFollowers, BanExpirationInPast, InvalidUnixTime, InvalidBotAction, CantBlockLocalInstance, + Unknown(String), + UrlLengthOverflow, + FederationError(Option), +} + +/// Federation related errors, these dont need to be translated. +#[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, EnumIter, Hash)] +#[cfg_attr(feature = "full", derive(ts_rs::TS))] +#[cfg_attr(feature = "full", ts(export))] +#[serde(tag = "error", content = "message", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FederationError { + // TODO: merge into a single NotFound error + CouldntFindActivity, + PersonIsBannedFromCommunity, + InvalidCommunity, + CannotCreatePostOrCommentInDeletedOrRemovedCommunity, + CannotReceivePage, + OnlyLocalAdminCanRemoveCommunity, + OnlyLocalAdminCanRestoreCommunity, + PostIsLocked, + PersonIsBannedFromSite(String), + InvalidVoteValue, + PageDoesNotSpecifyCreator, + CouldntGetComments, + CouldntGetPosts, + FederationDisabled, + DomainBlocked(String), + DomainNotInAllowList(String), + FederationDisabledByStrictAllowList, + ContradictingFilters, + CommunityHasNoFollowers, UrlWithoutDomain, InboxTimeout, - Unknown(String), CantDeleteSite, - UrlLengthOverflow, + ObjectIsNotPublic, } cfg_if! { @@ -249,6 +263,17 @@ cfg_if! { } } + impl From for LemmyError { + fn from(error_type: FederationError) -> Self { + let inner = anyhow::anyhow!("{}", error_type); + LemmyError { + error_type: LemmyErrorType::FederationError(Some(error_type)), + inner, + context: Backtrace::capture(), + } + } + } + pub trait LemmyErrorExt> { fn with_lemmy_type(self, error_type: LemmyErrorType) -> LemmyResult; } @@ -299,12 +324,12 @@ cfg_if! { #[test] fn deserializes_with_message() { - let reg_banned = LemmyErrorType::PersonIsBannedFromSite(String::from("reason")); + let reg_banned = LemmyErrorType::PictrsResponseError(String::from("reason")); let err = LemmyError::from(reg_banned).error_response(); let json = String::from_utf8(err.into_body().try_into_bytes().unwrap().to_vec()).unwrap(); assert_eq!( &json, - "{\"error\":\"person_is_banned_from_site\",\"message\":\"reason\"}" + "{\"error\":\"pictrs_response_error\",\"message\":\"reason\"}" ) } diff --git a/crates/utils/tests/test_errors_used.rs b/crates/utils/tests/test_errors_used.rs index 562d399dc..3a8b10249 100644 --- a/crates/utils/tests/test_errors_used.rs +++ b/crates/utils/tests/test_errors_used.rs @@ -24,6 +24,7 @@ fn test_errors_used() { let grep_apub = grep_apub .current_dir(current_dir.clone()) .arg("-R") + .arg("--exclude-dir=api") .arg(error.to_string()) .arg("crates/apub/"); let output = grep_apub.output().unwrap();