use std::sync::Arc;
use openlark_core::error::api_error;
#[derive(Debug)]
pub struct UserFacesService {
config: Arc<crate::models::SecurityConfig>,
}
impl UserFacesService {
pub fn new(config: Arc<crate::models::SecurityConfig>) -> Self {
Self { config }
}
pub fn get(&self) -> GetUserFaceBuilder {
GetUserFaceBuilder {
config: self.config.clone(),
user_id: String::new(),
}
}
pub fn update(&self) -> UpdateUserFaceBuilder {
UpdateUserFaceBuilder {
config: self.config.clone(),
user_id: String::new(),
face_image: Vec::new(),
image_format: "jpeg".to_string(),
}
}
}
#[derive(Debug)]
pub struct GetUserFaceBuilder {
config: Arc<crate::models::SecurityConfig>,
user_id: String,
}
impl GetUserFaceBuilder {
pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
self.user_id = user_id.into();
self
}
pub async fn send(self) -> crate::SecurityResult<crate::models::acs::FaceImageInfo> {
let url = format!(
"{}/open-apis/acs/v1/users/{}/face",
self.config.base_url, self.user_id
);
let response = reqwest::Client::new()
.get(&url)
.header(
"Authorization",
format!("Bearer {}", get_app_token(&self.config).await?),
)
.header("Content-Type", "application/json")
.send()
.await?;
if response.status().is_success() {
let api_response: crate::models::ApiResponse<crate::models::acs::FaceImageInfo> =
response.json().await?;
match api_response.data {
Some(face_info) => Ok(face_info),
None => Err(api_error(
api_response.code as u16,
"/acs/v1/user_faces",
&api_response.msg,
None,
)),
}
} else {
Err(api_error(
response.status().as_u16(),
"/acs/v1/user_faces",
format!("HTTP: {}", response.status()),
None,
))
}
}
}
#[derive(Debug)]
pub struct UpdateUserFaceBuilder {
config: Arc<crate::models::SecurityConfig>,
user_id: String,
face_image: Vec<u8>,
image_format: String,
}
impl UpdateUserFaceBuilder {
pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
self.user_id = user_id.into();
self
}
pub fn face_image(mut self, face_image: Vec<u8>) -> Self {
self.face_image = face_image;
self
}
pub fn image_format(mut self, image_format: impl Into<String>) -> Self {
self.image_format = image_format.into();
self
}
pub async fn face_image_from_file(
mut self,
file_path: impl AsRef<std::path::Path>,
) -> crate::SecurityResult<Self> {
use std::fs;
let image_data = fs::read(file_path).map_err(|_e| {
openlark_core::error::network_error_with_details(
"Failed to read image file",
None,
Some("image_file_read".to_string()),
)
})?;
self.face_image = image_data;
Ok(self)
}
pub async fn send(self) -> crate::SecurityResult<crate::models::acs::FaceImageInfo> {
let url = format!(
"{}/open-apis/acs/v1/users/{}/face",
self.config.base_url, self.user_id
);
let form = reqwest::multipart::Form::new().part(
"image",
reqwest::multipart::Part::bytes(self.face_image)
.file_name(format!("face_image.{}", self.image_format))
.mime_str(&format!("image/{}", self.image_format))
.map_err(|e| {
openlark_core::error::validation_error(
"mime_type",
format!("Invalid mime type: {e}"),
)
})?,
);
let response = reqwest::Client::new()
.put(&url)
.header(
"Authorization",
format!("Bearer {}", get_app_token(&self.config).await?),
)
.multipart(form)
.send()
.await?;
if response.status().is_success() {
let api_response: crate::models::ApiResponse<crate::models::acs::FaceImageInfo> =
response.json().await?;
match api_response.data {
Some(face_info) => Ok(face_info),
None => Err(api_error(
api_response.code as u16,
"/acs/v1/user_faces",
&api_response.msg,
None,
)),
}
} else {
Err(api_error(
response.status().as_u16(),
"/acs/v1/user_faces",
format!("HTTP: {}", response.status()),
None,
))
}
}
}
async fn get_app_token(config: &crate::models::SecurityConfig) -> crate::SecurityResult<String> {
config.get_app_access_token().await
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn create_test_config() -> Arc<crate::models::SecurityConfig> {
Arc::new(crate::models::SecurityConfig {
app_id: "test_app_id".to_string(),
app_secret: "test_app_secret".to_string(),
base_url: "https://open.feishu.cn".to_string(),
})
}
#[test]
fn test_user_faces_service_creation() {
let config = create_test_config();
let service = UserFacesService::new(config.clone());
assert_eq!(service.config.app_id, "test_app_id");
}
#[test]
fn test_get_user_face_builder() {
let config = create_test_config();
let service = UserFacesService::new(config);
let builder = service.get().user_id("user_123");
assert_eq!(builder.user_id, "user_123");
}
#[test]
fn test_update_user_face_builder_defaults() {
let config = create_test_config();
let service = UserFacesService::new(config);
let builder = service.update();
assert_eq!(builder.user_id, String::new());
assert!(builder.face_image.is_empty());
assert_eq!(builder.image_format, "jpeg");
}
#[test]
fn test_update_user_face_builder_with_params() {
let config = create_test_config();
let service = UserFacesService::new(config);
let image_data = vec![0x01, 0x02, 0x03, 0x04];
let builder = service
.update()
.user_id("user_456")
.face_image(image_data.clone())
.image_format("png");
assert_eq!(builder.user_id, "user_456");
assert_eq!(builder.face_image, image_data);
assert_eq!(builder.image_format, "png");
}
#[test]
fn test_update_user_face_builder_chaining() {
let config = create_test_config();
let service = UserFacesService::new(config);
let builder = service.update().user_id("user_789").image_format("jpeg");
assert_eq!(builder.user_id, "user_789");
assert_eq!(builder.image_format, "jpeg");
assert!(builder.face_image.is_empty()); }
#[test]
fn test_update_user_face_image_format_variants() {
let config = create_test_config();
let service = UserFacesService::new(config);
let builder_jpeg = service.update().image_format("jpeg");
assert_eq!(builder_jpeg.image_format, "jpeg");
let builder_png = service.update().image_format("png");
assert_eq!(builder_png.image_format, "png");
let builder_webp = service.update().image_format("webp");
assert_eq!(builder_webp.image_format, "webp");
}
}