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 ..Default::default()
237 }
238 }
239
240 fn mr() -> MergeRequest {
241 MergeRequest {
242 key: "pr#1".into(),
243 title: "T".into(),
244 description: None,
245 state: "open".into(),
246 source: "mock".into(),
247 source_branch: "f".into(),
248 target_branch: "m".into(),
249 author: None,
250 assignees: vec![],
251 reviewers: vec![],
252 labels: vec![],
253 draft: false,
254 url: None,
255 created_at: None,
256 updated_at: None,
257 }
258 }
259
260 fn kb_space() -> KbSpace {
261 KbSpace {
262 id: "space-1".into(),
263 key: "ENG".into(),
264 name: "Engineering".into(),
265 ..Default::default()
266 }
267 }
268
269 fn kb_page() -> KbPage {
270 KbPage {
271 id: "page-1".into(),
272 title: "Architecture".into(),
273 space_key: Some("ENG".into()),
274 ..Default::default()
275 }
276 }
277
278 fn kb_page_content() -> KbPageContent {
279 KbPageContent {
280 page: kb_page(),
281 content: "<p>hello</p>".into(),
282 content_type: "storage".into(),
283 ancestors: vec![],
284 labels: vec!["docs".into()],
285 }
286 }
287
288 #[test]
289 fn test_item_count_all_variants() {
290 assert_eq!(
291 ToolOutput::Issues(vec![issue(), issue()], None).item_count(),
292 2
293 );
294 assert_eq!(ToolOutput::MergeRequests(vec![], None).item_count(), 0);
295 assert_eq!(ToolOutput::SingleIssue(Box::new(issue())).item_count(), 1);
296 assert_eq!(
297 ToolOutput::SingleMergeRequest(Box::new(mr())).item_count(),
298 1
299 );
300 assert_eq!(ToolOutput::Discussions(vec![], None).item_count(), 0);
301 assert_eq!(ToolOutput::Diffs(vec![], None).item_count(), 0);
302 assert_eq!(ToolOutput::Comments(vec![], None).item_count(), 0);
303 assert_eq!(
304 ToolOutput::Pipeline(Box::new(devboy_core::PipelineInfo {
305 id: "1".into(),
306 status: devboy_core::PipelineStatus::Success,
307 reference: "main".into(),
308 sha: "abc".into(),
309 url: None,
310 duration: None,
311 coverage: None,
312 summary: devboy_core::PipelineSummary::default(),
313 stages: vec![],
314 failed_jobs: vec![],
315 }))
316 .item_count(),
317 1
318 );
319 assert_eq!(
320 ToolOutput::JobLog(Box::new(devboy_core::JobLogOutput {
321 job_id: "1".into(),
322 job_name: None,
323 content: "log".into(),
324 mode: "smart".into(),
325 total_lines: None,
326 }))
327 .item_count(),
328 1
329 );
330 assert_eq!(
331 ToolOutput::Statuses(
332 vec![IssueStatus {
333 id: "1".into(),
334 name: "Open".into(),
335 category: "open".into(),
336 color: None,
337 order: None,
338 }],
339 None
340 )
341 .item_count(),
342 1
343 );
344 assert_eq!(ToolOutput::Statuses(vec![], None).item_count(), 0);
345 assert_eq!(
346 ToolOutput::Users(
347 vec![User {
348 id: "1".into(),
349 username: "test".into(),
350 name: None,
351 email: None,
352 avatar_url: None,
353 }],
354 None
355 )
356 .item_count(),
357 1
358 );
359 assert_eq!(ToolOutput::Users(vec![], None).item_count(), 0);
360 assert_eq!(
361 ToolOutput::KnowledgeBaseSpaces(vec![kb_space()], None).item_count(),
362 1
363 );
364 assert_eq!(
365 ToolOutput::KnowledgeBasePages(vec![kb_page()], None).item_count(),
366 1
367 );
368 assert_eq!(
369 ToolOutput::KnowledgeBasePageSummary(Box::new(kb_page())).item_count(),
370 1
371 );
372 assert_eq!(
373 ToolOutput::KnowledgeBasePage(Box::new(kb_page_content())).item_count(),
374 1
375 );
376 assert_eq!(ToolOutput::Relations(Box::default()).item_count(), 1);
377 assert_eq!(ToolOutput::Structures(vec![], None).item_count(), 0);
378 assert_eq!(
379 ToolOutput::Structures(
380 vec![devboy_core::Structure {
381 id: 1,
382 name: "S".into(),
383 description: None
384 }],
385 None
386 )
387 .item_count(),
388 1
389 );
390 assert_eq!(
391 ToolOutput::StructureForest(Box::<devboy_core::StructureForest>::default())
392 .item_count(),
393 1
394 );
395 assert_eq!(
396 ToolOutput::StructureValues(Box::<devboy_core::StructureValues>::default())
397 .item_count(),
398 1
399 );
400 assert_eq!(ToolOutput::StructureViews(vec![], None).item_count(), 0);
401 assert_eq!(
402 ToolOutput::ForestModified(devboy_core::ForestModifyResult::default()).item_count(),
403 1
404 );
405 assert_eq!(ToolOutput::Text("x".into()).item_count(), 1);
406 }
407
408 #[test]
409 fn test_type_name_all_variants() {
410 assert_eq!(ToolOutput::Issues(vec![], None).type_name(), "issues");
411 assert_eq!(
412 ToolOutput::MergeRequests(vec![], None).type_name(),
413 "merge_requests"
414 );
415 assert_eq!(
416 ToolOutput::SingleIssue(Box::new(issue())).type_name(),
417 "issue"
418 );
419 assert_eq!(
420 ToolOutput::SingleMergeRequest(Box::new(mr())).type_name(),
421 "merge_request"
422 );
423 assert_eq!(
424 ToolOutput::Discussions(vec![], None).type_name(),
425 "discussions"
426 );
427 assert_eq!(ToolOutput::Diffs(vec![], None).type_name(), "diffs");
428 assert_eq!(ToolOutput::Comments(vec![], None).type_name(), "comments");
429 assert_eq!(
430 ToolOutput::Pipeline(Box::new(devboy_core::PipelineInfo {
431 id: "1".into(),
432 status: devboy_core::PipelineStatus::Success,
433 reference: "main".into(),
434 sha: "abc".into(),
435 url: None,
436 duration: None,
437 coverage: None,
438 summary: devboy_core::PipelineSummary::default(),
439 stages: vec![],
440 failed_jobs: vec![],
441 }))
442 .type_name(),
443 "pipeline"
444 );
445 assert_eq!(
446 ToolOutput::JobLog(Box::new(devboy_core::JobLogOutput {
447 job_id: "1".into(),
448 job_name: None,
449 content: "log".into(),
450 mode: "smart".into(),
451 total_lines: None,
452 }))
453 .type_name(),
454 "job_log"
455 );
456 assert_eq!(ToolOutput::Statuses(vec![], None).type_name(), "statuses");
457 assert_eq!(ToolOutput::Users(vec![], None).type_name(), "users");
458 assert_eq!(
459 ToolOutput::KnowledgeBaseSpaces(vec![], None).type_name(),
460 "knowledge_base_spaces"
461 );
462 assert_eq!(
463 ToolOutput::KnowledgeBasePages(vec![], None).type_name(),
464 "knowledge_base_pages"
465 );
466 assert_eq!(
467 ToolOutput::KnowledgeBasePageSummary(Box::new(kb_page())).type_name(),
468 "knowledge_base_page_summary"
469 );
470 assert_eq!(
471 ToolOutput::KnowledgeBasePage(Box::new(kb_page_content())).type_name(),
472 "knowledge_base_page"
473 );
474 assert_eq!(
475 ToolOutput::Relations(Box::default()).type_name(),
476 "issue_relations"
477 );
478 assert_eq!(ToolOutput::Text("x".into()).type_name(), "text");
479 assert_eq!(
480 ToolOutput::Structures(vec![], None).type_name(),
481 "structures"
482 );
483 assert_eq!(
484 ToolOutput::StructureForest(Box::<devboy_core::StructureForest>::default()).type_name(),
485 "structure_forest"
486 );
487 assert_eq!(
488 ToolOutput::StructureValues(Box::<devboy_core::StructureValues>::default()).type_name(),
489 "structure_values"
490 );
491 assert_eq!(
492 ToolOutput::StructureViews(vec![], None).type_name(),
493 "structure_views"
494 );
495 assert_eq!(
496 ToolOutput::ForestModified(devboy_core::ForestModifyResult::default()).type_name(),
497 "forest_modified"
498 );
499 }
500
501 #[test]
502 fn test_result_meta_present() {
503 let meta = ResultMeta {
504 pagination: Some(devboy_core::Pagination {
505 offset: 0,
506 limit: 10,
507 total: Some(50),
508 has_more: true,
509 next_cursor: None,
510 }),
511 sort_info: Some(devboy_core::SortInfo {
512 sort_by: Some("created_at".into()),
513 sort_order: devboy_core::SortOrder::Desc,
514 available_sorts: vec!["created_at".into()],
515 }),
516 };
517
518 let output = ToolOutput::Issues(vec![issue()], Some(meta));
519 let rm = output.result_meta().unwrap();
520 assert!(rm.pagination.is_some());
521 assert!(rm.sort_info.is_some());
522 assert_eq!(rm.pagination.as_ref().unwrap().total, Some(50));
523 }
524
525 #[test]
526 fn test_result_meta_none_for_single_variants() {
527 assert!(
528 ToolOutput::SingleIssue(Box::new(issue()))
529 .result_meta()
530 .is_none()
531 );
532 assert!(
533 ToolOutput::SingleMergeRequest(Box::new(mr()))
534 .result_meta()
535 .is_none()
536 );
537 assert!(ToolOutput::Text("hello".into()).result_meta().is_none());
538 assert!(
539 ToolOutput::Relations(Box::default())
540 .result_meta()
541 .is_none()
542 );
543 }
544
545 #[test]
546 fn test_result_meta_none_when_not_provided() {
547 assert!(ToolOutput::Issues(vec![], None).result_meta().is_none());
548 assert!(
549 ToolOutput::MergeRequests(vec![], None)
550 .result_meta()
551 .is_none()
552 );
553 assert!(
554 ToolOutput::Discussions(vec![], None)
555 .result_meta()
556 .is_none()
557 );
558 assert!(ToolOutput::Diffs(vec![], None).result_meta().is_none());
559 assert!(ToolOutput::Comments(vec![], None).result_meta().is_none());
560 assert!(ToolOutput::Statuses(vec![], None).result_meta().is_none());
561 assert!(ToolOutput::Users(vec![], None).result_meta().is_none());
562 assert!(
563 ToolOutput::MeetingNotes(vec![], None)
564 .result_meta()
565 .is_none()
566 );
567 assert!(
568 ToolOutput::KnowledgeBaseSpaces(vec![], None)
569 .result_meta()
570 .is_none()
571 );
572 assert!(
573 ToolOutput::KnowledgeBasePages(vec![], None)
574 .result_meta()
575 .is_none()
576 );
577 assert!(ToolOutput::Structures(vec![], None).result_meta().is_none());
578 assert!(
579 ToolOutput::StructureViews(vec![], None)
580 .result_meta()
581 .is_none()
582 );
583 assert!(
584 ToolOutput::StructureForest(Box::<devboy_core::StructureForest>::default())
585 .result_meta()
586 .is_none()
587 );
588 }
589}