Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement observe/observe_deep #76

Merged
merged 2 commits into from
Dec 5, 2024
Merged

feat: implement observe/observe_deep #76

merged 2 commits into from
Dec 5, 2024

Conversation

satoren
Copy link
Owner

@satoren satoren commented Oct 12, 2024

Unlike the original observe/observe_deep, it is not called in the middle of a transaction.
It is called in the form of a process message after the transaction is completed.

#67

Summary by CodeRabbit

  • New Features

    • Introduced functions for observing shared types, enhancing event handling capabilities.
    • Added new modules for handling events related to arrays, maps, text, and XML structures.
    • Implemented a protocol for managing shared types with observation functionalities.
  • Bug Fixes

    • Deprecated the get/2 function in favor of fetch/2, improving clarity in method usage.
  • Tests

    • Expanded test coverage for observing changes in arrays, maps, text, and XML elements, ensuring robust functionality of new features.

@coveralls
Copy link

coveralls commented Oct 12, 2024

Coverage Status

coverage: 94.722% (+0.2%) from 94.493%
when pulling e52b92f on observe
into acc3f38 on main.

@satoren satoren force-pushed the observe branch 4 times, most recently from 4c1c70f to 3888ce4 Compare October 22, 2024 09:28
Copy link
Contributor

coderabbitai bot commented Nov 10, 2024

Walkthrough

The pull request introduces several enhancements to the Yex library, including new functions for observing shared types in the Yex.Nif module, the addition of event structures for various data types, and improvements to the observation capabilities of existing modules like Yex.Map and Yex.Text. Additionally, new test cases have been implemented to validate these observation features across different data structures. Overall, the changes expand the functionality for monitoring and managing shared types within the Yex system.

Changes

File Change Summary
lib/nif.ex Added functions: shared_type_observe/5 and shared_type_observe_deep/5, both returning :erlang.nif_error(:nif_not_loaded).
lib/protocols/awareness.ex Added module documentation for Yex.Awareness, removed example for creating an awareness instance; no changes to functionality.
lib/shared_type/event.ex Introduced new modules: Yex.ArrayEvent, Yex.MapEvent, Yex.TextEvent, Yex.XmlEvent, Yex.XmlTextEvent, each with structs for event handling.
lib/shared_type/map.ex Deprecated get/2 method, updated documentation for fetch/2 and fetch!/2.
lib/shared_type/shared_type.ex Added Yex.SharedType module with functions for observing changes: observe/2, unobserve/1, observe_deep/2, unobserve_deep/1.
native/yex/src/array.rs Implemented traits NifSharedTypeDeepObservable and NifSharedTypeObservable for NifArray, enhancing event observation capabilities.
native/yex/src/atoms.rs Added new atoms: observe_event, observe_deep_event, action, old_value, new_value, add, update.
native/yex/src/event.rs Enhanced event handling with new structs and enums for various event types, including NifYArrayChange, NifYTextDelta, and NifYMapChange.
native/yex/src/lib.rs Added new module event.
native/yex/src/map.rs Implemented NifSharedTypeDeepObservable and NifSharedTypeObservable for NifMap.
native/yex/src/subscription.rs Updated import statements and added arc method; changed return type of sub_unsubscribe to NifResult<Atom>.
native/yex/src/text.rs Implemented NifSharedTypeDeepObservable and NifSharedTypeObservable for NifText.
native/yex/src/xml.rs Implemented NifSharedTypeDeepObservable and NifSharedTypeObservable for NifXmlFragment, NifXmlElement, and NifXmlText.
test/shared_type/array_test.exs Added tests for observing changes in arrays, including deep observation scenarios.
test/shared_type/map_test.exs Added tests for observing changes in maps, including deep observation scenarios.
test/shared_type/shared_type_test.exs Introduced tests for the Yex.SharedType module, validating observe/2 and observe_deep/2 functionality.
test/shared_type/text_test.exs Added tests for observing changes in text, including deep observation scenarios.
test/shared_type/xml_element_test.exs Added tests for observing changes in XML elements, including deep observation scenarios.
test/shared_type/xml_fragment_test.exs Added tests for observing changes in XML fragments, including deep observation scenarios.
test/shared_type/xml_text_test.exs Added tests for observing changes in XML text, including deep observation scenarios.
native/yex/src/yinput.rs Introduced new enum NifSharedTypeInput to represent various shared types.

Possibly related PRs

🐰 "In the meadow where we play,
Observing changes every day.
With events that hop and bounce,
Our shared types, we shall announce!
From arrays to XML, all in sight,
Together we’ll code, with pure delight!" 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@satoren satoren force-pushed the observe branch 5 times, most recently from 24cf968 to c634c05 Compare November 15, 2024 11:10
@satoren satoren marked this pull request as ready for review November 15, 2024 11:14
@satoren satoren changed the title [wip] observe and observe_deep observe and observe_deep Nov 15, 2024
@satoren satoren changed the title observe and observe_deep feat: implement observe/observe_deep Nov 15, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (52)
native/yex/src/subscription.rs (1)

11-15: LGTM: Thread-safe resource implementation

The arc method correctly implements a thread-safe wrapper for subscriptions using Mutex and ResourceArc. This pattern ensures safe sharing of subscription resources across thread boundaries.

Consider documenting the thread-safety guarantees and usage patterns for future maintainers, as this is a critical part of the subscription system's architecture.

test/shared_type/shared_type_test.exs (1)

5-51: Consider refactoring test cases for better maintainability

While the error cases are comprehensive, the test structure could be improved for better maintainability and readability.

Consider refactoring using test case parameterization:

-  test "raise error when wrong usage" do
+  describe "invalid inputs" do
+    test_cases = [
+      {1, "integer"},
+      {make_ref(), "reference"}
+    ]
+
+    for {input, type} <- test_cases do
+      test "raises Protocol.UndefinedError for #{type}" do
+        doc = Doc.new()
+        
+        for {function, args} <- [
+          {:unobserve, [input]},
+          {:unobserve_deep, [input]},
+          {:observe, [input]},
+          {:observe, [input, []]},
+          {:observe_deep, [input]},
+          {:observe_deep, [input, []]}
+        ] do
+          assert_raise Protocol.UndefinedError, fn ->
+            apply(SharedType, function, args)
+          end
+        end
+      end
+    end
+  end

This refactoring would:

  1. Make it easier to add new test cases
  2. Reduce code duplication
  3. Provide clearer test descriptions
  4. Make the test structure more maintainable
lib/shared_type/event.ex (7)

5-7: Fix incorrect @see references in documentation

The @see tags reference incorrect function arities:

  • Yex.Array.observe/1 should be observe/2
  • Yex.Array.observe_deep/1 should be observe_deep/2

15-19: Improve type specifications for better clarity

Consider these improvements:

  1. The change type union could be more explicitly defined as a custom type
  2. Document the purpose of the empty map %{} in the change union type
+  @type array_change ::
+    %{insert: list()} |
+    %{delete: number()} |
+    %{} # represents no change
+
   @type t :: %__MODULE__{
         path: list(number() | String.t()),
         target: Yex.Array.t(),
-        change: %{insert: list()} | %{delete: number()} | %{}
+        change: array_change()
       }

27-29: Fix incorrect @see references in documentation

The @see tags reference incorrect function arities:

  • Yex.Map.observe/1 should be observe/2
  • Yex.Map.observe_deep/1 should be observe_deep/2

41-47: Improve type documentation for the keys field

The keys type definition needs more clarity:

  1. The purpose of mapping to an empty map is not clear
  2. Consider adding a type description comment
-  @type keys :: %{String.t() => %{}}
+  @typedoc """
+  Represents the changed keys in the map.
+  The empty map value indicates the key was involved in the change.
+  """
+  @type keys :: %{String.t() => %{}}

   @type t :: %__MODULE__{
         path: list(number() | String.t()),
         target: Yex.Map.t(),
         keys: keys
       }

55-57: Fix incorrect @see references in documentation

The @see tags reference incorrect function arities:

  • Yex.Text.observe/1 should be observe/2

77-79: Fix incorrect @see references in XML documentation

The @see tags reference Text.observe instead of XML-related functions. Consider updating to reference the appropriate XML observation functions.


88-93: Standardize keys type definition across event types

The keys field type in XmlEvent differs from MapEvent:

  • MapEvent uses a custom keys type with a map of empty maps
  • XmlEvent reuses the array-style change type
    Consider standardizing this approach across event types.
lib/shared_type/xml_fragment.ex (1)

102-114: Consider enhancing documentation and cleanup mechanism

The implementation looks correct but could benefit from some improvements:

  1. The function stores the subscription in the process dictionary but there's no documented cleanup mechanism. This could lead to memory leaks if subscriptions aren't properly cleaned up.
  2. The :metadata option usage should be documented.

Consider:

  1. Adding documentation about how to unsubscribe/cleanup the observation
  2. Documenting the available options and their usage
  3. Adding a complementary unobserve/1 function

Example documentation enhancement:

 @doc """
 see `Yex.SharedType.observe/2`
+
+ ## Options
+
+ * `:metadata` - Additional metadata to be included with observation events
+
+ ## Cleanup
+
+ To stop observing and cleanup the subscription, use `unobserve/1` with the returned reference.
 """
lib/shared_type/map.ex (2)

118-120: Documentation could be more comprehensive.

While the documentation references external modules, it would be helpful to include:

  • Example usage showing how to handle observation events
  • List of events that can be observed
  • Description of the difference between observe/2 and observe_deep/2

