Skip to content

Commit 917c120

Browse files
committed
refactor: split feedback input loading helpers
Separate feedback input format detection from typed loading so new input shapes can be added without reworking the main loader. Made-with: Cursor
1 parent ba38e50 commit 917c120

4 files changed

Lines changed: 169 additions & 70 deletions

File tree

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979

8080
- [ ] `src/commands/eval/types.rs`: split fixture, pattern, report, and run-option types if churn keeps touching unrelated structs.
8181
- [ ] `src/commands/feedback_eval/types.rs`: separate input payload types from report/output types.
82-
- [ ] `src/commands/feedback_eval/input/loading.rs`: split format detection from JSON parsing/loading.
82+
- [x] `src/commands/feedback_eval/input/loading.rs`: split format detection from JSON parsing/loading.
8383
- [ ] `src/commands/feedback_eval/input/conversion.rs`: split review-session conversion from label normalization helpers.
8484
- [ ] `src/commands/pr.rs`: separate summary-only flow, full review flow, and comment-posting orchestration.
8585
- [ ] `src/commands/pr/gh.rs`: carve PR resolution, diff fetching, and metadata fetching.
Lines changed: 19 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
#[path = "loading/format.rs"]
2+
mod format;
3+
#[path = "loading/parse.rs"]
4+
mod parse;
5+
16
use anyhow::Result;
2-
use std::collections::HashMap;
37
use std::path::Path;
48

5-
use crate::core;
6-
use crate::server::state::ReviewSession;
7-
8-
use super::super::{FeedbackEvalComment, LoadedFeedbackEvalInput};
9-
use super::conversion::{extend_from_review_session, feedback_comment_from_comment};
9+
use super::super::LoadedFeedbackEvalInput;
10+
use format::detect_feedback_eval_input_format;
11+
use parse::load_feedback_eval_input_from_value;
1012

