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