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