modelrelay 6.5.0

Rust SDK for the ModelRelay API
Documentation
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperPage {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cursor: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperSearchRequest {
    pub query: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filters: Option<HashMap<String, serde_json::Value>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub page: Option<WrapperPage>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperItem {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub item_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub snippet: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperSearchResponse {
    pub items: Vec<WrapperItem>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_cursor: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperGetRequest {
    pub id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperGetResponse {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub item_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub size_bytes: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperContentRequest {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_bytes: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperContentResponse {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,
    pub content: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub truncated: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperErrorBody {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub retry_after_ms: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WrapperErrorResponse {
    pub error: WrapperErrorBody,
}

pub fn validate_search_response(resp: &WrapperSearchResponse) -> Result<(), String> {
    for (idx, item) in resp.items.iter().enumerate() {
        if item.id.trim().is_empty() {
            return Err(format!("items[{}].id is required", idx));
        }
    }
    Ok(())
}

pub fn validate_get_response(resp: &WrapperGetResponse) -> Result<(), String> {
    if resp.id.trim().is_empty() {
        return Err("id is required".to_string());
    }
    Ok(())
}

pub fn validate_content_response(resp: &WrapperContentResponse) -> Result<(), String> {
    if resp.id.trim().is_empty() {
        return Err("id is required".to_string());
    }
    Ok(())
}

pub fn validate_error_response(resp: &WrapperErrorResponse) -> Result<(), String> {
    if resp.error.code.trim().is_empty() {
        return Err("error.code is required".to_string());
    }
    if resp.error.message.trim().is_empty() {
        return Err("error.message is required".to_string());
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validate_search_requires_ids() {
        let resp = WrapperSearchResponse {
            items: vec![WrapperItem {
                id: "".into(),
                title: None,
                item_type: None,
                snippet: None,
                updated_at: None,
                source_url: None,
                metadata: None,
            }],
            next_cursor: None,
        };
        assert!(validate_search_response(&resp).is_err());
    }

    #[test]
    fn validate_get_requires_id() {
        let resp = WrapperGetResponse {
            id: "".into(),
            title: None,
            item_type: None,
            updated_at: None,
            size_bytes: None,
            mime_type: None,
            metadata: None,
        };
        assert!(validate_get_response(&resp).is_err());
    }

    #[test]
    fn validate_error_requires_fields() {
        let resp = WrapperErrorResponse {
            error: WrapperErrorBody {
                code: "".into(),
                message: "".into(),
                retry_after_ms: None,
            },
        };
        assert!(validate_error_response(&resp).is_err());
    }
}