Trait 기반 디자인으로 Rust에서 현대적인 유효성 검사 공개: Garde
Wenhao Wang
Dev Intern · Leapcell

소개
웹 서비스와 데이터 중심 애플리케이션의 세계에서 들어오는 데이터의 무결성과 정확성을 보장하는 것은 매우 중요합니다. 유효성이 검증되지 않은 입력은 보안 취약점, 예상치 못한 동작, 궁극적으로는 나쁜 사용자 경험의 일반적인 원인입니다. Rust의 강력한 타입 시스템은 안전성의 기초적인 계층을 제공하지만, 올바르게 타입화된 데이터 구조 내에서 유효하지 않은 값을 본질적으로 방지하지는 못합니다. 이것이 바로 유효성 검사 라이브러리가 필수적이 되는 이유입니다. 이를 통해 개발자는 데이터 콘텐츠에 대한 규칙을 정의하고 시행하여 예상되고 유효한 정보만 시스템을 통과하도록 할 수 있습니다. 전통적으로 Rust의 유효성 검사는 종종 상용구 코드나 프레임워크별 솔루션을 포함했습니다. 이 글에서는 이 중요한 과제를 해결하는 데 신선한 관점을 제공하는 현대적인 trait 기반 유효성 검사 라이브러리인 Garde를 소개합니다. 우아한 디자인을 탐색하고 Axum 및 Actix와 같은 인기 있는 비동기식 웹 프레임워크 내에서의 실제 유용성을 입증할 것입니다.
Garde의 핵심 개념 이해
실용적인 사항으로 들어가기 전에 Garde의 디자인을 뒷받침하는 핵심 개념을 명확하게 이해해 봅시다.
유효성 검사 Trait
Garde의 핵심은 유효성 검사 trait의 개념입니다. 단일의 모놀리식 유효성 검사 엔진에 의존하는 대신, Garde는 Rust의 강력한 trait 시스템을 활용합니다. 이는 유효성 검사 규칙이 trait으로 정의되어 모듈성, 확장성 및 컴파일 시간 보장을 허용한다는 것을 의미합니다. 어떤 타입이든 유효성 검사 trait을 구현하여 자체 유효성 검사 로직을 선언할 수 있습니다. 이 분산된 접근 방식은 더 작고 재사용 가능한 구성 요소에서 복잡한 유효성 검사 체계를 쉽게 구성할 수 있도록 합니다.
Derive Macro
이러한 유효성 검사 trait을 구현하는 프로세스를 단순화하기 위해 Garde는 강력한 derive macro를 제공합니다. 이러한 macro를 통해 개발자는 속성으로 struct와 enum을 주석 처리하여 필요한 유효성 검사 코드를 자동으로 생성할 수 있습니다. 이는 상용구를 크게 줄이고 가독성을 향상시켜 개발자가 반복적인 구현 세부 정보를 작성하는 대신 유효성 검사 규칙 정의에 집중할 수 있도록 합니다.
오류 처리
Garde는 유연한 오류 처리 메커니즘을 제공합니다. 유효성 검사에 실패하면 구조화된 오류 보고서를 생성하여 어떤 특정 규칙이 위반되었고 왜 위반되었는지 쉽게 식별할 수 있습니다. 이러한 정확한 피드백은 개발 중 디버깅과 API 소비자에게 의미 있는 오류 메시지를 제공하는 데 모두 중요합니다.
Garde의 아키텍처 및 구현
Garde의 디자인은 Validate trait을 중심으로 이루어집니다. 유효성 검사가 필요한 모든 타입은 이 trait을 구현해야 합니다. 앞에서 언급했듯이 #[derive(Validate)] macro는 이 프로세스를 단순화합니다.
간단한 사용자 등록 struct를 고려해 봅시다:
use garde::Validate; #[derive(Debug, Validate)] struct UserRegistration { #[garde(length(min = 3, max = 20))] #[garde(alpanum)] username: String, #[garde(email)] email: String, #[garde(length(min = 8))] #[garde(contains_digit)] #[garde(contains_uppercase)] password: String, }
이 예에서 UserRegistration struct는 #[derive(Validate)]로 주석 처리됩니다. 그런 다음 각 필드는 특정 garde 속성을 사용하여 유효성 검사 규칙을 정의합니다:
#[garde(length(min = 3, max = 20))]는username이 3자에서 20자 사이인지 확인합니다.#[garde(alpanum)]는username에 영숫자 문자만 포함되도록 합니다.#[garde(email)]는email필드를 표준 이메일 형식으로 확인합니다.#[garde(length(min = 8))],#[garde(contains_digit)],#[garde(contains_uppercase)]는 강력한 암호 정책을 시행합니다.
유효성 검사를 수행하려면 struct 인스턴스에서 validate() 메서드를 호출하면 됩니다:
let valid_user = UserRegistration { username: "testuser".to_string(), email: "test@example.com".to_string(), password: "StrongPassword123".to_string(), }; assert!(valid_user.validate(&()).is_ok()); let invalid_user = UserRegistration { username: "a".to_string(), // 너무 짧음 email: "invalid-email".to_string(), password: "weak".to_string(), }; assert!(valid_user.validate(&()).is_err());
validate(&()) 호출은 컨텍스트 매개변수를 사용하며, 이 간단한 경우에는 ()입니다. 더 복잡한 시나리오의 경우 사용자 정의 유효성 검사 로직에 필요한 데이터베이스 연결 또는 기타 서비스를 포함하는 컨텍스트 객체를 전달할 수 있습니다.
Axum 및 Actix에서의 애플리케이션
Garde는 웹 프레임워크와 통합될 때 진정한 빛을 발휘하며, API 요청 처리에 강력한 입력 유효성 검사가 중요합니다. Axum과 Actix 모두 들어오는 요청에서 데이터를 추출하고 사용자 정의 유효성 검사 로직을 통합하는 메커니즘을 제공합니다.
Axum 통합
Axum에서는 사용자 정의 추출기를 사용하여 Garde를 원활하게 통합할 수 있습니다. Garde의 Validate macro를 사용하는 타입에 대해 FromRequestParts 또는 FromRequest trait을 구현함으로써 들어오는 요청 본문을 자동으로 유효성 검사할 수 있습니다.
use axum::{ async_trait, extract::{FromRequest, rejection::FormRejection, Request}, http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde::{Deserialize, Serialize}; use garde::Validate; #[derive(Debug, Deserialize, Serialize, Validate)] struct CreateUserPayload { #[garde(length(min = 3, max = 20))] #[garde(alpanum)] username: String, #[garde(email)] email: String, #[garde(length(min = 8))] #[garde(contains_digit)] #[garde(contains_uppercase)] password: String, } struct ValidatedJson<T: Validate>(T); #[async_trait] impl<T> FromRequest for ValidatedJson<T> where T: Deserialize<'static> + Validate, { type Rejection = AppError; async fn from_request(req: Request, state: &()) -> Result<Self, Self::Rejection> { let Json(payload) = Json::<T>::from_request(req, state) .await .map_err(AppError::AxumJsonRejection)?; payload.validate(&()) .map_err(|e| AppError::ValidationFailed(e))?; Ok(ValidatedJson(payload)) } } pub enum AppError { ValidationFailed(garde::Errors), AxumJsonRejection(axum::extract::rejection::JsonRejection), // 기타 애플리케이션 오류 } impl IntoResponse for AppError { fn into_response(self) -> Response { match self { AppError::ValidationFailed(errors) => ( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "Validation failed", "details": errors.to_string() })), ).into_response(), AppError::AxumJsonRejection(rejection) => rejection.into_response(), } } } // Axum 핸들러에서: async fn create_user(ValidatedJson(payload): ValidatedJson<CreateUserPayload>) -> impl IntoResponse { // 여기에 도달하면 페이로드가 이미 유효성 검사되었습니다 println!("Creating user with username: {}", payload.username); StatusCode::CREATED }
여기서 ValidatedJson은 JSON 요청 본문을 먼저 역직렬화한 다음 Garde를 사용하여 유효성을 검사하는 사용자 정의 추출기 역할을 합니다. 유효성 검사에 실패하면 AppError::ValidationFailed가 반환되며, 이는 400 Bad Request 응답으로 변환됩니다.
Actix Web 통합
Actix Web도 사용자 정의 추출기를 용이하게 하여 Garde 통합을 간단하게 만듭니다.
use actix_web::{ web::{self, Json}, Responder, HttpResponse, }; use serde::{Deserialize, Serialize}; use garde::Validate; #[derive(Debug, Deserialize, Serialize, Validate)] struct CreateProductPayload { #[garde(length(min = 5))] name: String, #[garde(range(min = 1.0, max = 1000.0))] price: f64, } async fn create_product(payload: Json<CreateProductPayload>) -> impl Responder { match payload.validate(&()) { Ok(_) => { println!("Creating product: {}", payload.name); HttpResponse::Created().json(payload.0) } Err(errors) => { HttpResponse::BadRequest().json(serde_json::json!({ "error": "Validation failed", "details": errors.to_string(), })) } } } // Actix 앱 구성에서: // config.service(web::resource("/products").route(web::post().to(create_product)));
Actix 예에서는 Axum 예와 같은 전체 사용자 정의 추출기는 아니지만 (간결성을 위해), Json 추출기 직후에 유효성 검사 로직이 명확하게 적용됩니다. payload.validate(&()) 호출은 유효성 검사를 수행하고 결과에 따라 요청을 처리하거나 Garde의 오류 구조에서 파생된 오류 메시지와 함께 400 Bad Request를 반환합니다. 더 관용적인 Actix 통합을 위해서는 Axum 예와 유사한 사용자 정의 FromRequest 구현을 만들 수 있습니다.
결론
Garde는 Rust를 위한 현대적인 trait 기반 유효성 검사 라이브러리로, 데이터 무결성을 시행하는 명확하고 간결하며 매우 구성 가능한 방법을 제공합니다. Rust의 trait 시스템과 강력한 derive macro에 의존하여 상용구를 최소화하고 코드 가독성을 향상시키는 동시에 유연한 오류 처리는 자세한 피드백을 제공합니다. Axum 및 Actix와 같은 웹 프레임워크와 통합될 때 Garde는 유효한 데이터만 시스템에 들어가도록 하여 개발자가 더 강력하고 안전하며 유지보수 가능한 애플리케이션을 구축할 수 있도록 지원하며, Rust 개발자 도구 키트의 필수 도구가 됩니다. Garde는 복잡한 유효성 검사를 단순화하여 더 안정적인 Rust 애플리케이션을 육성합니다.