Example documentation structure:

@doc """
Observes changes to the map's direct key-value pairs.

## Events
- `:insert` - Triggered when a new key-value pair is added
- `:update` - Triggered when a value is modified
- `:delete` - Triggered when a key-value pair is removed

## Examples
    iex> doc = Yex.Doc.new()
    iex> map = Yex.Doc.get_map(doc, "map")
    iex> ref = Yex.Map.observe(map)
    iex> Yex.Map.set(map, "key", "value")
    iex> receive do
    ...>   {:insert, ^ref, "key", "value", metadata} -> :ok
    ...> after
    ...>   100 -> :error
    ...> end
    :ok
"""

Also applies to: 129-131


124-125: Consider adding error handling for NIF operations.

The NIF calls (map_observe and map_observe_deep) could potentially fail. Consider wrapping them in a try-rescue block or handling potential errors.

Example implementation:

-    sub = Yex.Nif.map_observe(map, cur_txn(map), self(), ref, Keyword.get(opt, :metadata))
-    Process.put(ref, sub)
+    case Yex.Nif.map_observe(map, cur_txn(map), self(), ref, Keyword.get(opt, :metadata)) do
+      {:ok, sub} ->
+        Process.put(ref, sub)
+        {:ok, ref}
+      {:error, reason} ->
+        {:error, reason}
+    end

Also applies to: 135-136

lib/shared_type/array.ex (3)

161-170: Consider adding cleanup mechanism and improving documentation

The implementation looks functionally correct, but there are a few considerations:

  1. The subscription stored in the process dictionary should have a cleanup mechanism to prevent memory leaks
  2. The :metadata option should be documented in the function's documentation

Consider adding:

  1. A cleanup function to remove the subscription:
@doc """
Stops observing changes for the given reference.
"""
@spec unobserve(reference()) :: :ok
def unobserve(ref) do
  if sub = Process.delete(ref) do
    Yex.Nif.array_unobserve(sub)
  end
  :ok
end
  1. Enhanced documentation:
@doc """
Observes changes to the array.

## Options
  * `:metadata` - Additional metadata to be included with change events
"""

172-174: Enhance documentation to clarify deep observation behavior

The current documentation only references Yex.SharedType.observe_deep/2 without explaining the difference between regular and deep observation.

Consider expanding the documentation:

@doc """
Observes deep changes to the array and its nested elements.

Deep observation will track changes to nested arrays and maps, unlike regular
observation which only tracks changes to the immediate elements.

See `Yex.SharedType.observe_deep/2` for more details.

## Options
  * `:metadata` - Additional metadata to be included with change events
"""

178-178: Remove unnecessary blank line

For consistent formatting, remove the extra newline after make_ref().

test/shared_type/xml_text_test.exs (3)

152-170: Consider adding complementary test scenarios.

While the current test effectively verifies the basic observation functionality, consider adding:

  1. Cleanup test to verify unsubscribe behavior
  2. Negative test cases (e.g., multiple observers, invalid operations)

Example structure for cleanup test:

test "observe cleanup", %{doc: doc, xml_text: text} do
  ref = SharedType.observe(text)
  :ok = SharedType.unobserve(text, ref)
  
  Doc.transaction(doc, "origin_value", fn ->
    XmlText.insert(text, 0, "123456")
  end)
  
  refute_receive {:observe_event, ^ref, _, _, _}
end

172-172: Fix test name formatting.

Remove the trailing space in the test name for consistency with other test names.

-    test "observe delete ", %{doc: doc, xml_text: text} do
+    test "observe delete", %{doc: doc, xml_text: text} do

189-210: Enhance deep observation test coverage.

Consider the following improvements:

  1. Verify the metadata content instead of using underscore pattern match
  2. Add test cases for nested structure changes to fully validate deep observation

Example structure for nested changes:

test "observe_deep with nested changes", %{doc: doc, xml_text: text} do
  ref = SharedType.observe_deep(text)
  
  :ok = Doc.transaction(doc, "origin_value", fn ->
    XmlText.insert(text, 0, "123")
    XmlText.format(text, 0, 3, %{"bold" => true})
    # Add nested element changes here when supported
  end)
  
  assert_receive {:observe_deep_event, ^ref,
    [
      %Yex.XmlTextEvent{
        path: [],
        target: ^text,
        delta: [
          %{attributes: %{"bold" => true}, insert: "123"}
        ]
      },
      # Assert nested changes here
    ], "origin_value", metadata}
  
  # Verify metadata contents
  assert is_map(metadata)
  # Add specific metadata assertions
end
lib/shared_type/xml_element.ex (3)

144-146: Enhance documentation with more details.

The current documentation only references Yex.SharedType.observe/2 without explaining the purpose, usage, or return value of this function. Consider adding:

  • Purpose of observation
  • Example usage
  • Description of the optional keyword arguments
  • Description of the events that will be received

147-156: Consider memory management for Process dictionary storage.

The subscription is stored in the Process dictionary but there's no clear cleanup mechanism. This could lead to memory leaks if observers aren't properly unsubscribed.

Consider:

  1. Adding an unobserve/1 function to clean up subscriptions
  2. Documenting the cleanup responsibility
  3. Using a supervised GenServer or Registry instead of Process dictionary for better lifecycle management

158-174: Clarify function visibility and consider code reuse.

A few concerns about this implementation:

  1. The function is marked with @doc false but has a public spec - this is inconsistent. If it's intended to be internal, consider making it private with defp.
  2. There's significant code duplication with observe/2. Consider extracting the common pattern:
- @doc false
- @spec observe_deep(t, keyword()) :: reference()
- def observe_deep(%__MODULE__{} = xml, opt \\ []) do
-   ref = make_ref()
-
-   sub =
-     Yex.Nif.xml_element_observe_deep(
-       xml,
-       cur_txn(xml),
-       self(),
-       ref,
-       Keyword.get(opt, :metadata)
-     )
-
-   Process.put(ref, sub)
-   ref
- end
+ defp do_observe(xml, opt, observer_fn) do
+   ref = make_ref()
+   sub = observer_fn.(xml, cur_txn(xml), self(), ref, Keyword.get(opt, :metadata))
+   Process.put(ref, sub)
+   ref
+ end
+
+ @doc false
+ @spec observe_deep(t, keyword()) :: reference()
+ def observe_deep(%__MODULE__{} = xml, opt \\ []) do
+   do_observe(xml, opt, &Yex.Nif.xml_element_observe_deep/5)
+ end

This would also simplify the observe/2 function:

 @spec observe(t, keyword()) :: reference()
 def observe(%__MODULE__{} = xml, opt \\ []) do
-  ref = make_ref()
-
-  sub =
-    Yex.Nif.xml_element_observe(xml, cur_txn(xml), self(), ref, Keyword.get(opt, :metadata))
-
-  Process.put(ref, sub)
-  ref
+  do_observe(xml, opt, &Yex.Nif.xml_element_observe/5)
 end
native/yex/src/text.rs (3)

215-224: Add documentation for the text_observe function.

Consider adding documentation that explains:

  • The purpose of the observation mechanism
  • The meaning and expected format of each parameter
  • The subscription lifecycle

Add documentation like this:

/// Observes changes to the text content.
/// 
/// # Parameters
/// * `text` - The text to observe
/// * `current_transaction` - Optional ongoing transaction
/// * `pid` - Process ID to receive notifications
/// * `ref_term` - Reference term for correlation
/// * `metadata` - Additional metadata for the subscription
/// 
/// # Returns
/// A subscription resource that will be cleaned up when dropped

227-235: Add documentation for the text_observe_deep function.

Similar to text_observe, this function needs documentation explaining how deep observation differs from regular observation.

Add documentation like this:

/// Deeply observes changes to the text content and its nested structures.
/// 
/// Unlike `text_observe`, this function also tracks changes in nested data structures
/// and attributes.
/// 
/// # Parameters
/// * `text` - The text to observe
/// * `current_transaction` - Optional ongoing transaction
/// * `pid` - Process ID to receive notifications
/// * `ref_term` - Reference term for correlation
/// * `metadata` - Additional metadata for the subscription
/// 
/// # Returns
/// A subscription resource that will be cleaned up when dropped

215-235: Consider adding parameter validation.

Both observation functions could benefit from basic parameter validation:

  • Ensure the text reference is valid
  • Validate metadata format if there are expectations

Example implementation:

fn text_observe(
    text: NifText,
    current_transaction: Option<ResourceArc<TransactionResource>>,
    pid: rustler::LocalPid,
    ref_term: Term<'_>,
    metadata: Term<'_>,
) -> NifResult<ResourceArc<SubscriptionResource>> {
    // Validate text reference
    if !text.is_valid() {
        return Err(rustler::Error::Term(Box::new("Invalid text reference")));
    }
    
    // Validate metadata if needed
    if !metadata.is_map() {
        return Err(rustler::Error::Term(Box::new("Metadata must be a map")));
    }
    
    text.observe(current_transaction, pid, ref_term, metadata)
}
test/shared_type/text_test.exs (4)

174-196: Consider adding boundary test cases.

While the test covers basic text insertion with attributes, consider adding test cases for:

  • Empty string insertions
  • Unicode character handling
  • Maximum text length scenarios

198-216: Add test cases for overlapping operations.

The delete test verifies basic deletion, but should also test:

  • Deleting at boundaries (start/end of text)
  • Deleting with overlapping formatting attributes

239-261: Add test cases for nested structure changes.

