Skip to content

Commit 57bfda5

Browse files
구조적 변경: 스토리지 서비스 추가 및 관련 파일 업데이트
- 새로운 스토리지 서비스 모듈 추가: 파일 업로드 기능을 포함한 `StorageService` 구조체 구현. - 파일 업로드 요청 및 응답을 위한 타입 정의: `UploadFileRequest` 및 `UploadFileResponse` 구조체 추가. - `SolapiClient`에 스토리지 서비스 인스턴스 반환 메서드 추가: `storage_service` 메서드 구현. - 관련 테스트 케이스 추가: 스토리지 서비스 생성 및 업로드 메서드 테스트. - `Cargo.toml` 파일에 스토리지 서비스 관련 의존성 추가 및 라이센스 정보 업데이트. - `.gitignore` 파일에 새로운 항목 추가: `.DS_Store` 및 `.env.local` 무시 설정.
1 parent 110724d commit 57bfda5

8 files changed

Lines changed: 395 additions & 8 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@
1010
# Dummy file
1111
examples/dummy.rs
1212
.env
13-
.env.local
13+
.env.local
14+
15+
.DS_Store

Cargo.lock

Lines changed: 14 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
name = "solapi"
33
version = "0.1.0"
44
edition = "2021"
5+
license = "MIT"
6+
repository = "https://github.com/solapi/solapi-rust"
7+
description = "Official SOLAPI Rust SDK for messaging and storage APIs. Provides a typed async client to send SMS/LMS/MMS, KakaoTalk (AlimTalk/FriendTalk), schedule messages, and manage file storage."
8+
keywords = ["solapi", "sms", "lms", "mms", "kakaotalk", "alimtalk", "messaging", "sdk"]
9+
categories = ["api-bindings", "web-programming::http-client", "asynchronous", "network-programming"]
510

611
[dependencies]
712
hmac = "0.12"
@@ -18,3 +23,4 @@ encoding_rs = "0.8"
1823

1924
[dev-dependencies]
2025
wiremock = "0.6"
26+
base64 = "0.21"

