Skip to content

Commit 5592389

Browse files
authored
refactor: dedupe route/query types (#1014)
1 parent c3706e2 commit 5592389

32 files changed

+267
-463
lines changed

crates/yamlpatch/src/lib.rs

Lines changed: 10 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3,98 +3,6 @@
33
use std::borrow::Cow;
44

55
use line_index::{LineCol, TextRange, TextSize};
6-
use serde::Serialize;
7-
8-
/// Represents a route component in a YAML path.
9-
#[derive(Serialize, Clone, Debug)]
10-
pub enum RouteComponent<'doc> {
11-
/// A key in a mapping.
12-
Key(&'doc str),
13-
/// An index in a sequence.
14-
Index(usize),
15-
}
16-
17-
impl From<usize> for RouteComponent<'_> {
18-
fn from(value: usize) -> Self {
19-
Self::Index(value)
20-
}
21-
}
22-
23-
impl<'doc> From<&'doc str> for RouteComponent<'doc> {
24-
fn from(value: &'doc str) -> Self {
25-
Self::Key(value)
26-
}
27-
}
28-
29-
/// Represents a route (path) to a YAML feature.
30-
#[derive(Serialize, Clone, Debug)]
31-
pub struct Route<'doc> {
32-
components: Vec<RouteComponent<'doc>>,
33-
}
34-
35-
impl Default for Route<'_> {
36-
fn default() -> Self {
37-
Self::new()
38-
}
39-
}
40-
41-
impl<'doc> Route<'doc> {
42-
/// Create a new empty route.
43-
pub fn new() -> Route<'doc> {
44-
Self {
45-
components: Default::default(),
46-
}
47-
}
48-
49-
/// Create a new route with the given keys appended.
50-
pub fn with_keys(&self, keys: &[RouteComponent<'doc>]) -> Route<'doc> {
51-
let mut components = self.components.clone();
52-
components.extend(keys.iter().cloned());
53-
Route { components }
54-
}
55-
56-
/// Check if this route is the root route (empty).
57-
pub fn is_root(&self) -> bool {
58-
self.components.is_empty()
59-
}
60-
61-
/// Convert this route to a yamlpath query.
62-
pub fn to_query(&self) -> Option<yamlpath::Query<'doc>> {
63-
if self.is_root() {
64-
return None;
65-
}
66-
67-
let mut builder = yamlpath::QueryBuilder::new();
68-
69-
for component in &self.components {
70-
builder = match component {
71-
RouteComponent::Key(key) => builder.key(key),
72-
RouteComponent::Index(idx) => builder.index(*idx),
73-
}
74-
}
75-
76-
Some(builder.build())
77-
}
78-
}
79-
80-
impl<'doc> From<Vec<RouteComponent<'doc>>> for Route<'doc> {
81-
fn from(components: Vec<RouteComponent<'doc>>) -> Self {
82-
Self { components }
83-
}
84-
}
85-
86-
/// Macro to create a route from a series of keys and indices.
87-
#[macro_export]
88-
macro_rules! route {
89-
($($key:expr),* $(,)?) => {
90-
$crate::Route::from(
91-
vec![$($crate::RouteComponent::from($key)),*]
92-
)
93-
};
94-
() => {
95-
$crate::Route::new()
96-
};
97-
}
986