While the test verifies basic deep observation, consider adding tests for:

  • Nested attribute changes
  • Multiple levels of depth in the text structure
  • Concurrent modifications at different depths

263-281: Consider adding tests for selective unobserve_deep.

The test covers basic unobserve_deep functionality well. Consider adding tests for:

  • Unobserving specific paths while keeping others observed
  • Verifying that sibling paths remain observed after partial unobserve
lib/protocols/awareness.ex (1)

1-5: Enhance module documentation to reflect new capabilities

While the documentation is concise, it could be more comprehensive given the module's importance in the observation system. Consider adding:

  • A brief overview of the awareness protocol's purpose and benefits
  • Examples of common use cases
  • Integration points with the new observation capabilities being added in this PR
 @moduledoc """
-  Awareness is an optional feature that works well together with Yjs.
+  Awareness is an optional feature that works well together with Yjs, providing real-time
+  presence and state synchronization capabilities across connected peers.
+
+  ## Overview
+  The Awareness protocol enables:
+  - Real-time state synchronization between clients
+  - Presence detection and monitoring
+  - Integration with observation systems for state changes
+
+  ## Common Use Cases
+  - Collaborative editing presence indicators
+  - User status synchronization
+  - Real-time cursor positions
+
+  ## Integration with Observation System
+  The Awareness module works seamlessly with the observation system, allowing you to:
+  - Monitor state changes using `monitor_change/2`
+  - Track updates via `monitor_update/2`
+  - Integrate with other observable types like `Yex.Array` and `Yex.Map`
 """
test/shared_type/xml_element_test.exs (3)

155-169: Consider expanding test coverage

While the basic observation test is good, consider adding tests for:

  • Multiple attribute changes in a single transaction
  • Observer cleanup/unsubscribe functionality
  • Edge cases (empty values, special characters)

171-171: Remove extra space in test name

The test name "observe delete " contains a trailing space.

-    test "observe delete ", %{doc: doc, xml_element: xml_element} do
+    test "observe delete", %{doc: doc, xml_element: xml_element} do

201-203: Use more descriptive variable names

Consider renaming variables to better describe their role in the structure:

-      el2 = XmlElement.first_child(xml_element)
-      el3 = XmlElement.first_child(el2)
-      text = XmlElement.first_child(el3)
+      outer_span = XmlElement.first_child(xml_element)
+      inner_span = XmlElement.first_child(outer_span)
+      text_node = XmlElement.first_child(inner_span)
lib/nif.ex (3)

43-46: Add documentation for text observation functions.

Consider adding @doc comments to describe:

  • The purpose and behavior of these observation functions
  • The expected format and usage of the metadata parameter
  • The difference between regular and deep observation

74-77: Add type specifications for map observation functions.

Consider adding @spec declarations to clearly define the expected types for:

  • The map reference
  • The transaction context
  • The metadata parameter
  • The return value

Line range hint 43-157: Document the observation pattern architecture.

Consider adding module-level documentation to describe:

  • The overall observation pattern and its implementation across different data types
  • The difference between regular and deep observation behaviors
  • The event propagation mechanism and how it integrates with the Rust NIF layer
  • Guidelines for implementing new observable types in the future

This documentation would help maintain consistency as the codebase grows and new observable types are added.

native/yex/src/map.rs (1)

121-141: Consider adding documentation for the new observation functions

Adding doc comments to map_observe and map_observe_deep would enhance code clarity and assist other developers in understanding their usage and parameters.

lib/shared_type/shared_type.ex (5)

8-9: Fix grammatical error in documentation

The sentence should read: "Registers a change observer that will be messaged every time this shared type is modified."


26-27: Fix grammatical error in documentation

The sentence should read: "Registers a change observer that will be messaged every time this shared type is modified."


45-46: Fix grammatical error in documentation

The sentence should read: "Registers a change observer that will be messaged every time this shared type or any of its children is modified."


62-63: Fix grammatical error in documentation

The sentence should read: "Registers a change observer that will be messaged every time this shared type or any of its children is modified."


54-54: Fix grammatical errors in documentation

Change "is a array of event struct that describes the change;" to "is an array of event structs that describe the changes;"

test/shared_type/map_test.exs (1)

116-116: Remove trailing whitespace in test names

The test names on lines 116 and 139 have trailing spaces after "set " and "delete ". Consider removing the extra spaces for consistency and clarity.

Also applies to: 139-139

test/shared_type/xml_fragment_test.exs (1)

173-223: Refactor observe_deep test for improved readability

The observe_deep test involves complex nested structures and multiple operations within a transaction. To enhance readability and maintainability, consider the following suggestions:

  • Extract helper functions: Break down the creation of nested XML elements and the manipulation within the transaction into separate helper functions.
  • Add descriptive comments: Include comments explaining each step of the test to guide future reviewers and maintainers.

Apply this diff to extract helper functions:

 defmodule YexXmlFragmentTest do
   # ...

+  defp setup_nested_xml(f) do
+    XmlFragment.push(
+      f,
+      XmlElementPrelim.new("span", [
+        XmlElementPrelim.new("span", [
+          XmlTextPrelim.from("text")
+        ])
+      ])
+    )
+  end
+
+  defp perform_deep_operations(doc, f, el2, el3, text) do
+    Doc.transaction(doc, "origin_value", fn ->
+      XmlFragment.push(f, XmlTextPrelim.from("1"))
+      XmlElement.insert_attribute(el2, "attr", "value")
+      XmlElement.push(el3, XmlElementPrelim.empty("div"))
+      XmlText.insert(text, 0, "text")
+    end)
+  end

   test "observe_deep", %{doc: doc, xml_fragment: f} do
-    XmlFragment.push(
-      f,
-      XmlElementPrelim.new("span", [
-        XmlElementPrelim.new("span", [
-          XmlTextPrelim.from("text")
-        ])
-      ])
-    )
+    setup_nested_xml(f)

     el2 = XmlFragment.first_child(f)
     el3 = XmlElement.first_child(el2)
     text = XmlElement.first_child(el3)

     ref = SharedType.observe_deep(f)

-    :ok =
-      Doc.transaction(doc, "origin_value", fn ->
-        XmlFragment.push(f, XmlTextPrelim.from("1"))
-        XmlElement.insert_attribute(el2, "attr", "value")
-        XmlElement.push(el3, XmlElementPrelim.empty("div"))
-        XmlText.insert(text, 0, "text")
-      end)
+    perform_deep_operations(doc, f, el2, el3, text)

     assert_receive {:observe_deep_event, ^ref,
                     [
                       %Yex.XmlEvent{
                         path: [],
                         target: ^f,
                         keys: %{},
                         delta: [%{retain: 1}, %{insert: [%Yex.XmlText{}]}]
                       },
                       %Yex.XmlEvent{
                         path: [0],
                         target: ^el2,
                         keys: %{"attr" => %{action: :add, new_value: "value"}},
                         delta: []
                       },
                       %Yex.XmlEvent{
                         keys: %{},
                         path: [0, 0],
                         target: ^el3,
                         delta: [%{retain: 1}, %{insert: [%Yex.XmlElement{}]}]
                       },
                       %Yex.XmlTextEvent{
                         path: [0, 0, 0],
                         target: ^text,
                         delta: [%{insert: "text"}]
                       }
                     ], "origin_value", _metadata}
   end
 end
test/shared_type/array_test.exs (1)

215-216: Consistent Use of Array Keys in Tests

In the tests, the array is retrieved using different keys such as "text", "data", and "array". For better consistency and clarity across all tests, consider standardizing the key used when calling Doc.get_array/2. Using a consistent key name can improve readability and maintainability.

Also applies to: 234-235, 254-255, 273-274, 292-293, 337-338

native/yex/src/xml.rs (1)

203-223: Refactor duplicated observe and observe_deep functions

The observe and observe_deep functions for NifXmlFragment, NifXmlElement, and NifXmlText have nearly identical implementations. Consider abstracting the common logic into a generic function or macro to reduce code duplication and enhance maintainability.

Also applies to: 391-411, 565-585

native/yex/src/event.rs (7)

128-132: Consider removing unnecessary Decoder implementation

The Decoder implementation for NifYArrayChange is unimplemented using unimplemented!(). If decoding is not required for this struct, consider removing the Decoder implementation entirely to avoid confusion.


210-214: Consider removing unnecessary Decoder implementation

The Decoder implementation for NifYTextDelta is unimplemented. If decoding is unnecessary for this struct, it's better to remove the Decoder implementation to maintain code clarity.


285-289: Consider removing unnecessary Decoder implementation

The Decoder for NifYMapChange is currently unimplemented. Remove it if decoding isn't needed for this struct to keep the codebase clean.


49-51: Simplify match expression using impl From

The From implementation for PathSegment can be simplified for clarity.

Apply the following change:

fn from(value: yrs::types::PathSegment) -> Self {
    match value {
-       yrs::types::PathSegment::Key(key) => PathSegment::Key(key.to_string()),
+       yrs::types::PathSegment::Key(key) => Self::Key(key.to_string()),
-       yrs::types::PathSegment::Index(index) => PathSegment::Index(index),
+       yrs::types::PathSegment::Index(index) => Self::Index(index),
    }
}

This utilizes Self to make the code more maintainable.


77-85: Enhance error handling in Decoder implementation for NifPath

In the Decoder for NifPath, consider providing more informative error messages instead of using BadArg. This will help users understand why decoding failed.

Modify the error handling as follows:

else {
-   Err(rustler::Error::BadArg)
+   Err(rustler::Error::Atom("invalid_path_segment"))
}

This change provides a specific error atom that can be matched on the Erlang side.


162-208: Optimize by avoiding unnecessary cloning of delta vector

In the Encoder implementation for NifYTextDelta, the delta vector is cloned before iteration. Avoiding this clone can improve performance.

Apply the following change:

- self.delta.clone().into_iter()
+ self.delta.iter()

Adjust subsequent code to work with references as needed.


237-283: Avoid unnecessary cloning of change HashMap

In NifYMapChange's Encoder, cloning the change HashMap before iteration may not be necessary and can be avoided to enhance performance.

Adjust the code as follows:

- self.change.clone().into_iter()
+ self.change.iter()

Ensure the iteration works with references after this modification.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a23ce60 and c634c05.

📒 Files selected for processing (25)
  • lib/nif.ex (5 hunks)
  • lib/protocols/awareness.ex (1 hunks)
  • lib/shared_type/array.ex (1 hunks)
  • lib/shared_type/event.ex (1 hunks)
  • lib/shared_type/map.ex (2 hunks)
  • lib/shared_type/shared_type.ex (1 hunks)
  • lib/shared_type/text.ex (1 hunks)
  • lib/shared_type/xml_element.ex (1 hunks)
  • lib/shared_type/xml_fragment.ex (1 hunks)
  • lib/shared_type/xml_text.ex (1 hunks)
  • native/yex/src/array.rs (3 hunks)
  • native/yex/src/atoms.rs (2 hunks)
  • native/yex/src/event.rs (1 hunks)
  • native/yex/src/lib.rs (1 hunks)
  • native/yex/src/map.rs (3 hunks)
  • native/yex/src/subscription.rs (1 hunks)
  • native/yex/src/text.rs (3 hunks)
  • native/yex/src/xml.rs (8 hunks)
  • test/shared_type/array_test.exs (2 hunks)
  • test/shared_type/map_test.exs (2 hunks)
  • test/shared_type/shared_type_test.exs (1 hunks)
  • test/shared_type/text_test.exs (2 hunks)
  • test/shared_type/xml_element_test.exs (2 hunks)
  • test/shared_type/xml_fragment_test.exs (2 hunks)
  • test/shared_type/xml_text_test.exs (2 hunks)
🔇 Additional comments (51)
native/yex/src/atoms.rs (2)

11-12: LGTM: Event type atoms follow consistent naming.

The new observation event atoms are well-named and align with the PR's objective of implementing observation capabilities.


28-32: LGTM: Action-related atoms are logically grouped.

The new action-related atoms (action, old_value, new_value, add, update) are appropriately placed and follow the established pattern. They provide the necessary metadata fields for tracking changes in the observation system.

Let's verify the usage of these new atoms:

✅ Verification successful

Based on the search results, I can now generate the final response as we have sufficient evidence showing the usage of these atoms in the codebase.

Verified: Action-related atoms are correctly used in event handling

The atoms are properly used in the event system, particularly in native/yex/src/event.rs where they're used to represent different types of changes:

  • action, new_value for add operations
  • action, old_value for delete operations
  • action, old_value, new_value for update operations

The test files confirm these atoms are working as expected in the observation system, as seen in the map event tests:

%{action: :add, new_value: "Hello"}
%{action: :delete, old_value: "Hello"}
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of the new atoms in the codebase
# Expected: These atoms should be used in observation-related functions

# Search for observation-related functions using these atoms
rg -A 5 'observe_event|observe_deep_event'

# Search for usage of action-related atoms in event handling
rg -A 5 '\b(action|old_value|new_value|add|update)\b' --type rust

Length of output: 20185

native/yex/src/subscription.rs (2)

1-1: LGTM: Import changes are appropriate

The updated imports properly support the new functionality with Atom and NifResult types.

Also applies to: 5-5


18-25: LGTM: Improved NIF return type

The function now properly returns an Erlang atom on success, which better aligns with Erlang/Elixir conventions. The mutex handling and subscription cleanup are implemented correctly.

Let's verify that all callers handle the new return type correctly:

✅ Verification successful

Since the rg search only found the function definition itself and no other calls to sub_unsubscribe, let's verify if this function is exported and called from Elixir/Erlang side.


Based on the search results, I can now generate the final response since we have found all the necessary information about the usage of sub_unsubscribe:

Return type change is handled correctly by all callers

The change from Result<(), NifError> to NifResult<Atom> is properly handled by all callers:

  • The function is correctly exported as a NIF with #[rustler::nif]
  • All Elixir callers handle the atom return value appropriately:
    • Yex.Doc.demonitor_update_v1/1
    • Yex.Doc.demonitor_update_v2/1
    • Yex.SharedType.unsubscribe/1
    • Yex.Awareness.demonitor_update/1
    • Yex.Awareness.demonitor_change/1

The callers don't explicitly handle the return value since they only care about the side effect of unsubscribing, and any error would be propagated as an exception by the NIF runtime.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for calls to sub_unsubscribe to ensure they handle the atom return
rg -t rust "sub_unsubscribe" -A 2 -B 2

Length of output: 384


Script:

#!/bin/bash
# Check if sub_unsubscribe is exported in the NIF module
rg -t rust "rustler::nif" -B 2 -A 2

# Also check Elixir files for unsubscribe calls
rg -t elixir "unsubscribe" -B 2 -A 2

Length of output: 21396

native/yex/src/lib.rs (1)

7-7: LGTM! Verify module integration.

The event module declaration follows Rust conventions and is properly placed. Since this is a WIP PR introducing observation capabilities, ensure the module is properly integrated with the components that will use it.

Let's verify the module integration:

✅ Verification successful

Module integration is properly implemented

The event module is well integrated into the codebase:

  • Defines core event-related traits (NifSharedTypeObservable, NifSharedTypeDeepObservable) used across different types
  • Implements event handling for all major data types:
    • Map events (NifMapEvent)
    • Array events (NifArrayEvent)
    • Text events (NifTextEvent)
    • XML events (NifXmlEvent, NifXmlTextEvent)
  • Each type (Map, Array, Text, XML) correctly implements the observable traits and provides observe/observe_deep functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how the event module is used across the codebase
# Expected: Find implementations and usages in array.rs, map.rs, text.rs, etc.

# Check for event module imports
rg -t rust "^use .*event::" native/yex/src/

# Check for event-related trait implementations
ast-grep --pattern 'impl $_ for $_ {
  $$$
  observe($$$) {
    $$$
  }
  $$$
}'

Length of output: 237


Script:

#!/bin/bash
# Let's check the event module's implementation and its traits
cat native/yex/src/event.rs

# Check for any other event-related implementations in map.rs since it imports event
cat native/yex/src/map.rs

# Search for any other potential event-related code
rg -t rust "observe|Observable|MapEvent" native/yex/src/

Length of output: 24197

test/shared_type/shared_type_test.exs (2)

1-4: LGTM: Module setup follows best practices

The module setup is clean and follows Elixir conventions with proper use of ExUnit and clear aliasing.


1-58: Verify test coverage for new functionality

While the PR shows overall coverage improvement to 95.455%, let's verify coverage for the new observation functionality.

✅ Verification successful

Test coverage is comprehensive for SharedType protocol implementation

The test coverage for the SharedType protocol implementation is adequate based on the following analysis:

  1. The test file covers all error cases for invalid inputs:

    • Testing unobserve/unobserve_deep with non-reference values
    • Testing observe/observe_deep with invalid types (integers and raw references)
    • Testing all variants of observe functions (with and without options)
  2. The implementation shows complete protocol coverage:

    • Protocol defined for all shared types (Array, Map, Text, XmlElement, XmlFragment, XmlText)
    • All observe/unobserve functions are properly delegated
    • Reference implementation handles unsubscribe logic correctly
  3. The test file includes positive cases:

    • Testing successful observation of text type
    • Testing both regular and deep observation variants

The test coverage aligns well with the implementation, covering both error paths and successful scenarios for the observation functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check coverage for the SharedType module and related files

# Find all related implementation files
echo "Finding related implementation files..."
fd -e ex -e exs . | grep -i "shared_type\|observe"

# Extract coverage data from coveralls
echo "Checking coverage data..."
find . -name "coveralls.json" -exec cat {} \; | \
  jq '.source_files[] | select(.name | test("shared_type|observe"; "i")) | {name: .name, coverage: .coverage}'

Length of output: 930


Script:

#!/bin/bash
# Let's try a different approach to check test coverage

# First, let's examine the implementation of SharedType protocol and its functions
echo "=== SharedType Implementation ==="
cat lib/shared_type/shared_type.ex

# Then check the test file content
echo -e "\n=== SharedType Test ==="
cat test/shared_type/shared_type_test.exs

# Look for any observe/unobserve function calls in implementation files
echo -e "\n=== Observe/Unobserve Usage ==="
rg "observe|unobserve" lib/shared_type/ -A 2 -B 2

Length of output: 20678

lib/shared_type/event.ex (1)

65-69: Verify target type for TextEvent

The target type is specified as Yex.Map.t() which seems incorrect for a text event. Should this be Yex.Text.t() instead?

lib/shared_type/xml_fragment.ex (1)

116-134: Verify deep observation behavior and enhance documentation

The implementation looks correct and follows a consistent pattern with observe/2. However:

  1. The deep observation behavior and its differences from regular observation should be documented
  2. The cleanup mechanism concerns are shared with observe/2

Let's verify the deep observation implementation:

Consider enhancing the documentation to explain:

  1. What "deep" observation means in the context of XML fragments
  2. How deep observation differs from regular observation
  3. Performance implications of deep observation

Example documentation enhancement:

 @doc """
 see `Yex.SharedType.observe_deep/2`
+
+ Observes changes to the XML fragment and all its nested elements recursively.
+ Unlike `observe/2` which only tracks direct children, this function tracks all
+ descendants of the fragment.
+
+ ## Performance Considerations
+
+ Deep observation may have higher performance overhead compared to regular
+ observation as it tracks changes at all levels of the XML hierarchy.
 """
✅ Verification successful

Based on the test files and implementation details gathered, I can now provide a final response:

Deep observation behavior is correctly implemented and well-tested

The implementation is verified to be correct. The test cases demonstrate that:

  1. observe_deep tracks changes recursively through the XML hierarchy:

    • Changes to the fragment itself (pushing new elements)
    • Changes to child elements (attribute modifications)
    • Changes to nested elements (adding new elements)
    • Changes to text content deep in the structure
  2. The events are properly structured with path information to track the hierarchy:

    • Root level changes have empty path []
    • First level children have path [0]
    • Deeper nested changes have paths like [0, 0] and [0, 0, 0]

The suggested documentation enhancement would still be valuable but not critical, as the behavior is well-tested and follows a consistent pattern across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for tests that verify the difference between observe and observe_deep
rg -l "observe_deep.*test" test/

Length of output: 34


Script:

#!/bin/bash
# Let's try different search patterns to find relevant test files and implementation details

# Search for any mentions of observe_deep in test files
rg "observe_deep" test/

# Search for the implementation of xml_fragment_observe_deep in Nif
rg "xml_fragment_observe_deep" lib/

# Look for any documentation or examples of deep observation
rg -i "deep observation|observe.*deep" {lib,test}/**/*.{ex,exs,md}

# Find any related test files for XML fragment
fd "xml.*test.exs?" test/

Length of output: 11632


Script:

#!/bin/bash
# Let's examine the test files to understand the behavior difference between observe and observe_deep

# Check xml_fragment_test.exs for both observe and observe_deep tests
rg -A 10 "test \"observe\".*|test \"observe_deep\".*" test/shared_type/xml_fragment_test.exs

# Look at the SharedType documentation for observe_deep
rg -A 10 "@doc.*observe_deep" lib/shared_type/shared_type.ex

Length of output: 778


Script:

#!/bin/bash
# Let's examine the full test files and implementation to understand the behavior

# Get the full content of the xml_fragment_test.exs
cat test/shared_type/xml_fragment_test.exs

# Look for any documentation about deep observation in the codebase
rg -B 2 -A 4 "observe_deep.*::" lib/shared_type/{shared_type,xml_fragment}.ex

# Check if there are any comments or documentation blocks explaining deep observation
rg -B 2 -A 4 "@doc.*observe" lib/shared_type/{shared_type,xml_fragment}.ex

Length of output: 9469

lib/shared_type/map.ex (2)

44-44: LGTM! Documentation indentation fix.

The indentation fix improves readability while maintaining the deprecation notice and comprehensive examples.


122-127: Verify subscription cleanup mechanism.

The subscription is stored in the process dictionary using Process.put/2. This could lead to memory leaks if subscriptions aren't properly cleaned up when they're no longer needed.

Let's check if there's a cleanup mechanism in place:

✅ Verification successful

Subscription cleanup is properly implemented

The codebase has a robust cleanup mechanism through unobserve/1 and unobserve_deep/1 functions in lib/shared_type/shared_type.ex. These functions:

  • Remove the subscription reference from the process dictionary via Process.delete/1
  • Clean up the underlying subscription using Yex.Nif.sub_unsubscribe/1

The original review concern about potential memory leaks is addressed by this implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for subscription cleanup code
rg -A 5 "Process\.delete|:DOWN.*subscription|unsubscribe|cleanup.*subscription"

Length of output: 2930

lib/shared_type/text.ex (1)

89-89: Verify NIF implementation and test coverage.

The code calls into Yex.Nif.text_observe and Yex.Nif.text_observe_deep. Let's verify these are properly implemented and tested.

Also applies to: 101-101

✅ Verification successful

Let me gather more information about the NIF implementation since the previous search didn't show the NIF code.


Let me search for the text implementation and tests to verify the functionality.


Based on the code review and verification results, I can now provide a final response:

NIF implementation and test coverage are properly implemented.

The verification shows:

  • The NIF functions text_observe and text_observe_deep are properly implemented in Rust with appropriate error handling and memory safety
  • The Elixir side has proper integration in lib/shared_type/text.ex with:
    • observe/2 function that calls Yex.Nif.text_observe
    • observe_deep/2 function that calls Yex.Nif.text_observe_deep
  • Both functions handle transaction context and metadata properly
  • The implementation follows the same pattern as other shared types (Array, Map, etc.)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check NIF implementation
echo "Checking for NIF implementation..."
rg -A 5 "def text_observe" native/yex/src/

# Check test coverage
echo "Checking test coverage..."
rg -A 10 "test.*observe" test/

Length of output: 13474


Script:

#!/bin/bash
# Check for NIF module definition
echo "Checking NIF module definition..."
rg -A 5 "defmodule.*Nif" lib/

# Look for text_observe in Rust code
echo "Checking Rust implementation..."
fd -e rs . native/yex/src/ -x cat {}

Length of output: 95648


Script:

#!/bin/bash
# Check text implementation
echo "Checking text implementation..."
rg -A 10 "text_observe" lib/shared_type/text.ex

# Check text tests
echo "Checking text tests..."
rg -A 10 "test.*observe.*text" test/shared_type/text_test.exs

Length of output: 792

lib/shared_type/xml_text.ex (1)

85-94: 🛠️ Refactor suggestion

Enhance documentation and add error handling

The implementation has several areas for improvement:

  1. The documentation should explain the function's behavior, parameters, and return value directly rather than just referring to Yex.SharedType.observe/2
  2. Consider adding error handling for the NIF call
  3. The subscription stored in the Process dictionary should be cleaned up when no longer needed

Consider updating the documentation and implementation:

 @doc """
-see `Yex.SharedType.observe/2`
+Observes changes to the XML text node and notifies the current process.
+
+## Parameters
+  - xml: The XML text node to observe
+  - opt: Keyword list of options
+    - :metadata: Additional metadata to include with notifications
+
+## Returns
+  Reference that can be used to unsubscribe
+
+## Examples
+    xml_text = Yex.Doc.get_xml_text(doc, "text")
+    ref = Yex.XmlText.observe(xml_text)
+    # ... later
+    Yex.unobserve(ref)
 """
 @spec observe(t, keyword()) :: reference()
 def observe(%__MODULE__{} = xml, opt \\ []) do
   ref = make_ref()
-  sub = Yex.Nif.xml_text_observe(xml, cur_txn(xml), self(), ref, Keyword.get(opt, :metadata))
-  Process.put(ref, sub)
+  case Yex.Nif.xml_text_observe(xml, cur_txn(xml), self(), ref, Keyword.get(opt, :metadata)) do
+    {:ok, sub} ->
+      Process.put(ref, sub)
+      ref
+    {:error, reason} ->
+      {:error, reason}
+  end
-  ref
 end

Let's verify if there's a cleanup mechanism for the Process dictionary:

lib/shared_type/array.ex (1)

167-167: Verify Nif function implementations

The code calls array_observe and array_observe_deep from the Nif module. Let's verify these implementations exist and follow the same pattern as other Nif functions.

Also applies to: 180-180

✅ Verification successful

Let me gather more context about the observe pattern in other modules to ensure consistency.


Implementation verified and consistent with codebase patterns

The array_observe and array_observe_deep functions are properly implemented in the Rust NIF module (native/yex/src/array.rs) and follow the same pattern as other shared types in the codebase:

  • All shared types (map, text, xml, array) implement both regular and deep observation
  • Consistent function signatures across all implementations (taking the same parameters)
  • Consistent Elixir-side usage pattern with self(), cur_txn, and metadata handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Nif function definitions
rg -A 2 "def array_observe" "native/yex/src"
rg -A 2 "fn array_observe" "native/yex/src"

Length of output: 452


Script:

#!/bin/bash
# Search for observe patterns across the codebase
rg -A 2 "fn \w+_observe" "native/yex/src"
rg -A 2 "observe.*self\(\)" "lib"

Length of output: 4612

test/shared_type/xml_text_test.exs (1)

3-3: LGTM: Module alias is properly structured.

The addition of SharedType to the alias list is appropriate for the new observation-related test cases.

lib/shared_type/xml_element.ex (1)

144-174: Verify integration with the broader observation system.

Since this is part of a larger observation system implementation:

  1. Ensure consistent behavior with other observable types
  2. Verify event format matches the system's expectations
✅ Verification successful

Based on the search results, I can now generate the final response as I have sufficient information to verify the implementation:

Implementation aligns with the broader observation system

The observe and observe_deep implementations in xml_element.ex follow the same consistent pattern used across all shared types:

  • Same function signatures and return types
  • Identical process dictionary usage for subscription management
  • Consistent NIF interaction pattern with cur_txn, self(), ref, and metadata handling

The implementation matches other observable types like Array, Map, Text, and XmlFragment, ensuring system-wide consistency.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other observe implementations to verify consistency
echo "Searching for other observe implementations..."
rg -A 10 "def observe\(" --type elixir