1113
pub(in super::super) async fn load_feedback_eval_input(
1214
path: &Path,
@@ -18,67 +20,15 @@ pub(in super::super) async fn load_feedback_eval_input(
1820
pub(in super::super) fn load_feedback_eval_input_from_str(
1921
content: &str,
2022
) -> Result<LoadedFeedbackEvalInput> {
21-
if let Ok(review_map) = serde_json::from_str::<HashMap<String, ReviewSession>>(content) {
22-
let mut loaded = LoadedFeedbackEvalInput::default();
23-
for (review_id, session) in review_map {
24-
extend_from_review_session(&mut loaded, Some(review_id), session);
25-
}
26-
return Ok(loaded);
27-
}
28-
29-
if let Ok(review_list) = serde_json::from_str::<Vec<ReviewSession>>(content) {
30-
let mut loaded = LoadedFeedbackEvalInput::default();
31-
for session in review_list {
32-
let review_id = session.id.clone();
33-
extend_from_review_session(&mut loaded, Some(review_id), session);
34-
}
35-
return Ok(loaded);
36-
}
37-
38-
if let Ok(store) = serde_json::from_str::<core::SemanticFeedbackStore>(content) {
39-
let total_comments_seen = store.examples.len();
40-
let comments = store
41-
.examples
42-
.into_iter()
43-
.map(|example| FeedbackEvalComment {
44-
source_kind: "semantic-feedback".to_string(),
45-
review_id: None,
46-
repo: None,
47-
pr_number: None,
48-
title: None,
49-
file_path: None,
50-
line_number: None,
51-
file_patterns: example.file_patterns,
52-
content: example.content,
53-
category: example.category,
54-
severity: None,
55-
confidence: None,
56-
accepted: example.accepted,
57-
})
58-
.collect();
59-
return Ok(LoadedFeedbackEvalInput {
60-
total_comments_seen,
61-
total_reviews_seen: 0,
62-
comments,
63-
});
64-
}
65-
66-
if let Ok(comments) = serde_json::from_str::<Vec<core::Comment>>(content) {
67-
let total_comments_seen = comments.len();
68-
let comments = comments
69-
.into_iter()
70-
.filter_map(|comment| {
71-
feedback_comment_from_comment("comments-json", None, None, None, None, comment)
72-
})
73-
.collect();
74-
return Ok(LoadedFeedbackEvalInput {
75-
total_comments_seen,
76-
total_reviews_seen: 0,
77-
comments,
78-
});
79-
}
80-
81-
anyhow::bail!(
82-
"Unsupported feedback eval input format: expected reviews.json, a comments array, or semantic feedback store JSON"
83-
)
23+
let value = serde_json::from_str(content).map_err(|_| {
24+
anyhow::anyhow!(
25+
"Unsupported feedback eval input format: expected reviews.json, a comments array, or semantic feedback store JSON"
26+
)
27+
})?;
28+
let Some(input_format) = detect_feedback_eval_input_format(&value) else {
29+
anyhow::bail!(
30+
"Unsupported feedback eval input format: expected reviews.json, a comments array, or semantic feedback store JSON"
31+
);
32+
};
33+
load_feedback_eval_input_from_value(value, input_format)
8434
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use serde_json::Value;
2+
3+
pub(super) enum FeedbackEvalInputFormat {
4+
ReviewMap,
5+
ReviewList,
6+
SemanticStore,
7+
CommentsJson,
8+
}
9+
10+
pub(super) fn detect_feedback_eval_input_format(value: &Value) -> Option<FeedbackEvalInputFormat> {
11+
match value {
12+
Value::Object(map) => {
13+
if map.contains_key("examples") {
14+
Some(FeedbackEvalInputFormat::SemanticStore)
15+
} else if map.values().all(is_review_session_like) {
16+
Some(FeedbackEvalInputFormat::ReviewMap)
17+
} else {
18+
None
19+
}
20+
}
21+
Value::Array(items) => {
22+
let Some(first) = items.first() else {
23+
return Some(FeedbackEvalInputFormat::ReviewList);
24+
};
25+
if is_review_session_like(first) {
26+
Some(FeedbackEvalInputFormat::ReviewList)
27+
} else if is_comment_like(first) {
28+
Some(FeedbackEvalInputFormat::CommentsJson)
29+
} else {
30+
None
31+
}
32+
}
33+
_ => None,
34+
}
35+
}
36+
37+
fn is_review_session_like(value: &Value) -> bool {
38+
value.as_object().is_some_and(|object| {
39+
object.contains_key("comments") || object.contains_key("event") || object.contains_key("id")
40+
})
41+
}
42+
43+
fn is_comment_like(value: &Value) -> bool {
44+
value.as_object().is_some_and(|object| {
45+
object.contains_key("file_path")
46+
|| object.contains_key("content")
47+
|| object.contains_key("feedback")
48+
})
49+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use anyhow::Result;
2+
use serde_json::Value;
3+
use std::collections::HashMap;
4+
5+
use crate::core;
6+
use crate::server::state::ReviewSession;
7+
8+
use super::super::super::{FeedbackEvalComment, LoadedFeedbackEvalInput};
9+
use super::super::conversion::{extend_from_review_session, feedback_comment_from_comment};
10+
use super::format::FeedbackEvalInputFormat;
11+
12+
pub(super) fn load_feedback_eval_input_from_value(
13+
value: Value,
14+
input_format: FeedbackEvalInputFormat,
15+
) -> Result<LoadedFeedbackEvalInput> {
16+
match input_format {
17+
FeedbackEvalInputFormat::ReviewMap => {
18+
load_feedback_eval_input_from_review_map(serde_json::from_value(value)?)
19+
}
20+
FeedbackEvalInputFormat::ReviewList => {
21+
load_feedback_eval_input_from_review_list(serde_json::from_value(value)?)
22+
}
23+
FeedbackEvalInputFormat::SemanticStore => {
24+
load_feedback_eval_input_from_semantic_store(serde_json::from_value(value)?)
25+
}
26+
FeedbackEvalInputFormat::CommentsJson => {
27+
load_feedback_eval_input_from_comments_json(serde_json::from_value(value)?)
28+
}
29+
}
30+
}
31+
32+
fn load_feedback_eval_input_from_review_map(
33+
review_map: HashMap<String, ReviewSession>,
34+
) -> Result<LoadedFeedbackEvalInput> {
35+
let mut loaded = LoadedFeedbackEvalInput::default();
36+
for (review_id, session) in review_map {
37+
extend_from_review_session(&mut loaded, Some(review_id), session);
38+
}
39+
Ok(loaded)
40+
}
41+
42+
fn load_feedback_eval_input_from_review_list(
43+
review_list: Vec<ReviewSession>,
44+
) -> Result<LoadedFeedbackEvalInput> {
45+
let mut loaded = LoadedFeedbackEvalInput::default();
46+
for session in review_list {
47+
let review_id = session.id.clone();
48+
extend_from_review_session(&mut loaded, Some(review_id), session);
49+
}
50+
Ok(loaded)
51+
}
52+
53+
fn load_feedback_eval_input_from_semantic_store(
54+
store: core::SemanticFeedbackStore,
55+
) -> Result<LoadedFeedbackEvalInput> {
56+
let total_comments_seen = store.examples.len();
57+
let comments = store
58+
.examples
59+
.into_iter()
60+
.map(|example| FeedbackEvalComment {
61+
source_kind: "semantic-feedback".to_string(),
62+
review_id: None,
63+
repo: None,
64+
pr_number: None,
65+
title: None,
66+
file_path: None,
67+
line_number: None,
68+
file_patterns: example.file_patterns,
69+
content: example.content,
70+
category: example.category,
71+
severity: None,
72+
confidence: None,
73+
accepted: example.accepted,
74+
})
75+
.collect();
76+
77+
Ok(LoadedFeedbackEvalInput {
78+
total_comments_seen,
79+
total_reviews_seen: 0,
80+
comments,
81+
})
82+
}
83+
84+
fn load_feedback_eval_input_from_comments_json(
85+
comments: Vec<core::Comment>,
86+
) -> Result<LoadedFeedbackEvalInput> {
87+
let total_comments_seen = comments.len();
88+
let comments = comments
89+
.into_iter()
90+
.filter_map(|comment| {
91+
feedback_comment_from_comment("comments-json", None, None, None, None, comment)
92+
})
93+
.collect();
94+
95+
Ok(LoadedFeedbackEvalInput {
96+
total_comments_seen,
97+
total_reviews_seen: 0,
98+
comments,
99+
})
100+
}

0 commit comments

Comments
 (0)