997
/// Error types for YAML patch operations
1008
#[derive(thiserror::Error, Debug)]
@@ -189,7 +97,7 @@ impl Style {
18997
#[derive(Debug, Clone)]
19098
pub struct Patch<'doc> {
19199
/// The route to the feature to patch.
192-
pub route: Route<'doc>,
100+
pub route: yamlpath::Route<'doc>,
193101
/// The operation to perform on the feature.
194102
pub operation: Op<'doc>,
195103
}
@@ -375,11 +283,7 @@ fn apply_single_patch(
375283
// Check to see whether `key` is already present within the route.
376284
// NOTE: Safe unwrap, since `with_keys` ensures we always have at
377285
// least one component.
378-
let key_query = patch
379-
.route
380-
.with_keys(&[key.as_str().into()])
381-
.to_query()
382-
.unwrap();
286+
let key_query = patch.route.with_key(key.as_str());
383287

384288
if document.query_exists(&key_query) {
385289
return Err(Error::InvalidOperation(format!(
@@ -389,7 +293,7 @@ fn apply_single_patch(
389293
)));
390294
}
391295

392-
let feature = if patch.route.is_root() {
296+
let feature = if patch.route.is_empty() {
393297
document.top_feature()?
394298
} else {
395299
route_to_feature_exact(&patch.route, document)?.ok_or_else(|| {
@@ -428,7 +332,7 @@ fn apply_single_patch(
428332
yamlpath::Document::new(result).map_err(Error::from)
429333
}
430334
Op::MergeInto { key, updates } => {
431-
let existing_key_route = patch.route.with_keys(&[key.as_str().into()]);
335+
let existing_key_route = patch.route.with_key(key.as_str());
432336
match route_to_feature_exact(&existing_key_route, document) {
433337
// The key already exists, and has a nonempty body.
434338
Ok(Some(existing_feature)) => {
@@ -474,7 +378,7 @@ fn apply_single_patch(
474378
current_document = apply_single_patch(
475379
&current_document,
476380
&Patch {
477-
route: existing_key_route.with_keys(&[k.as_str().into()]),
381+
route: existing_key_route.with_key(k.as_str()),
478382
operation: Op::Replace(v.clone()),
479383
},
480384
)?;
@@ -514,7 +418,7 @@ fn apply_single_patch(
514418
}
515419
}
516420
Op::Remove => {
517-
if patch.route.is_root() {
421+
if patch.route.is_empty() {
518422
return Err(Error::InvalidOperation(
519423
"Cannot remove root document".to_string(),
520424
));
@@ -542,23 +446,17 @@ fn apply_single_patch(
542446
}
543447

544448
pub fn route_to_feature_pretty<'a>(
545-
route: &Route<'_>,
449+
route: &yamlpath::Route<'_>,
546450
doc: &'a yamlpath::Document,
547451
) -> Result<yamlpath::Feature<'a>, Error> {
548-
match route.to_query() {
549-
Some(query) => doc.query_pretty(&query).map_err(Error::from),
550-
None => Ok(doc.root()),
551-
}
452+
doc.query_pretty(route).map_err(Error::from)
552453
}
553454

554455
pub fn route_to_feature_exact<'a>(
555-
route: &Route<'_>,
456+
route: &yamlpath::Route<'_>,
556457
doc: &'a yamlpath::Document,
557458
) -> Result<Option<yamlpath::Feature<'a>>, Error> {
558-
match route.to_query() {
559-
Some(query) => doc.query_exact(&query).map_err(Error::from),
560-
None => Ok(Some(doc.root())),
561-
}
459+
doc.query_exact(route).map_err(Error::from)
562460
}
563461

564462
/// Serialize a serde_yaml::Value to a YAML string, handling different types appropriately

crates/yamlpatch/tests/unit_tests.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use yamlpatch::*;
2+
use yamlpath::route;
23

34
#[test]
45
fn test_serialize_flow() {
@@ -916,11 +917,11 @@ jobs:
916917

917918
// Test what yamlpath extracts for the checkout step
918919
let doc = yamlpath::Document::new(original).unwrap();
919-
let checkout_query = route!("jobs", "test", "steps", 0).to_query().unwrap();
920+
let checkout_query = route!("jobs", "test", "steps", 0);
920921
let checkout_feature = doc.query_pretty(&checkout_query).unwrap();
921922

922923
// Test what yamlpath extracts for the test job
923-
let job_query = route!("jobs", "test").to_query().unwrap();
924+
let job_query = route!("jobs", "test");
924925
let job_feature = doc.query_pretty(&job_query).unwrap();
925926

926927
// Assert that the checkout step extraction includes the expected content
@@ -1002,11 +1003,11 @@ fn test_comment_boundary_issue() {
10021003

10031004
// See what yamlpath extracts for step 0
10041005
let doc = yamlpath::Document::new(original).unwrap();
1005-
let step0_query = route!("steps", 0).to_query().unwrap();
1006+
let step0_query = route!("steps", 0);
10061007
let step0_feature = doc.query_pretty(&step0_query).unwrap();
10071008

10081009
// See what yamlpath extracts for step 1
1009-
let step1_query = route!("steps", 1).to_query().unwrap();
1010+
let step1_query = route!("steps", 1);
10101011
let step1_feature = doc.query_pretty(&step1_query).unwrap();
10111012

10121013
// Check for overlaps
@@ -1308,7 +1309,7 @@ fn test_debug_indentation_issue() {
13081309

13091310
// Test yamlpath extraction
13101311
let doc = yamlpath::Document::new(original).unwrap();
1311-
let step_query = route!("jobs", "build", "steps", 0).to_query().unwrap();
1312+
let step_query = route!("jobs", "build", "steps", 0);
13121313
let step_feature = doc.query_pretty(&step_query).unwrap();
13131314

13141315
// Test indentation calculation and content extraction
@@ -1410,9 +1411,7 @@ jobs:
14101411

14111412
// Test yamlpath extraction of the env section
14121413
let doc = yamlpath::Document::new(original).unwrap();
1413-
let env_query = route!("jobs", "test", "steps", 0, "env")
1414-
.to_query()
1415-
.unwrap();
1414+
let env_query = route!("jobs", "test", "steps", 0, "env");
14161415

14171416
if let Ok(env_feature) = doc.query_pretty(&env_query) {
14181417
let env_content = doc.extract(&env_feature);

crates/yamlpath/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ workspace = true
1616

1717
[dependencies]
1818
line-index.workspace = true
19+
serde.workspace = true
1920
thiserror.workspace = true
2021
tree-sitter.workspace = true
2122
tree-sitter-yaml = { workspace = true }
2223

2324
[dev-dependencies]
24-
serde.workspace = true
2525
serde_yaml.workspace = true

0 commit comments

Comments
 (0)