LICENSE

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
MIT License
2+
3+
Copyright (c) 2025~ SOLAPI Inc (https://solapi.com)
4+
5+
Permission is hereby granted, free of charge, to any person
6+
obtaining a copy of this software and associated documentation
7+
files (the "Software"), to deal in the Software without
8+
restriction, including without limitation the rights to use,
9+
copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the
11+
Software is furnished to do so, subject to the following
12+
conditions:
13+
14+
The above copyright notice and this permission notice shall be
15+
included in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
OTHER DEALINGS IN THE SOFTWARE.

src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod services;
44

55
// Public exports
66
pub use services::message;
7+
pub use services::storage;
78

89
/// SOLAPI 클라이언트
910
///
@@ -43,6 +44,20 @@ impl SolapiClient {
4344
pub fn message_service(&self) -> services::message::MessageService {
4445
services::message::MessageService::new(&self.api_key, &self.api_secret)
4546
}
47+
48+
/// 스토리지 서비스 인스턴스를 반환합니다.
49+
///
50+
/// # Examples
51+
///
52+
/// ```
53+
/// use solapi::SolapiClient;
54+
///
55+
/// let client = SolapiClient::new("your_api_key", "your_api_secret");
56+
/// let storage_service = client.storage_service();
57+
/// ```
58+
pub fn storage_service(&self) -> services::storage::StorageService {
59+
services::storage::StorageService::new(&self.api_key, &self.api_secret)
60+
}
4661
}
4762

4863
#[cfg(test)]
@@ -64,6 +79,13 @@ mod tests {
6479
let _message_service = client.message_service();
6580
}
6681

82+
#[test]
83+
fn should_return_storage_service() {
84+
let client = SolapiClient::new("test_key", "test_secret");
85+
86+
let _storage_service = client.storage_service();
87+
}
88+
6789
#[test]
6890
fn should_export_voice_option_and_voice_type() {
6991
use message::{VoiceOption, VoiceType};
@@ -98,4 +120,19 @@ mod tests {
98120
assert!(message.voice_options.is_some());
99121
assert_eq!(message.voice_options.unwrap().voice_type, VoiceType::Female);
100122
}
123+
124+
#[test]
125+
fn should_export_storage_types() {
126+
use storage::{FileType, UploadFileRequest};
127+
128+
let request = UploadFileRequest {
129+
file: "base64string".to_string(),
130+
name: Some("test.jpg".to_string()),
131+
file_type: Some(FileType::Mms),
132+
link: None,
133+
};
134+
135+
assert_eq!(request.file, "base64string");
136+
assert_eq!(request.name, Some("test.jpg".to_string()));
137+
}
101138
}

src/services/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod message;
2+
pub mod storage;

src/services/storage/mod.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
mod types;
2+
3+
pub use types::*;
4+
5+
use crate::http::HttpClient;
6+
7+
/// 스토리지 서비스
8+
#[derive(Clone)]
9+
pub struct StorageService {
10+
client: HttpClient,
11+
}
12+
13+
impl StorageService {
14+
pub(crate) fn new(api_key: &str, api_secret: &str) -> Self {
15+
Self {
16+
client: HttpClient::new(api_key, api_secret),
17+
}
18+
}
19+
20+
/// 파일 업로드 (비동기)
21+
///
22+
/// # Examples
23+
///
24+
/// ```no_run
25+
/// use solapi::SolapiClient;
26+
/// use solapi::storage::{UploadFileRequest, FileType};
27+
///
28+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
29+
/// let client = SolapiClient::new("api_key", "api_secret");
30+
/// let storage = client.storage_service();
31+
///
32+
/// let request = UploadFileRequest {
33+
/// file: "base64_encoded_content".to_string(),
34+
/// name: Some("test.jpg".to_string()),
35+
/// file_type: Some(FileType::Mms),
36+
/// link: None,
37+
/// };
38+
///
39+
/// let response = storage.upload(request).await?;
40+
/// println!("File uploaded: {}", response.url);
41+
/// # Ok(())
42+
/// # }
43+
/// ```
44+
pub async fn upload(
45+
&self,
46+
request: UploadFileRequest,
47+
) -> Result<UploadFileResponse, crate::http::HttpError> {
48+
self.client.post("/storage/v1/files", &request).await
49+
}
50+
51+
/// 파일 업로드 (동기)
52+
///
53+
/// 내부적으로 tokio runtime을 생성하여 비동기 upload 메서드를 호출합니다.
54+
///
55+
/// # Examples
56+
///
57+
/// ```no_run
58+
/// use solapi::SolapiClient;
59+
/// use solapi::storage::{UploadFileRequest, FileType};
60+
///
61+
/// let client = SolapiClient::new("api_key", "api_secret");
62+
/// let storage = client.storage_service();
63+
///
64+
/// let request = UploadFileRequest {
65+
/// file: "base64_encoded_content".to_string(),
66+
/// name: Some("test.jpg".to_string()),
67+
/// file_type: Some(FileType::Mms),
68+
/// link: None,
69+
/// };
70+
///
71+
/// let response = storage.upload_blocking(request)?;
72+
/// println!("File uploaded: {}", response.url);
73+
/// # Ok::<(), Box<dyn std::error::Error>>(())
74+
/// ```
75+
pub fn upload_blocking(
76+
&self,
77+
request: UploadFileRequest,
78+
) -> Result<UploadFileResponse, crate::http::HttpError> {
79+
tokio::runtime::Runtime::new()
80+
.map_err(|e| crate::http::HttpError::ValidationError {
81+
message: format!("Failed to create tokio runtime: {}", e),
82+
})?
83+
.block_on(self.upload(request))
84+
}
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use super::*;
90+
91+
#[test]
92+
fn should_create_storage_service() {
93+
let _service = StorageService::new("test_key", "test_secret");
94+
}
95+
96+
#[tokio::test]
97+
async fn should_have_upload_method() {
98+
let service = StorageService::new("test_key", "test_secret");
99+
let request = UploadFileRequest {
100+
file: "base64string".to_string(),
101+
name: Some("test.jpg".to_string()),
102+
file_type: Some(FileType::Mms),
103+
link: None,
104+
};
105+
106+
let _ = service.upload(request).await;
107+
}
108+
109+
#[test]
110+
fn should_have_upload_blocking_method() {
111+
let service = StorageService::new("test_key", "test_secret");
112+
let request = UploadFileRequest {
113+
file: "base64string".to_string(),
114+
name: Some("test.jpg".to_string()),
115+
file_type: Some(FileType::Mms),
116+
link: None,
117+
};
118+
119+
let _ = service.upload_blocking(request);
120+
}
121+
}

0 commit comments

Comments
 (0)