1use devboy_core::{
2 Comment, Discussion, FileDiff, ForestModifyResult, Issue, IssueRelations, IssueStatus,
3 JobLogOutput, KbPage, KbPageContent, KbSpace, MeetingNote, MeetingTranscript, MergeRequest,
4 MessengerChat, MessengerMessage, Pagination, PipelineInfo, ProjectVersion, SortInfo, Structure,
5 StructureForest, StructureValues, StructureView, User,
6};
7
8#[derive(Debug, Clone, Default)]
10pub struct ResultMeta {
11 pub pagination: Option<Pagination>,
12 pub sort_info: Option<SortInfo>,
13}
14
15#[derive(Debug)]
21pub enum ToolOutput {
22 MergeRequests(Vec<MergeRequest>, Option<ResultMeta>),
23 SingleMergeRequest(Box<MergeRequest>),
24 Discussions(Vec<Discussion>, Option<ResultMeta>),
26 Diffs(Vec<FileDiff>, Option<ResultMeta>),
27 Issues(Vec<Issue>, Option<ResultMeta>),
29 SingleIssue(Box<Issue>),
31 Comments(Vec<Comment>, Option<ResultMeta>),
32 Pipeline(Box<PipelineInfo>),
34 JobLog(Box<JobLogOutput>),
35 Statuses(Vec<IssueStatus>, Option<ResultMeta>),
36 Users(Vec<User>, Option<ResultMeta>),
37 MeetingNotes(Vec<MeetingNote>, Option<ResultMeta>),
38 MeetingTranscript(Box<MeetingTranscript>),
40 KnowledgeBaseSpaces(Vec<KbSpace>, Option<ResultMeta>),
41 KnowledgeBasePages(Vec<KbPage>, Option<ResultMeta>),
42 KnowledgeBasePageSummary(Box<KbPage>),
44 KnowledgeBasePage(Box<KbPageContent>),
46 Relations(Box<IssueRelations>),
48 MessengerChats(Vec<MessengerChat>, Option<ResultMeta>),
49 MessengerMessages(Vec<MessengerMessage>, Option<ResultMeta>),
50 SingleMessage(Box<MessengerMessage>),
52 AssetList {
53 attachments: Vec<serde_json::Value>,
55 count: usize,
57 capabilities: serde_json::Value,
59 },
60 AssetDownloaded {
62 asset_id: String,
64 size: usize,
65 local_path: Option<String>,
67 data: Option<String>,
69 cached: bool,
71 },
72 AssetUploaded {
73 url: String,
75 filename: String,
76 size: usize,
77 },
78 AssetDeleted {
79 asset_id: String,
81 message: String,
83 },
84 Structures(Vec<Structure>, Option<ResultMeta>),
85 ProjectVersions(Vec<ProjectVersion>, Option<ResultMeta>),
87 SingleProjectVersion(Box<ProjectVersion>),
89 StructureForest(Box<StructureForest>),
91 StructureValues(Box<StructureValues>),
93 StructureViews(Vec<StructureView>, Option<ResultMeta>),
94 ForestModified(ForestModifyResult),
96 Text(String),
98}
99
100impl ToolOutput {
101 pub fn item_count(&self) -> usize {
103 match self {
104 Self::MergeRequests(v, _) => v.len(),
105 Self::Discussions(v, _) => v.len(),
106 Self::Diffs(v, _) => v.len(),
107 Self::Issues(v, _) => v.len(),
108 Self::Comments(v, _) => v.len(),
109 Self::Statuses(v, _) => v.len(),
110 Self::Users(v, _) => v.len(),
111 Self::MeetingNotes(v, _) => v.len(),
112 Self::KnowledgeBaseSpaces(v, _) => v.len(),
113 Self::KnowledgeBasePages(v, _) => v.len(),
114 Self::MessengerChats(v, _) => v.len(),
115 Self::MessengerMessages(v, _) => v.len(),
116 Self::Structures(v, _) => v.len(),
117 Self::StructureViews(v, _) => v.len(),
118 Self::ProjectVersions(v, _) => v.len(),
119 Self::AssetList { count, .. } => *count,
120 Self::SingleMergeRequest(_)
121 | Self::SingleIssue(_)
122 | Self::Pipeline(_)
123 | Self::JobLog(_)
124 | Self::MeetingTranscript(_)
125 | Self::KnowledgeBasePageSummary(_)
126 | Self::KnowledgeBasePage(_)
127 | Self::Relations(_)
128 | Self::SingleMessage(_)
129 | Self::StructureForest(_)
130 | Self::StructureValues(_)
131 | Self::ForestModified(_)
132 | Self::SingleProjectVersion(_)
133 | Self::AssetDownloaded { .. }
134 | Self::AssetUploaded { .. }
135 | Self::AssetDeleted { .. }
136 | Self::Text(_) => 1,
137 }
138 }
139
140 pub fn type_name(&self) -> &'static str {
142 match self {
143 Self::MergeRequests(..) => "merge_requests",
144 Self::SingleMergeRequest(_) => "merge_request",
145 Self::Discussions(..) => "discussions",
146 Self::Diffs(..) => "diffs",
147 Self::Issues(..) => "issues",
148 Self::SingleIssue(_) => "issue",
149 Self::Comments(..) => "comments",
150 Self::Pipeline(_) => "pipeline",
151 Self::JobLog(_) => "job_log",
152 Self::Statuses(..) => "statuses",
153 Self::Users(..) => "users",
154 Self::MeetingNotes(..) => "meeting_notes",
155 Self::MeetingTranscript(_) => "meeting_transcript",
156 Self::KnowledgeBaseSpaces(..) => "knowledge_base_spaces",
157 Self::KnowledgeBasePages(..) => "knowledge_base_pages",
158 Self::KnowledgeBasePageSummary(_) => "knowledge_base_page_summary",
159 Self::KnowledgeBasePage(_) => "knowledge_base_page",
160 Self::Relations(_) => "issue_relations",
161 Self::MessengerChats(..) => "messenger_chats",
162 Self::MessengerMessages(..) => "messenger_messages",
163 Self::SingleMessage(_) => "messenger_message",
164 Self::Structures(..) => "structures",
165 Self::StructureForest(_) => "structure_forest",
166 Self::StructureValues(_) => "structure_values",
167 Self::StructureViews(..) => "structure_views",
168 Self::ForestModified(_) => "forest_modified",
169 Self::ProjectVersions(..) => "project_versions",
170 Self::SingleProjectVersion(_) => "project_version",
171 Self::AssetList { .. } => "asset_list",
172 Self::AssetDownloaded { .. } => "asset_downloaded",
173 Self::AssetUploaded { .. } => "asset_uploaded",
174 Self::AssetDeleted { .. } => "asset_deleted",
175 Self::Text(_) => "text",
176 }
177 }
178
179 pub fn result_meta(&self) -> Option<&ResultMeta> {
181 match self {
182 Self::MergeRequests(_, meta)
183 | Self::Discussions(_, meta)
184 | Self::Diffs(_, meta)
185 | Self::Issues(_, meta)
186 | Self::Comments(_, meta)
187 | Self::Statuses(_, meta)
188 | Self::Users(_, meta)
189 | Self::MeetingNotes(_, meta)
190 | Self::KnowledgeBaseSpaces(_, meta)
191 | Self::KnowledgeBasePages(_, meta)
192 | Self::MessengerChats(_, meta)
193 | Self::MessengerMessages(_, meta)
194 | Self::Structures(_, meta)
195 | Self::StructureViews(_, meta)
196 | Self::ProjectVersions(_, meta) => meta.as_ref(),
197 _ => None,
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use devboy_core::{Issue, IssueStatus, KbPage, KbPageContent, KbSpace, MergeRequest, User};
206
207 fn issue() -> Issue {
208 Issue {
209 key: "gh#1".into(),
210 title: "T".into(),
211 description: None,
212 state: "open".into(),
213 source: "mock".into(),
214 priority: None,
215 labels: vec![],
216 author: None,
217 assignees: vec![],
218 url: None,
219 created_at: None,
220 updated_at: None,
221 attachments_count: None,
222 parent: None,
223 subtasks: vec![],
224 }
225 }
226
227 fn mr() -> MergeRequest {
228 MergeRequest {
229 key: "pr#1".into(),
230 title: "T".into(),
231 description: None,
232 state: "open".into(),
233 source: "mock".into(),
234 source_branch: "f".into(),
235 target_branch: "m".into(),
236 author: None,
237 assignees: vec![],
238 reviewers: vec![],
239 labels: vec![],
240 draft: false,
241 url: None,
242 created_at: None,
243 updated_at: None,
244 }
245 }
246
247 fn kb_space() -> KbSpace {
248 KbSpace {
249 id: "space-1".into(),
250 key: "ENG".into(),
251 name: "Engineering".into(),
252 ..Default::default()
253 }
254 }
255
256 fn kb_page() -> KbPage {
257 KbPage {
258 id: "page-1".into(),
259 title: "Architecture".into(),
260 space_key: Some("ENG".into()),
261 ..Default::default()
262 }
263 }
264
265 fn kb_page_content() -> KbPageContent {
266 KbPageContent {
267 page: kb_page(),
268 content: "<p>hello</p>".into(),
269 content_type: "storage".into(),
270 ancestors: vec![],
271 labels: vec!["docs".into()],
272 }
273 }
274
275 #[test]
276 fn test_item_count_all_variants() {
277 assert_eq!(
278 ToolOutput::Issues(vec![issue(), issue()], None).item_count(),
279 2
280 );
281 assert_eq!(ToolOutput::MergeRequests(vec![], None).item_count(), 0);
282 assert_eq!(ToolOutput::SingleIssue(Box::new(issue())).item_count(), 1);
283 assert_eq!(
284 ToolOutput::SingleMergeRequest(Box::new(mr())).item_count(),
285 1
286 );
287 assert_eq!(ToolOutput::Discussions(vec![], None).item_count(), 0);
288 assert_eq!(ToolOutput::Diffs(vec![], None).item_count(), 0);
289 assert_eq!(ToolOutput::Comments(vec![], None).item_count(), 0);
290 assert_eq!(
291 ToolOutput::Pipeline(Box::new(devboy_core::PipelineInfo {
292 id: "1".into(),
293 status: devboy_core::PipelineStatus::Success,
294 reference: "main".into(),
295 sha: "abc".into(),
296 url: None,
297 duration: None,
298 coverage: None,
299 summary: devboy_core::PipelineSummary::default(),
300 stages: vec![],
301 failed_jobs: vec![],
302 }))
303 .item_count(),
304 1
305 );
306 assert_eq!(
307 ToolOutput::JobLog(Box::new(devboy_core::JobLogOutput {
308 job_id: "1".into(),
309 job_name: None,
310 content: "log".into(),
311 mode: "smart".into(),
312 total_lines: None,
313 }))
314 .item_count(),
315 1
316 );
317 assert_eq!(
318 ToolOutput::Statuses(
319 vec![IssueStatus {
320 id: "1".into(),
321 name: "Open".into(),
322 category: "open".into(),
323 color: None,
324 order: None,
325 }],
326 None
327 )
328 .item_count(),
329 1
330 );
331 assert_eq!(ToolOutput::Statuses(vec![], None).item_count(), 0);
332 assert_eq!(
333 ToolOutput::Users(
334 vec![User {
335 id: "1".into(),
336 username: "test".into(),
337 name: None,
338 email: None,
339 avatar_url: None,
340 }],
341 None
342 )
343 .item_count(),
344 1
345 );
346 assert_eq!(ToolOutput::Users(vec![], None).item_count(), 0);
347 assert_eq!(
348 ToolOutput::KnowledgeBaseSpaces(vec![kb_space()], None).item_count(),
349 1
350 );
351 assert_eq!(
352 ToolOutput::KnowledgeBasePages(vec![kb_page()], None).item_count(),
353 1
354 );
355 assert_eq!(
356 ToolOutput::KnowledgeBasePageSummary(Box::new(kb_page())).item_count(),
357 1
358 );
359 assert_eq!(
360 ToolOutput::KnowledgeBasePage(Box::new(kb_page_content())).item_count(),
361 1
362 );
363 assert_eq!(ToolOutput::Relations(Box::default()).item_count(), 1);
364 assert_eq!(ToolOutput::Structures(vec![], None).item_count(), 0);
365 assert_eq!(
366 ToolOutput::Structures(
367 vec![devboy_core::Structure {
368 id: 1,
369 name: "S".into(),
370 description: None
371 }],
372 None
373 )
374 .item_count(),
375 1
376 );
377 assert_eq!(
378 ToolOutput::StructureForest(Box::<devboy_core::StructureForest>::default())
379 .item_count(),
380 1
381 );
382 assert_eq!(
383 ToolOutput::StructureValues(Box::<devboy_core::StructureValues>::default())
384 .item_count(),
385 1
386 );
387 assert_eq!(ToolOutput::StructureViews(vec![], None).item_count(), 0);
388 assert_eq!(
389 ToolOutput::ForestModified(devboy_core::ForestModifyResult::default()).item_count(),
390 1
391 );
392 assert_eq!(ToolOutput::Text("x".into()).item_count(), 1);
393 }
394
395 #[test]
396 fn test_type_name_all_variants() {
397 assert_eq!(ToolOutput::Issues(vec![], None).type_name(), "issues");
398 assert_eq!(
399 ToolOutput::MergeRequests(vec![], None).type_name(),
400 "merge_requests"
401 );
402 assert_eq!(
403 ToolOutput::SingleIssue(Box::new(issue())).type_name(),
404 "issue"
405 );
406 assert_eq!(
407 ToolOutput::SingleMergeRequest(Box::new(mr())).type_name(),
408 "merge_request"
409 );
410 assert_eq!(
411 ToolOutput::Discussions(vec![], None).type_name(),
412 "discussions"
413 );
414 assert_eq!(ToolOutput::Diffs(vec![], None).type_name(), "diffs");
415 assert_eq!(ToolOutput::Comments(vec![], None).type_name(), "comments");
416 assert_eq!(
417 ToolOutput::Pipeline(Box::new(devboy_core::PipelineInfo {
418 id: "1".into(),
419 status: devboy_core::PipelineStatus::Success,
420 reference: "main".into(),
421 sha: "abc".into(),
422 url: None,
423 duration: None,
424 coverage: None,
425 summary: devboy_core::PipelineSummary::default(),
426 stages: vec![],
427 failed_jobs: vec![],
428 }))
429 .type_name(),
430 "pipeline"
431 );
432 assert_eq!(
433 ToolOutput::JobLog(Box::new(devboy_core::JobLogOutput {
434 job_id: "1".into(),
435 job_name: None,
436 content: "log".into(),
437 mode: "smart".into(),
438 total_lines: None,
439 }))
440 .type_name(),
441 "job_log"
442 );
443 assert_eq!(ToolOutput::Statuses(vec![], None).type_name(), "statuses");
444 assert_eq!(ToolOutput::Users(vec![], None).type_name(), "users");
445 assert_eq!(
446 ToolOutput::KnowledgeBaseSpaces(vec![], None).type_name(),
447 "knowledge_base_spaces"
448 );
449 assert_eq!(
450 ToolOutput::KnowledgeBasePages(vec![], None).type_name(),
451 "knowledge_base_pages"
452 );
453 assert_eq!(
454 ToolOutput::KnowledgeBasePageSummary(Box::new(kb_page())).type_name(),
455 "knowledge_base_page_summary"
456 );
457 assert_eq!(
458 ToolOutput::KnowledgeBasePage(Box::new(kb_page_content())).type_name(),
459 "knowledge_base_page"
460 );
461 assert_eq!(
462 ToolOutput::Relations(Box::default()).type_name(),
463 "issue_relations"
464 );
465 assert_eq!(ToolOutput::Text("x".into()).type_name(), "text");
466 assert_eq!(
467 ToolOutput::Structures(vec![], None).type_name(),
468 "structures"
469 );
470 assert_eq!(
471 ToolOutput::StructureForest(Box::<devboy_core::StructureForest>::default()).type_name(),
472 "structure_forest"
473 );
474 assert_eq!(
475 ToolOutput::StructureValues(Box::<devboy_core::StructureValues>::default()).type_name(),
476 "structure_values"
477 );
478 assert_eq!(
479 ToolOutput::StructureViews(vec![], None).type_name(),
480 "structure_views"
481 );
482 assert_eq!(
483 ToolOutput::ForestModified(devboy_core::ForestModifyResult::default()).type_name(),
484 "forest_modified"
485 );
486 }
487
488 #[test]
489 fn test_result_meta_present() {
490 let meta = ResultMeta {
491 pagination: Some(devboy_core::Pagination {
492 offset: 0,
493 limit: 10,
494 total: Some(50),
495 has_more: true,
496 next_cursor: None,
497 }),
498 sort_info: Some(devboy_core::SortInfo {
499 sort_by: Some("created_at".into()),
500 sort_order: devboy_core::SortOrder::Desc,
501 available_sorts: vec!["created_at".into()],
502 }),
503 };
504
505 let output = ToolOutput::Issues(vec![issue()], Some(meta));
506 let rm = output.result_meta().unwrap();
507 assert!(rm.pagination.is_some());
508 assert!(rm.sort_info.is_some());
509 assert_eq!(rm.pagination.as_ref().unwrap().total, Some(50));
510 }
511
512 #[test]
513 fn test_result_meta_none_for_single_variants() {
514 assert!(
515 ToolOutput::SingleIssue(Box::new(issue()))
516 .result_meta()
517 .is_none()
518 );
519 assert!(
520 ToolOutput::SingleMergeRequest(Box::new(mr()))
521 .result_meta()
522 .is_none()
523 );
524 assert!(ToolOutput::Text("hello".into()).result_meta().is_none());
525 assert!(
526 ToolOutput::Relations(Box::default())
527 .result_meta()
528 .is_none()
529 );
530 }
531
532 #[test]
533 fn test_result_meta_none_when_not_provided() {
534 assert!(ToolOutput::Issues(vec![], None).result_meta().is_none());
535 assert!(
536 ToolOutput::MergeRequests(vec![], None)
537 .result_meta()
538 .is_none()
539 );
540 assert!(
541 ToolOutput::Discussions(vec![], None)
542 .result_meta()
543 .is_none()
544 );
545 assert!(ToolOutput::Diffs(vec![], None).result_meta().is_none());
546 assert!(ToolOutput::Comments(vec![], None).result_meta().is_none());
547 assert!(ToolOutput::Statuses(vec![], None).result_meta().is_none());
548 assert!(ToolOutput::Users(vec![], None).result_meta().is_none());
549 assert!(
550 ToolOutput::MeetingNotes(vec![], None)
551 .result_meta()
552 .is_none()
553 );
554 assert!(
555 ToolOutput::KnowledgeBaseSpaces(vec![], None)
556 .result_meta()
557 .is_none()
558 );
559 assert!(
560 ToolOutput::KnowledgeBasePages(vec![], None)
561 .result_meta()
562 .is_none()
563 );
564 assert!(ToolOutput::Structures(vec![], None).result_meta().is_none());
565 assert!(
566 ToolOutput::StructureViews(vec![], None)
567 .result_meta()
568 .is_none()
569 );
570 assert!(
571 ToolOutput::StructureForest(Box::<devboy_core::StructureForest>::default())
572 .result_meta()
573 .is_none()
574 );
575 }
576}