# Search for event handling to understand the expected event format
echo "Searching for event handling..."
rg -A 10 "receive.*do" --type elixir

Length of output: 19804

native/yex/src/text.rs (1)

11-13: LGTM: Clean trait implementations for observation capabilities.

The trait implementations and imports are well-organized and correctly implement the observation pattern. The empty implementation of NifSharedTypeDeepObservable as a marker trait is a good choice for type-level functionality.

Also applies to: 47-50

test/shared_type/text_test.exs (2)

3-3: LGTM: Module alias is properly structured.

The SharedType module alias is correctly grouped with related modules and follows Elixir conventions.


218-236: LGTM: Comprehensive unobserve test coverage.

Good test coverage including:

  • Proper cleanup with unobserve
  • Verification that events stop after unobserving
  • Edge case handling for non-existent references
lib/protocols/awareness.ex (1)

Line range hint 6-183: Implementation looks solid!

The existing implementation:

  • Is well-documented with comprehensive examples
  • Follows Elixir best practices
  • Has proper resource management for subscriptions
  • Shows good test coverage
test/shared_type/xml_element_test.exs (3)

4-13: LGTM: Module aliases are well-organized

The addition of SharedType to the aliases is appropriate for the new observation functionality tests.


172-189: LGTM: Deletion observation test is comprehensive

The test properly verifies the deletion event structure including the delta information.


191-241: LGTM: Deep observation test is thorough

The test effectively verifies:

  • Deep observation of nested structures
  • Multiple types of changes (element insertion, attribute modification, text changes)
  • Correct event ordering and path tracking
lib/nif.ex (1)

61-65: LGTM! Implementation follows module patterns.

The array observation functions maintain consistent parameter naming with other array-related functions in the module.

native/yex/src/map.rs (4)

3-8: Necessary imports added for observation functionality

The added import statements correctly bring in the required modules for implementing the observation traits and functions.


40-43: Trait implementations correctly added to NifMap

The NifMap struct now implements NifSharedTypeObservable and NifSharedTypeDeepObservable traits, with Event correctly defined as NifMapEvent.


121-130: New NIF function map_observe is correctly implemented

The map_observe function enables observation of changes to NifMap instances, passing the appropriate parameters to map.observe.


132-141: New NIF function map_observe_deep is correctly implemented

The map_observe_deep function allows for deep observation of NifMap, correctly invoking map.observe_deep with the required parameters.

lib/shared_type/shared_type.ex (2)

88-93: Verify the handling of 'unobserve' functions

In this implementation for shared types (Yex.Array, etc.), the unobserve and unobserve_deep functions raise a Protocol.UndefinedError. Is this intended? Should these functions be properly implemented to allow unsubscribing from observations?


95-107: Ensure consistency in 'observe' and 'unobserve' implementations

In the implementation for Reference, the observe and observe_deep functions raise Protocol.UndefinedError, while the unobserve and unobserve_deep functions are implemented using unsubscribe/1. This is the inverse of the implementation for shared types. Verify that this difference is intentional and aligns with the desired behavior.

native/yex/src/array.rs (4)

8-10: Addition of imports for observation functionality

The added imports for NifArrayEvent, NifSharedTypeDeepObservable, NifSharedTypeObservable, and SubscriptionResource are necessary and appropriate for implementing the new observation features.


44-47: Implementation of observation traits for NifArray

Implementing NifSharedTypeDeepObservable and NifSharedTypeObservable for NifArray correctly enables observation capabilities. The associated type Event is properly set to NifArrayEvent.


163-172: Definition of array_observe function

The array_observe function is correctly defined to expose array observation via NIF. The function signature and parameters are appropriate.


174-182: Definition of array_observe_deep function

The array_observe_deep function is properly defined to enable deep observation of arrays through NIF. The function signature and parameters are suitable.

test/shared_type/map_test.exs (1)

115-178: Comprehensive and effective tests for observation functionality

The newly added tests for "observe" and "observe_deep" features are well-structured and cover various scenarios thoroughly. They effectively validate the correct behavior of observing changes in maps, including setting, deleting, and unobserving both shallow and deep observations.

Also applies to: 180-242

test/shared_type/xml_fragment_test.exs (3)

3-3: Alias SharedType correctly added for observation functionality

The addition of SharedType to the alias list ensures that the module's observation functions are accessible within the test module.


135-151: Test observe correctly validates observation events

The observe test accurately sets up an observer and asserts that the correct :observe_event message is received after a transaction that modifies the XML fragment.


153-171: Test observe delete correctly handles deletion observation

The test effectively checks that observing a deletion in the XML fragment emits the expected :observe_event with the correct delta indicating the deletion.

test/shared_type/array_test.exs (7)

3-3: Import Alias Update is Appropriate

The addition of SharedType to the module aliases ensures that the module functions are easily accessible, improving code readability.


211-229: Test for 'observe' on Insert Operations is Comprehensive

The test accurately covers the insertion operation within a transaction and correctly asserts the reception of the expected event. The use of pattern matching in assert_receive ensures that the event structure is validated precisely.


231-249: Test for 'observe' on Delete Operations is Correct

The test initializes the array, observes it, performs a delete operation within a transaction, and asserts the expected event. The change is correctly represented as a deletion of one element.


251-268: Test for Retain and Insert Operations Functions as Intended

This test effectively verifies the combined retain and insert operations during observation. The assertion correctly matches the expected change, ensuring that the observation captures both operations.


270-287: Unobserve Functionality is Properly Tested

The test ensures that after unobserving, no events are received upon modifying the array. Additionally, it verifies that unobserving an invalid or non-existent reference gracefully returns :ok, adhering to expected behavior.


290-332: Deep Observation Test Accurately Captures Nested Changes

The observe_deep test sets up a nested data structure and verifies that changes at multiple levels are observed correctly. The assertions validate the path and content of each event, ensuring comprehensive coverage of deep observation functionality.


334-351: Unobserve Deep Functionality is Validated Successfully

This test confirms that after calling unobserve_deep, no deep observation events are received upon data modification. It also checks that unobserving a random reference does not cause errors, maintaining robustness.

native/yex/src/xml.rs (5)

3-3: Import of Term added appropriately

The addition of Term to the imports is necessary for handling term parameters in the new observe functions.


11-11: Necessary imports for observation functionality

Including NifSharedTypeDeepObservable, NifSharedTypeObservable, event types, and SubscriptionResource is essential for implementing the new observability features.

Also applies to: 13-13


52-55: Implemented observability traits for NifXmlFragment

The NifXmlFragment struct now correctly implements NifSharedTypeDeepObservable and NifSharedTypeObservable with the Event type set to NifXmlEvent.


72-75: Implemented observability traits for NifXmlElement

The NifXmlElement struct appropriately implements NifSharedTypeDeepObservable and NifSharedTypeObservable with Event type NifXmlEvent.


119-122: Implemented observability traits for NifXmlText

The NifXmlText struct correctly implements NifSharedTypeDeepObservable and NifSharedTypeObservable with the Event type specified as NifXmlTextEvent.

native/yex/src/event.rs (1)

242-283: Improve match arms by handling all possible EntryChange variants

Ensure that all possible variants of EntryChange are being handled correctly. Verify that no new variants have been added in the yrs crate that are not accounted for, to prevent potential bugs.

Run the following script to check for unhandled variants:

Review the output to confirm that all variants are covered in the match statement.

✅ Verification successful

Let's search for the actual implementation to verify all variants are handled in our match statement.


Looking at the code, I can now generate the final response:

The match statement correctly handles all variants of EntryChange

The code imports EntryChange from yrs::types::map::MapEvent, and the implementation handles all three possible variants of EntryChange:

  • EntryChange::Inserted(content)
  • EntryChange::Removed(old_value)
  • EntryChange::Updated(old_value, new_value)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if all variants of EntryChange are handled.

# Test: List all variants of EntryChange in the yrs crate.
# Expect: Only the handled variants are present.

ast-grep --pattern "pub enum EntryChange { $$$ }" "$(fd 'change.rs' src/)" --json | jq '.[].meta.variants[]'

Length of output: 307


Script:

#!/bin/bash
# Let's try a different approach to find EntryChange enum definition
# First, let's locate the relevant files
rg -l "EntryChange" --type rust

# Then look for the enum definition
rg "enum EntryChange" -A 10 --type rust

Length of output: 97


Script:

#!/bin/bash
# Search for any imports or uses of EntryChange to find its source
rg "use.*EntryChange|EntryChange\s*::" --type rust

Length of output: 293


Script:

#!/bin/bash
# Let's check the imports in event.rs to find where EntryChange is coming from
rg "^use.*EntryChange" native/yex/src/event.rs

# Also check the full content around EntryChange usage to understand its context
rg -B 5 -A 20 "EntryChange::" native/yex/src/event.rs

Length of output: 2002


Script:

#!/bin/bash
# Let's check the entire event.rs file to find the type definition or import of EntryChange
cat native/yex/src/event.rs

Length of output: 15548

test/shared_type/shared_type_test.exs Show resolved Hide resolved
lib/shared_type/event.ex Outdated Show resolved Hide resolved
lib/shared_type/text.ex Outdated Show resolved Hide resolved
lib/shared_type/text.ex Outdated Show resolved Hide resolved
lib/shared_type/xml_text.ex Outdated Show resolved Hide resolved
lib/shared_type/shared_type.ex Outdated Show resolved Hide resolved
test/shared_type/map_test.exs Show resolved Hide resolved
native/yex/src/event.rs Show resolved Hide resolved
native/yex/src/event.rs Show resolved Hide resolved
native/yex/src/event.rs Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (12)
native/yex/src/subscription.rs (2)

