use serde::{Deserialize, Serialize};
pub type SpliceSpan = crate::output::SpanResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MagellanSpan {
pub span_id: String,
pub file_path: String,
pub byte_start: usize,
pub byte_end: usize,
pub start_line: usize,
pub end_line: usize,
pub start_col: usize,
pub end_col: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<crate::output::SpanContext>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantics: Option<crate::output::SpanSemantics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksums: Option<crate::output::SpanChecksums>,
}
impl MagellanSpan {
pub fn new(
span_id: String,
file_path: String,
byte_start: usize,
byte_end: usize,
start_line: usize,
end_line: usize,
start_col: usize,
end_col: usize,
) -> Self {
Self {
span_id,
file_path,
byte_start,
byte_end,
start_line,
end_line,
start_col,
end_col,
context: None,
semantics: None,
checksums: None,
}
}
pub fn with_context(mut self, context: crate::output::SpanContext) -> Self {
self.context = Some(context);
self
}
pub fn with_semantics(mut self, semantics: crate::output::SpanSemantics) -> Self {
self.semantics = Some(semantics);
self
}
pub fn with_checksums(mut self, checksums: crate::output::SpanChecksums) -> Self {
self.checksums = Some(checksums);
self
}
}
pub fn from_magellan(span: MagellanSpan) -> SpliceSpan {
let mut result = SpliceSpan::from_byte_span(span.file_path, span.byte_start, span.byte_end);
result = result.with_line_col(span.start_line, span.end_line, span.start_col, span.end_col);
result.span_id = span.span_id;
if let Some(context) = span.context {
result = result.with_context(context);
}
if let Some(semantics) = span.semantics {
result.semantics = Some(semantics);
}
if let Some(checksums) = span.checksums {
result.checksums = Some(checksums);
}
result
}
pub fn to_magellan(span: SpliceSpan) -> MagellanSpan {
MagellanSpan {
span_id: span.span_id,
file_path: span.file_path,
byte_start: span.byte_start,
byte_end: span.byte_end,
start_line: span.start_line,
end_line: span.end_line,
start_col: span.start_col,
end_col: span.end_col,
context: span.context,
semantics: span.semantics,
checksums: span.checksums,
}
}
pub fn translate_field_name(splice_field: &str) -> Option<&'static str> {
match splice_field {
"line_start" => Some("start_line"),
"line_end" => Some("end_line"),
"col_start" => Some("start_col"),
"col_end" => Some("end_col"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_magellan_translation() {
let magellan = MagellanSpan::new(
"test_span_id".to_string(),
"/path/to/file.rs".to_string(),
100,
200,
5,
10,
0,
4,
);
let splice = from_magellan(magellan.clone());
assert_eq!(splice.start_line, 5, "start_line should map to start_line");
assert_eq!(splice.end_line, 10, "end_line should map to end_line");
assert_eq!(splice.start_col, 0, "start_col should map to start_col");
assert_eq!(splice.end_col, 4, "end_col should map to end_col");
assert_eq!(splice.byte_start, 100, "byte_start should be preserved");
assert_eq!(splice.byte_end, 200, "byte_end should be preserved");
assert_eq!(
splice.file_path, "/path/to/file.rs",
"file_path should be preserved"
);
assert_eq!(
splice.span_id, "test_span_id",
"span_id should be preserved"
);
}
#[test]
fn test_to_magellan_translation() {
let splice = SpliceSpan::from_byte_span("/path/to/file.rs".to_string(), 100, 200)
.with_line_col(5, 10, 0, 4);
let magellan = to_magellan(splice.clone());
assert_eq!(
magellan.start_line, 5,
"start_line should come from start_line"
);
assert_eq!(magellan.end_line, 10, "end_line should come from end_line");
assert_eq!(
magellan.start_col, 0,
"start_col should come from start_col"
);
assert_eq!(magellan.end_col, 4, "end_col should come from end_col");
assert_eq!(magellan.byte_start, 100, "byte_start should be preserved");
assert_eq!(magellan.byte_end, 200, "byte_end should be preserved");
assert_eq!(
magellan.file_path, "/path/to/file.rs",
"file_path should be preserved"
);
}
#[test]
fn test_roundtrip_translation() {
let original = SpliceSpan::from_byte_span("/path/to/file.rs".to_string(), 100, 200)
.with_line_col(5, 10, 0, 4);
let magellan = to_magellan(original.clone());
let roundtrip = from_magellan(magellan);
assert_eq!(roundtrip.file_path, original.file_path);
assert_eq!(roundtrip.byte_start, original.byte_start);
assert_eq!(roundtrip.byte_end, original.byte_end);
assert_eq!(roundtrip.start_line, original.start_line);
assert_eq!(roundtrip.end_line, original.end_line);
assert_eq!(roundtrip.start_col, original.start_col);
assert_eq!(roundtrip.end_col, original.end_col);
assert_eq!(roundtrip.span_id, original.span_id);
}
#[test]
fn test_translate_field_name() {
assert_eq!(translate_field_name("line_start"), Some("start_line"));
assert_eq!(translate_field_name("line_end"), Some("end_line"));
assert_eq!(translate_field_name("col_start"), Some("start_col"));
assert_eq!(translate_field_name("col_end"), Some("end_col"));
assert_eq!(translate_field_name("file_path"), None);
assert_eq!(translate_field_name("span_id"), None);
assert_eq!(translate_field_name("byte_start"), None);
assert_eq!(translate_field_name("byte_end"), None);
assert_eq!(translate_field_name("unknown_field"), None);
}
#[test]
fn test_optional_fields_preserved() {
let context = crate::output::SpanContext {
before: vec!["line 1".to_string()],
selected: vec!["line 2".to_string()],
after: vec!["line 3".to_string()],
};
let semantics = crate::output::SpanSemantics {
kind: "function".to_string(),
language: "rust".to_string(),
};
let checksums = crate::output::SpanChecksums {
checksum_before: Some("abc123".to_string()),
checksum_after: Some("def456".to_string()),
file_checksum_before: Some("file789".to_string()),
};
let magellan = MagellanSpan::new(
"test_span_id".to_string(),
"/path/to/file.rs".to_string(),
100,
200,
5,
10,
0,
4,
)
.with_context(context.clone())
.with_semantics(semantics.clone())
.with_checksums(checksums.clone());
let splice = from_magellan(magellan);
let roundtrip = to_magellan(splice);
assert!(roundtrip.context.is_some());
assert!(roundtrip.semantics.is_some());
assert!(roundtrip.checksums.is_some());
let roundtrip_context = roundtrip.context.unwrap();
assert_eq!(roundtrip_context.before, context.before);
assert_eq!(roundtrip_context.selected, context.selected);
assert_eq!(roundtrip_context.after, context.after);
let roundtrip_semantics = roundtrip.semantics.unwrap();
assert_eq!(roundtrip_semantics.kind, semantics.kind);
assert_eq!(roundtrip_semantics.language, semantics.language);
let roundtrip_checksums = roundtrip.checksums.unwrap();
assert_eq!(
roundtrip_checksums.checksum_before,
checksums.checksum_before
);
assert_eq!(roundtrip_checksums.checksum_after, checksums.checksum_after);
assert_eq!(
roundtrip_checksums.file_checksum_before,
checksums.file_checksum_before
);
}
#[test]
fn test_span_id_preserved() {
let magellan = MagellanSpan::new(
"unique_span_id_12345".to_string(),
"/path/to/file.rs".to_string(),
100,
200,
5,
10,
0,
4,
);
let splice = from_magellan(magellan.clone());
assert_eq!(splice.span_id, "unique_span_id_12345");
let magellan2 = to_magellan(splice);
assert_eq!(magellan2.span_id, "unique_span_id_12345");
}
}