Skip to main content

morphix/adapter/
mod.rs

1//! Adapters for serializing mutations to different formats.
2//!
3//! This module provides the [`Adapter`] trait and implementations for various serialization
4//! formats. Adapters bridge the gap between morphix's internal mutation representation and
5//! external formats like JSON or YAML.
6
7use std::mem::take;
8
9use crate::{Mutation, MutationError, MutationKind, Mutations, Path, PathSegment};
10
11#[cfg(feature = "json")]
12mod json;
13#[cfg(feature = "yaml")]
14mod yaml;
15
16#[cfg(feature = "json")]
17pub use json::Json;
18#[cfg(feature = "yaml")]
19pub use yaml::Yaml;
20
21/// Trait for adapting mutations to different serialization formats.
22///
23/// The [`Adapter`] trait provides an abstraction layer between the mutation detection system and
24/// the serialization format. This allows morphix to support multiple output formats while
25/// maintaining type safety.
26///
27/// ## Type Parameters
28///
29/// - [`Value`](Adapter::Value): Type used to represent [`Replace`](MutationKind::Replace) and
30///   [`Append`](MutationKind::Append) values.
31/// - [`Error`](Adapter::Error): Error type for serialization / deserialization operations.
32pub trait Adapter: Sized {
33    /// Type used to represent [`Replace`](MutationKind::Replace) and
34    /// [`Append`](MutationKind::Append) values.
35    type Value;
36
37    /// Error type for serialization / deserialization operations.
38    type Error;
39
40    /// Constructs the adapter from an optional mutation.
41    fn from_mutations(mutation: Mutations) -> Result<Self, Self::Error>;
42
43    /// Gets a mutable reference to a nested value by path segment.
44    ///
45    /// This method navigates into `value` using the provided `segment` and returns a mutable
46    /// reference to the nested value if it exists.
47    ///
48    /// ## Parameters
49    ///
50    /// - `value`: The value to navigate into
51    /// - `segment`: The path segment indicating which nested value to access
52    /// - `allow_create`: If `true` and the segment refers to a non-existent key in an object / map,
53    ///   an empty value will be created at that location
54    ///
55    /// ## Returns
56    ///
57    /// - `Some(value)`: A mutable reference to the nested value
58    /// - `None`: If the operation is not supported on this value type, or if the segment refers to
59    ///   a non-existent location and `allow_create` is `false`
60    fn get_mut<'a>(
61        value: &'a mut Self::Value,
62        segment: &PathSegment,
63        allow_create: bool,
64    ) -> Option<&'a mut Self::Value>;
65
66    /// Removes a value at the specified path segment.
67    ///
68    /// This method removes and returns the value at the location specified by `segment`
69    /// within `value`. It is used to apply [`Delete`](MutationKind::Delete) mutations.
70    ///
71    /// ## Parameters
72    ///
73    /// - `value`: The parent value containing the element to remove
74    /// - `segment`: The path segment indicating which nested value to remove
75    ///
76    /// ## Returns
77    ///
78    /// - `Some(removed_value)`: The removed value if the operation succeeded
79    /// - `None`: If the operation is not supported on this value type (e.g., not a map), or if the
80    ///   segment refers to a non-existent location
81    #[cfg(feature = "delete")]
82    fn delete(value: &mut Self::Value, segment: &PathSegment) -> Option<Self::Value>;
83
84    /// Appends a value to the end of another value.
85    ///
86    /// This method performs an append operation similar to [`String::push_str`] or
87    /// [`Extend::extend`], merging `append_value` into the end of `value`.
88    ///
89    /// ## Parameters
90    ///
91    /// - `value`: The value to append to
92    /// - `append_value`: The value to be appended
93    ///
94    /// ## Returns
95    ///
96    /// - `Some(append_len)`: The length of the appended portion
97    /// - `None`: If the operation is not supported (e.g., incompatible types between `value` and
98    ///   `append_value`, or `value` is not an appendable type)
99    ///
100    /// ## Note
101    ///
102    /// For strings, the returned length represents the char count, not the byte length.
103    #[cfg(feature = "append")]
104    fn append(value: &mut Self::Value, append_value: Self::Value) -> Option<usize>;
105
106    /// Returns the appendable length of a value.
107    ///
108    /// This method returns the current length of a value that can be used with
109    /// [`append`](Adapter::append) operations.
110    ///
111    /// ## Returns
112    ///
113    /// - `Some(len)`: The current length of the value
114    /// - `None`: If the value is not an appendable type
115    ///
116    /// ## Note
117    ///
118    /// For strings, the returned length represents the char count, not the byte length.
119    #[cfg(feature = "append")]
120    fn len(value: &Self::Value) -> Option<usize>;
121
122    /// Truncates a value by removing elements from the end.
123    ///
124    /// This method removes up to `truncate_len` elements from the end of `value`.
125    ///
126    /// ## Parameters
127    ///
128    /// - `value`: The value to truncate
129    /// - `truncate_len`: The number of elements to remove from the end
130    ///
131    /// ## Returns
132    ///
133    /// - `Some(remaining)`: The remaining truncation length that could not be applied. Returns `0`
134    ///   if the full truncation was successful. If `truncate_len` exceeds the actual length,
135    ///   returns `truncate_len - actual_len` and clears the value.
136    /// - `None`: If the operation is not supported on this value type
137    ///
138    /// ## Note
139    ///
140    /// For strings, the returned length represents the char count, not the byte length.
141    #[cfg(feature = "truncate")]
142    fn truncate(value: &mut Self::Value, truncate_len: usize) -> Option<usize>;
143
144    /// Applies a [Mutation] to an existing value.
145    fn mutate(
146        mut value: &mut Self::Value,
147        mut mutation: Mutation<Self::Value>,
148        path_stack: &mut Path<false>,
149    ) -> Result<(), MutationError> {
150        let is_replace = matches!(mutation.kind, MutationKind::Replace { .. });
151        #[cfg(feature = "delete")]
152        let is_delete = matches!(mutation.kind, MutationKind::Delete);
153
154        while let Some(segment) = mutation.path.pop() {
155            let is_last_segment = mutation.path.is_empty();
156            #[cfg(feature = "delete")]
157            if is_last_segment && is_delete {
158                match Self::delete(value, &segment) {
159                    Some(_) => return Ok(()),
160                    None => {
161                        path_stack.push(segment);
162                        return Err(MutationError::IndexError { path: take(path_stack) });
163                    }
164                }
165            }
166            let inner_value = Self::get_mut(value, &segment, is_replace && is_last_segment);
167            path_stack.push(segment);
168            let Some(inner_value) = inner_value else {
169                return Err(MutationError::IndexError { path: take(path_stack) });
170            };
171            value = inner_value;
172        }
173        #[cfg(feature = "delete")]
174        if is_delete {
175            return Err(MutationError::IndexError { path: take(path_stack) });
176        }
177
178        match mutation.kind {
179            MutationKind::Replace(replace_value) => {
180                *value = replace_value;
181            }
182            #[cfg(feature = "append")]
183            MutationKind::Append(append_value) => {
184                if Self::append(value, append_value).is_none() {
185                    return Err(MutationError::OperationError { path: take(path_stack) });
186                }
187            }
188            #[cfg(feature = "truncate")]
189            MutationKind::Truncate(truncate_len) => {
190                let Some(remaining) = Self::truncate(value, truncate_len) else {
191                    return Err(MutationError::OperationError { path: take(path_stack) });
192                };
193                if remaining > 0 {
194                    return Err(MutationError::TruncateError {
195                        path: take(path_stack),
196                        actual_len: truncate_len - remaining,
197                        truncate_len,
198                    });
199                }
200            }
201            #[cfg(feature = "delete")]
202            MutationKind::Delete => unreachable!(),
203            MutationKind::Batch(mutations) => {
204                let len = path_stack.len();
205                for mutation in mutations {
206                    Self::mutate(value, mutation, path_stack)?;
207                    path_stack.truncate(len);
208                }
209            }
210        }
211
212        Ok(())
213    }
214}