11-15: LGTM: Well-implemented thread-safe resource management

The arc method implementation correctly uses:

  • ResourceArc for reference counting
  • Mutex for thread safety
  • Option for nullable state management

This pattern ensures proper resource cleanup and thread-safe access to subscriptions, which is crucial for the observe/observe_deep functionality in a concurrent environment.


18-25: Consider enhancing error handling verbosity

The implementation is correct and thread-safe, but could benefit from more detailed error reporting.

Consider adding more specific error atoms for different failure cases. Here's a suggested improvement:

 #[rustler::nif]
 fn sub_unsubscribe(env: Env<'_>, sub: ResourceArc<SubscriptionResource>) -> NifResult<Atom> {
     ENV.set(&mut env.clone(), || {
         let mut inner = match sub.0.lock() {
             Ok(guard) => guard,
-            Err(poisoned) => poisoned.into_inner(),
+            Err(poisoned) => {
+                // Log the poisoning for debugging
+                eprintln!("Mutex was poisoned: {}", poisoned);
+                poisoned.into_inner()
+            },
         };
         *inner = None;
         Ok(atoms::ok())
     })
 }
test/shared_type/xml_text_test.exs (3)

152-170: Consider using a more descriptive test name

The current test name "observe" is too generic. Consider renaming it to something more specific like "observe_text_modifications" or "observe_insert_and_format" to better describe what's being tested.


172-172: Fix test name formatting

Remove the trailing space in the test name "observe delete " to maintain consistency with other test names.


198-209: Consider adding specific metadata assertions

The test currently uses _metadata placeholder in the assertion, which skips validation of the metadata content. Consider adding specific assertions for the metadata structure if it's relevant to the test case.

test/shared_type/map_test.exs (1)

184-190: Consider extracting setup code to a helper function

The setup code for creating nested data structures could be moved to a helper function to improve readability and reusability.

+ defp setup_nested_map(map) do
+   Map.set(
+     map,
+     "key1",
+     Yex.MapPrelim.from(%{
+       "key2" => Yex.MapPrelim.from(%{"key3" => Yex.ArrayPrelim.from([1, 2, 3, 4])})
+     })
+   )
+ end
test/shared_type/xml_fragment_test.exs (2)

135-151: Consider adding more comprehensive test cases

The current test only verifies a simple push operation. Consider adding test cases for:

  • Multiple operations within the same transaction
  • Different types of XML elements
  • Edge cases like empty elements

183-185: Use more descriptive variable names

The variable names el2, el3 could be more descriptive to better indicate their roles in the XML structure. Consider names like outerSpan, innerSpan, etc.

test/shared_type/array_test.exs (2)

211-289: LGTM! Well-structured test cases for array observation.

The test cases comprehensively cover array operations (insert, delete, retain) and proper cleanup. Good use of transactions and assertions.

Consider adding a test case for concurrent observers to verify multiple observers receive the same events.


290-332: Add documentation for the complex test setup.

While the test is comprehensive, the nested structure setup is complex. Consider adding comments explaining the structure and expected event propagation.

 test "observe_deep" do
   doc = Doc.new()
   array = Doc.get_array(doc, "data")
