Skip to content

Commit 38a0c11

Browse files
committed
Implement application layer
- Add DTOs with validation - Implement use cases for create and get operations - Add unit tests for use cases - Update progress tracking
1 parent 2bcaa85 commit 38a0c11

File tree

4 files changed

+460
-24
lines changed

4 files changed

+460
-24
lines changed

jump/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ tokio = { version = "1.32", features = ["full"] }
1919
serde = { version = "1.0", features = ["derive"] }
2020
serde_json = "1.0"
2121

22+
# Validation
23+
validator = { version = "0.16", features = ["derive"] }
24+
2225
# Logging and tracing
2326
tracing = "0.1"
2427
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

jump/src/application/dtos.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//! Data Transfer Objects (DTOs) for the application layer.
2+
//!
3+
//! This module contains all the DTOs used for:
4+
//! - API request/response objects
5+
//! - Data validation
6+
//! - Serialization/deserialization of payloads
7+
//!
8+
//! DTOs are designed to be API-version specific and decouple the domain model
9+
//! from the external interface.
10+
11+
use chrono::{DateTime, Utc};
12+
use serde::{Deserialize, Serialize};
13+
use validator::Validate;
14+
15+
/// Request DTO for creating a new payload.
16+
///
17+
/// This struct represents the expected JSON structure for POST /api/v1/payloads
18+
///
19+
/// # Example JSON
20+
/// ```json
21+
/// {
22+
/// "content": "Your payload content here",
23+
/// "mime_type": "text/plain",
24+
/// "expiry_time": "2024-03-14T12:00:00Z"
25+
/// }
26+
/// ```
27+
#[derive(Debug, Deserialize, Validate)]
28+
pub struct CreatePayloadRequest {
29+
/// The content to be shared. Must not be empty.
30+
#[validate(length(min = 1, message = "Content cannot be empty"))]
31+
#[validate(length(max = 1048576, message = "Content cannot exceed 1MB"))]
32+
pub content: String,
33+
34+
/// Optional MIME type of the content. If not provided, defaults to "text/plain".
35+
#[validate(regex(
36+
path = "MIME_TYPE_REGEX",
37+
message = "Invalid MIME type format"
38+
))]
39+
pub mime_type: Option<String>,
40+
41+
/// Optional expiry time. If not provided, defaults to 24 hours from creation.
42+
pub expiry_time: Option<DateTime<Utc>>,
43+
}
44+
45+
/// Response DTO for successful payload creation.
46+
///
47+
/// This struct represents the JSON structure returned after successfully
48+
/// creating a new payload.
49+
#[derive(Debug, Serialize)]
50+
pub struct CreatePayloadResponse {
51+
/// The unique identifier for accessing the payload
52+
pub hash_id: String,
53+
54+
/// The stored content
55+
pub content: String,
56+
57+
/// The MIME type of the content
58+
pub mime_type: String,
59+
60+
/// When the payload was created
61+
pub created_at: DateTime<Utc>,
62+
63+
/// When the payload was last updated
64+
pub updated_at: DateTime<Utc>,
65+
66+
/// When the payload was last viewed (if ever)
67+
pub viewed_at: Option<DateTime<Utc>>,
68+
69+
/// When the payload will expire
70+
pub expiry_time: DateTime<Utc>,
71+
}
72+
73+
/// Response DTO for retrieving a payload.
74+
///
75+
/// This struct represents the JSON structure returned when retrieving
76+
/// an existing payload.
77+
#[derive(Debug, Serialize)]
78+
pub struct GetPayloadResponse {
79+
/// The unique identifier of the payload
80+
pub hash_id: String,
81+
82+
/// The stored content
83+
pub content: String,
84+
85+
/// The MIME type of the content
86+
pub mime_type: String,
87+
88+
/// When the payload was created
89+
pub created_at: DateTime<Utc>,
90+
91+
/// When the payload was last updated
92+
pub updated_at: DateTime<Utc>,
93+
94+
/// When the payload was last viewed (if ever)
95+
pub viewed_at: Option<DateTime<Utc>>,
96+
97+
/// When the payload will expire
98+
pub expiry_time: DateTime<Utc>,
99+
}
100+
101+
/// Error response DTO.
102+
///
103+
/// This struct represents the JSON structure returned when an error occurs.
104+
#[derive(Debug, Serialize)]
105+
pub struct ErrorResponse {
106+
/// The error message
107+
pub error: String,
108+
109+
/// Optional field for rate limit errors, indicating when to retry
110+
#[serde(skip_serializing_if = "Option::is_none")]
111+
pub retry_after: Option<i32>,
112+
}
113+
114+
/// Regular expression for validating MIME types
115+
static MIME_TYPE_REGEX: &str = r"^[a-z]+/[a-z0-9.+-]+$";
116+
117+
#[cfg(test)]
118+
mod tests {
119+
use super::*;
120+
use validator::Validate;
121+
122+
#[test]
123+
fn test_create_payload_request_validation() {
124+
// Valid request
125+
let valid_request = CreatePayloadRequest {
126+
content: "Test content".to_string(),
127+
mime_type: Some("text/plain".to_string()),
128+
expiry_time: None,
129+
};
130+
assert!(valid_request.validate().is_ok());
131+
132+
// Empty content
133+
let empty_content = CreatePayloadRequest {
134+
content: "".to_string(),
135+
mime_type: None,
136+
expiry_time: None,
137+
};
138+
assert!(empty_content.validate().is_err());
139+
140+
// Invalid MIME type
141+
let invalid_mime = CreatePayloadRequest {
142+
content: "Test content".to_string(),
143+
mime_type: Some("invalid-mime-type".to_string()),
144+
expiry_time: None,
145+
};
146+
assert!(invalid_mime.validate().is_err());
147+
}
148+
}

0 commit comments

Comments
 (0)