slack_morphism/api/
blocks.rs1use rsb_derive::Builder;
6use serde::{Deserialize, Serialize};
7use serde_with::{serde_as, skip_serializing_none};
8
9use crate::errors::{map_serde_error, SlackClientError};
10use crate::models::blocks::*;
11use crate::models::*;
12use crate::SlackClientSession;
13use crate::{ClientResult, SlackClientHttpConnector};
14
15const BLOCKS_VALIDATE_FAILURE_CODES: [&str; 3] =
16 ["invalid_blocks", "invalid_message", "invalid_view"];
17
18impl<'a, SCHC> SlackClientSession<'a, SCHC>
19where
20 SCHC: SlackClientHttpConnector + Send,
21{
22 pub async fn blocks_validate(
38 &self,
39 req: &SlackApiBlocksValidateRequest,
40 ) -> ClientResult<SlackApiBlocksValidateResponse> {
41 match self
42 .http_session_api
43 .http_post("blocks.validate", req, None)
44 .await
45 {
46 Err(SlackClientError::ApiError(api_error))
47 if BLOCKS_VALIDATE_FAILURE_CODES.contains(&api_error.code.as_str()) =>
48 {
49 match api_error.http_response_body.as_deref() {
50 Some(body) => {
51 serde_json::from_str(body).map_err(|e| map_serde_error(e, Some(body)))
52 }
53 None => Err(SlackClientError::ApiError(api_error)),
54 }
55 }
56 other => other,
57 }
58 }
59}
60
61#[serde_as]
65#[skip_serializing_none]
66#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
67pub struct SlackApiBlocksValidateRequest {
68 #[serde_as(as = "Option<serde_with::json::JsonString>")]
69 pub blocks: Option<Vec<SlackBlock>>,
70 #[serde_as(as = "Option<serde_with::json::JsonString>")]
71 pub message: Option<SlackMessageContent>,
72 #[serde_as(as = "Option<serde_with::json::JsonString>")]
73 pub view: Option<SlackView>,
74}
75
76#[skip_serializing_none]
77#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
78pub struct SlackApiBlocksValidateResponse {
79 pub ok: bool,
80 pub error: Option<SlackApiBlocksValidateErrorKind>,
82 pub errors: Option<Vec<SlackApiBlocksValidateError>>,
83}
84
85#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum SlackApiBlocksValidateErrorKind {
90 InvalidBlocks,
91 InvalidMessage,
92 InvalidView,
93 #[serde(untagged)]
95 Other(String),
96}
97
98#[skip_serializing_none]
99#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
100pub struct SlackApiBlocksValidateError {
101 pub pointer: String,
103 pub code: SlackApiBlocksValidateErrorCode,
104 pub message: String,
105 pub constraint: Option<serde_json::Value>,
107 pub field: Option<String>,
108}
109
110#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum SlackApiBlocksValidateErrorCode {
116 FailedConstraint,
117 MissingField,
118 #[serde(untagged)]
120 Other(String),
121}
122
123#[cfg(test)]
124mod test {
125 use super::*;
126
127 #[test]
128 fn test_slack_api_blocks_validate_request_encodes_payload_as_json_string() {
129 let req =
130 SlackApiBlocksValidateRequest::new().with_blocks(vec![SlackDividerBlock::new().into()]);
131 let json = serde_json::to_value(&req).unwrap();
132 assert_eq!(
133 json,
134 serde_json::json!({ "blocks": "[{\"type\":\"divider\"}]" })
135 );
136 let decoded: SlackApiBlocksValidateRequest = serde_json::from_value(json).unwrap();
137 assert_eq!(decoded, req);
138 }
139
140 #[test]
141 fn test_slack_api_blocks_validate_error_response_round_trip() {
142 let payload = r#"{"ok":false,"error":"invalid_message","errors":[{"code":"failed_constraint","constraint":{"type":"enum","expected":["divider","image","section"],"got":"markdown"},"message":"unsupported type: markdown","pointer":"/blocks/0/child_blocks/0/type"}]}"#;
143
144 let envelope: crate::SlackEnvelopeMessage = serde_json::from_str(payload).unwrap();
145 assert_eq!(envelope.error.as_deref(), Some("invalid_message"));
146 assert_eq!(
147 envelope.errors.as_ref().unwrap()[0]["code"],
148 "failed_constraint"
149 );
150 assert!(envelope.errors_as_strings().unwrap()[0].contains("\"failed_constraint\""));
151
152 let response: SlackApiBlocksValidateResponse = serde_json::from_str(payload).unwrap();
153 assert!(!response.ok);
154 assert_eq!(
155 response.error,
156 Some(SlackApiBlocksValidateErrorKind::InvalidMessage)
157 );
158 let errors = response.errors.as_ref().unwrap();
159 assert_eq!(errors[0].pointer, "/blocks/0/child_blocks/0/type");
160 assert_eq!(
161 errors[0].code,
162 SlackApiBlocksValidateErrorCode::FailedConstraint
163 );
164 assert_eq!(errors[0].constraint.as_ref().unwrap()["got"], "markdown");
165 assert_eq!(errors[0].field, None);
166 assert_eq!(
167 serde_json::to_value(&response).unwrap(),
168 serde_json::from_str::<serde_json::Value>(payload).unwrap()
169 );
170
171 let ok: SlackApiBlocksValidateResponse = serde_json::from_str(r#"{"ok":true}"#).unwrap();
172 assert!(ok.ok && ok.errors.is_none());
173
174 let other_code: SlackApiBlocksValidateErrorCode =
175 serde_json::from_str(r#""something_new""#).unwrap();
176 assert_eq!(
177 other_code,
178 SlackApiBlocksValidateErrorCode::Other("something_new".into())
179 );
180 assert_eq!(
181 serde_json::to_string(&other_code).unwrap(),
182 r#""something_new""#
183 );
184
185 let other: SlackApiBlocksValidateErrorKind =
186 serde_json::from_str(r#""something_new""#).unwrap();
187 assert_eq!(
188 other,
189 SlackApiBlocksValidateErrorKind::Other("something_new".into())
190 );
191 assert_eq!(serde_json::to_string(&other).unwrap(), r#""something_new""#);
192 }
193}