+  # Setup nested structure:
+  # array
+  #   └── map
+  #       └── key: map
+  #           └── key: array[1,2,3,4]
   Array.insert(
     array,
     0,
test/shared_type/xml_element_test.exs (1)

191-241: Consider breaking down the complex assertion.

The test effectively verifies deep observation, but the assertion block is complex. Consider breaking it down into multiple assertions for better readability and maintenance.

-      assert_receive {:observe_deep_event, ^ref,
-                      [
-                        %Yex.XmlEvent{...},
-                        %Yex.XmlEvent{...},
-                        %Yex.XmlEvent{...},
-                        %Yex.XmlTextEvent{...}
-                      ], "origin_value", _metadata}
+      assert_receive {:observe_deep_event, ^ref, events, "origin_value", _metadata}
+      [root_event, attr_event, child_event, text_event] = events
+      
+      assert %Yex.XmlEvent{
+        path: [],
+        target: ^xml_element,
+        delta: [%{retain: 1}, %{insert: [%Yex.XmlText{}]}]
+      } = root_event
+      
+      # Continue with individual assertions for each event
native/yex/src/yinput.rs (1)

332-340: LGTM! Clean implementation of shared type enum.

The NifSharedTypeInput enum effectively encapsulates all shared type variants. Consider adding documentation comments to explain the purpose and usage of this enum.

+/// Represents different types of shared data structures that can be observed.
+/// This enum is used to provide a unified interface for handling different
+/// shared types in the observation system.
 #[derive(NifUntaggedEnum)]
 pub enum NifSharedTypeInput {
     Text(NifText),
     Map(NifMap),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 18ac445 and 3a0106c.

📒 Files selected for processing (26)
  • lib/nif.ex (1 hunks)
  • lib/protocols/awareness.ex (1 hunks)
  • lib/shared_type/array.ex (1 hunks)
  • lib/shared_type/event.ex (1 hunks)
  • lib/shared_type/map.ex (2 hunks)
  • lib/shared_type/shared_type.ex (1 hunks)
  • lib/shared_type/text.ex (1 hunks)
  • lib/shared_type/xml_element.ex (1 hunks)
  • lib/shared_type/xml_fragment.ex (1 hunks)
  • lib/shared_type/xml_text.ex (1 hunks)
  • native/yex/src/array.rs (2 hunks)
  • native/yex/src/atoms.rs (2 hunks)
  • native/yex/src/event.rs (1 hunks)
  • native/yex/src/lib.rs (1 hunks)
  • native/yex/src/map.rs (2 hunks)
  • native/yex/src/subscription.rs (1 hunks)
  • native/yex/src/text.rs (2 hunks)
  • native/yex/src/xml.rs (5 hunks)
  • native/yex/src/yinput.rs (2 hunks)
  • test/shared_type/array_test.exs (2 hunks)
  • test/shared_type/map_test.exs (2 hunks)
  • test/shared_type/shared_type_test.exs (1 hunks)
  • test/shared_type/text_test.exs (2 hunks)
  • test/shared_type/xml_element_test.exs (2 hunks)
  • test/shared_type/xml_fragment_test.exs (2 hunks)
  • test/shared_type/xml_text_test.exs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
  • native/yex/src/lib.rs
  • lib/shared_type/array.ex
  • lib/shared_type/xml_element.ex
  • native/yex/src/atoms.rs
  • test/shared_type/shared_type_test.exs
  • lib/shared_type/map.ex
  • lib/shared_type/xml_fragment.ex
  • lib/shared_type/text.ex
  • lib/shared_type/xml_text.ex
  • test/shared_type/text_test.exs
  • native/yex/src/xml.rs
  • lib/nif.ex
  • native/yex/src/text.rs
  • lib/protocols/awareness.ex
🔇 Additional comments (10)
native/yex/src/subscription.rs (1)

1-1: LGTM: Import changes are appropriate

The updated imports correctly reflect the new requirements for error handling with NifResult<Atom> and access to atom constants.

Also applies to: 5-5

lib/shared_type/shared_type.ex (1)

46-47: 🛠️ Refactor suggestion

Consider avoiding the use of Process.get/1, Process.put/1, and Process.delete/1

Using the process dictionary can lead to code that's hard to understand and maintain. Consider using a dedicated process or agent to manage subscriptions instead.

Also applies to: 91-92, 105-107, 109-117

native/yex/src/event.rs (3)

109-125: ⚠️ Potential issue

Handle potential errors instead of using unwrap() to prevent panics

In the Encoder implementation for NifYArrayChange, using unwrap() after map_put() can cause the application to panic if an error occurs during map insertion. It's safer to handle the Result properly or use the ? operator to propagate the error.

Apply the following changes to handle errors gracefully:

-map = map.map_put(atoms::insert(), content).unwrap();
+map = map.map_put(atoms::insert(), content)?;

Repeat this pattern for all instances of unwrap() within this method to ensure robust error handling.


178-209: ⚠️ Potential issue

Handle potential errors instead of using unwrap() in NifYTextDelta Encoder

Using unwrap() after map_put() can lead to panics if an error occurs. Use the ? operator to handle Result types and propagate errors properly.

Apply the following changes:

-let mut map = Term::map_new(env).map_put(atoms::insert(), insert).unwrap();
+let mut map = Term::map_new(env).map_put(atoms::insert(), insert)?;

Ensure all instances of unwrap() in this method are addressed accordingly.


253-278: ⚠️ Potential issue

Handle potential errors instead of using unwrap() in NifYMapChange Encoder

Using unwrap() after map_put() can cause panics if an error occurs during map insertion. It's safer to handle the Result properly or use the ? operator to propagate the error.

Modify the code as follows:

-map = map.map_put(atoms::action(), atoms::add()).unwrap()
+map = map.map_put(atoms::action(), atoms::add())?

Repeat this change for all unwrap() calls within this method.

lib/shared_type/event.ex (1)

92-111: Consider refactoring XmlTextEvent to reduce duplication

The XmlTextEvent module appears to be identical to Yex.TextEvent except for its name. Consider creating a common base module or using composition to reduce code duplication.

native/yex/src/map.rs (1)

39-42: Implementing observability traits enhances functionality

The implementation of NifSharedTypeDeepObservable and NifSharedTypeObservable for NifMap effectively integrates event observation capabilities, aligning with the design of shared types in the system.

native/yex/src/array.rs (1)

43-46: Implementing observability traits enhances functionality

The implementation of NifSharedTypeDeepObservable and NifSharedTypeObservable for NifArray effectively integrates event observation capabilities, aligning with the design of shared types in the system.

test/shared_type/map_test.exs (1)

163-163: Incorrect shared type retrieved: 'text' instead of 'map'

The test retrieves a map using Doc.get_map(doc, "text"). This appears to be incorrect as other tests use "map". Consider changing it to Doc.get_map(doc, "map") for consistency.

test/shared_type/xml_element_test.exs (1)

155-169: LGTM! Clear and focused test for XML attribute observation.

The test effectively verifies attribute insertion events with proper assertions.

lib/shared_type/event.ex Outdated Show resolved Hide resolved
Unlike the original observe/observe_deep, it is not called in the middle of a transaction.
It is called in the form of a process message after the transaction is completed.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (16)
lib/shared_type/event.ex (1)

23-23: Add description for Map type changes in documentation

The documentation string is empty. Consider adding a description similar to other event modules.

  @moduledoc """
+ Event when Map type changes
  """
lib/shared_type/shared_type.ex (1)

8-8: Fix grammatical error in documentation

The word "message" should be "messaged" in this context.

- Registers a change observer that will be message every time this shared type is modified.
+ Registers a change observer that will be messaged every time this shared type is modified.
test/shared_type/map_test.exs (2)

115-178: Consider adding edge cases to the observe test suite

The observe test suite could be strengthened by adding the following scenarios:

  • Multiple concurrent observers
  • Nested operations within the same transaction
  • Empty transactions
  • Operations that trigger multiple events

184-190: Consider extracting the nested structure setup

The deep structure setup could be moved to a helper function to improve readability and reusability.

+  defp setup_nested_map(doc) do
+    map = Doc.get_map(doc, "data")
+    Map.set(
+      map,
+      "key1",
+      Yex.MapPrelim.from(%{
+        "key2" => Yex.MapPrelim.from(%{"key3" => Yex.ArrayPrelim.from([1, 2, 3, 4])})
+      })
+    )
+    map
+  end
test/shared_type/text_test.exs (1)

182-186: Add assertions for intermediate state

Consider adding assertions to verify the text content before and after each operation within the transaction.

       :ok =
         Doc.transaction(doc, "origin_value", fn ->
+          assert Text.to_string(text) == ""
           Text.insert(text, 0, "Hello")
+          assert Text.to_string(text) == "Hello"
           Text.insert(text, 6, " World", %{"bold" => true})
+          assert Text.to_string(text) == "Hello World"
         end)
test/shared_type/xml_fragment_test.exs (2)

173-223: Consider breaking down the observe_deep test

The observe_deep test is testing multiple aspects in a single test case. Consider splitting it into smaller, focused test cases:

  1. Basic deep observation
  2. Attribute modifications
  3. Nested element modifications
  4. Text modifications

This would improve test maintainability and make failures easier to diagnose.


197-222: Add comments explaining the event structure

The complex event structure assertion could benefit from comments explaining each part of the expected event.

      assert_receive {:observe_deep_event, ^ref,
                      [
+                       # Root fragment modification
                        %Yex.XmlEvent{
                          path: [],
                          target: ^f,
                          keys: %{},
                          delta: [%{retain: 1}, %{insert: [%Yex.XmlText{}]}]
                        },
+                       # Attribute modification on first span
                        %Yex.XmlEvent{
                          path: [0],
                          target: ^el2,
                          keys: %{"attr" => %{action: :add, new_value: "value"}},
                          delta: []
                        },
+                       # Element modification on nested span
                        %Yex.XmlEvent{
                          keys: %{},
                          path: [0, 0],
                          target: ^el3,
                          delta: [%{retain: 1}, %{insert: [%Yex.XmlElement{}]}]
                        },
+                       # Text modification in deepest element
                        %Yex.XmlTextEvent{
                          path: [0, 0, 0],
                          target: ^text,
                          delta: [%{insert: "text"}]
                        }
                      ], "origin_value", _metadata}
test/shared_type/array_test.exs (6)

211-229: Enhance test coverage with edge cases

While the basic insertion test is good, consider adding these scenarios:

  • Test with empty strings
  • Test with large arrays
  • Test with concurrent transactions

231-249: Add boundary test cases for delete operation

Consider adding these test cases:

  • Deleting from an empty array
  • Deleting with invalid index
  • Deleting multiple elements

251-268: Expand retain and insert test scenarios

Consider adding these test cases:

  • Multiple retain operations
  • Retain with different data types
  • Retain with boundary conditions

270-287: Add edge cases for unobserve functionality

Consider adding these test cases:

  • Multiple unobserve calls on the same reference
  • Unobserve during an ongoing transaction
  • Re-observe after unobserve

290-332: Enhance deep observation test coverage

Consider adding these scenarios:

  • Deeper nesting levels
  • Multiple concurrent changes at different levels
  • Changes in different branches of the structure

Line range hint 1-351: Address test coverage decrease

The PR shows a 5.4% decrease in test coverage. Consider adding tests for:

  • Error conditions and exception handling
  • Race conditions in concurrent scenarios
  • Performance benchmarks for large data structures
test/shared_type/xml_element_test.exs (2)

155-169: Add validation for XML-specific edge cases

While the basic observation test is good, consider adding XML-specific scenarios:

  • Malformed XML attributes
  • Special characters in attributes
  • Empty elements

191-241: Enhance deep observation tests for XML

Consider adding these XML-specific scenarios:

  • Nested CDATA sections
  • Comments
  • Processing instructions
native/yex/src/event.rs (1)

393-437: Consider adding timeout handling for observe_deep

The deep observation implementation might benefit from timeout handling for large structures.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3a0106c and e52b92f.

📒 Files selected for processing (21)
  • lib/nif.ex (1 hunks)
  • lib/protocols/awareness.ex (1 hunks)
  • lib/shared_type/event.ex (1 hunks)
  • lib/shared_type/map.ex (1 hunks)
  • lib/shared_type/shared_type.ex (1 hunks)
  • native/yex/src/array.rs (2 hunks)
  • native/yex/src/atoms.rs (2 hunks)
  • native/yex/src/event.rs (1 hunks)
  • native/yex/src/lib.rs (1 hunks)
  • native/yex/src/map.rs (2 hunks)
  • native/yex/src/subscription.rs (1 hunks)
  • native/yex/src/text.rs (2 hunks)
  • native/yex/src/xml.rs (5 hunks)
  • native/yex/src/yinput.rs (2 hunks)
  • test/shared_type/array_test.exs (2 hunks)
  • test/shared_type/map_test.exs (2 hunks)
  • test/shared_type/shared_type_test.exs (1 hunks)
  • test/shared_type/text_test.exs (2 hunks)
  • test/shared_type/xml_element_test.exs (2 hunks)
  • test/shared_type/xml_fragment_test.exs (2 hunks)
  • test/shared_type/xml_text_test.exs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • native/yex/src/lib.rs
  • test/shared_type/shared_type_test.exs
  • lib/nif.ex
  • native/yex/src/text.rs
  • native/yex/src/subscription.rs
  • lib/protocols/awareness.ex
  • native/yex/src/map.rs
  • native/yex/src/yinput.rs
  • lib/shared_type/map.ex
  • native/yex/src/atoms.rs
  • native/yex/src/xml.rs
  • native/yex/src/array.rs
🔇 Additional comments (6)
lib/shared_type/event.ex (1)

48-67: Consider refactoring TextEvent and XmlTextEvent to reduce duplication

These modules are nearly identical except for their target types. Consider using a shared implementation.

Also applies to: 92-111

lib/shared_type/shared_type.ex (1)

46-46: Consider alternatives to Process dictionary

Using Process.put/get for subscription management could lead to maintainability issues.

Also applies to: 91-91

test/shared_type/map_test.exs (1)

163-163: Incorrect shared type key used

The test retrieves a map using "text" as the key, which seems inconsistent with the test's purpose. This matches the pattern flagged in previous reviews.

-      map = Doc.get_map(doc, "text")
+      map = Doc.get_map(doc, "map")
native/yex/src/event.rs (3)

96-127: ⚠️ Potential issue

Replace unwrap() with proper error handling in NifYArrayChange

The use of unwrap() can lead to panics. Consider using the ? operator or proper error handling.

- map = map.map_put(atoms::insert(), content).unwrap();
+ map = map.map_put(atoms::insert(), content)?;

168-209: ⚠️ Potential issue

Replace unwrap() with proper error handling in NifYTextDelta

Similar to the previous issue, replace unwrap() calls with proper error handling.

- map = map.map_put(atoms::insert(), insert).unwrap();
+ map = map.map_put(atoms::insert(), insert)?;

243-284: ⚠️ Potential issue

Replace unwrap() with proper error handling in NifYMapChange

Continue the pattern of proper error handling here as well.

- .map_put(atoms::action(), atoms::add()).unwrap()
+ .map_put(atoms::action(), atoms::add())?

lib/shared_type/event.ex Show resolved Hide resolved
lib/shared_type/shared_type.ex Show resolved Hide resolved
test/shared_type/xml_text_test.exs Show resolved Hide resolved
@satoren satoren merged commit 23d9ab7 into main Dec 5, 2024
3 checks passed
@satoren satoren deleted the observe branch December 5, 2024 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants