laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use crate::{
  Uri,
  fs::{
    FileSystem,
    UriPath,
    physical::PhysicalFileSystem,
  },
};

/// Test that file Uris are properly handled in laburnum-fs
///
/// According to Uri standards (RFC 8089), the correct format for local file
/// Uris is: `file:///path/to/file` (triple slash) or `file://localhost/path/to/file`
///
/// The triple slash format has significance:
/// - scheme: `file:`
/// - authority: `//` (empty or "localhost" for local files)
/// - path: `/path/to/file` (starts with slash)
///
/// Note: `file://path` (double slash followed by a non-localhost hostname) is only valid
/// for remote file systems, not local paths. For local files, we must use
/// triple slash.
#[test]
fn test_file_uri_format_handling() {
  // Create a temporary directory and test file
  let temp_dir = tempfile::Builder::new()
    .prefix("laburnum-fs-test")
    .tempdir()
    .expect("Failed to create temp directory");

  let temp_path = temp_dir.path();
  let test_file_path = temp_path.join("test.txt");
  let test_content = "test content";

  // Write a test file
  std::fs::write(&test_file_path, test_content)
    .expect("Failed to write test file");

  // Create standard file Uri using the API - this will be the canonical format
  let canonical_uri = Uri::from_file_path(&test_file_path).unwrap();

  // Create a localhost version which should be equivalent
  let localhost_uri = Uri::parse(
    &canonical_uri
      .as_str()
      .replace("file:///", "file://localhost/"),
  )
  .unwrap();

  // Log for debugging
  println!("Canonical uri: {canonical_uri}");
  println!("Localhost uri: {localhost_uri}");

  // Verify the Uris have the correct scheme
  assert_eq!(
    canonical_uri.scheme(),
    fluent_uri::component::Scheme::new_or_panic("file")
  );
  assert_eq!(
    localhost_uri.scheme(),
    fluent_uri::component::Scheme::new_or_panic("file")
  );

  // Create filesystems with both Uri formats
  let fs_from_canonical = PhysicalFileSystem::new(canonical_uri.clone())
    .expect("Failed to create filesystem from canonical Uri");

  let fs_from_localhost = PhysicalFileSystem::new(localhost_uri.clone())
    .expect("Failed to create filesystem from localhost Uri");

  // Read content using both Uris with both filesystems
  let content1 = fs_from_canonical
    .read(&canonical_uri)
    .expect("Failed to read with canonical Uri");

  let content2 = fs_from_localhost
    .read(&localhost_uri)
    .expect("Failed to read with localhost Uri");

  // Cross-read (canonical FS with localhost Uri and vice versa)
  let content3 = fs_from_canonical
    .read(&localhost_uri)
    .expect("Failed to read localhost Uri with canonical filesystem");

  let content4 = fs_from_localhost
    .read(&canonical_uri)
    .expect("Failed to read canonical Uri with localhost filesystem");

  // Verify all content reads match
  assert_eq!(content1, test_content.as_bytes());
  assert_eq!(content2, test_content.as_bytes());
  assert_eq!(content3, test_content.as_bytes());
  assert_eq!(content4, test_content.as_bytes());
}

#[test]
fn test_uri_path_operations_with_different_formats() {
  // Test with standard file Uri format (three slashes)
  let standard_uri = Uri::parse("file:///path/to/file.txt").unwrap();
  let standard_parent = standard_uri.parent().unwrap();
  assert!(standard_parent.as_str().contains("/path/to/"));

  // Test with localhost format
  let localhost_uri = Uri::parse("file://localhost/path/to/file.txt").unwrap();
  let localhost_parent = localhost_uri.parent().unwrap();
  assert!(localhost_parent.as_str().contains("/path/to/"));

  // Test file name extraction - should be the same for both formats
  assert_eq!(standard_uri.file_name().unwrap(), "file.txt");
  assert_eq!(localhost_uri.file_name().unwrap(), "file.txt");

  // Test path joining
  let standard_joined = standard_uri
    .parent()
    .unwrap()
    .join_path("other.txt")
    .unwrap();
  assert!(standard_joined.as_str().contains("/path/to/other.txt"));

  let localhost_joined = localhost_uri
    .parent()
    .unwrap()
    .join_path("other.txt")
    .unwrap();
  assert!(localhost_joined.as_str().contains("/path/to/other.txt"));

  // Test normalization
  let uri_with_dots_standard =
    Uri::parse("file:///path/./to/../to/file.txt").unwrap();
  let standard_normalized = uri_with_dots_standard.normalize();
  // Verify path segments are normalized correctly
  assert!(standard_normalized.as_str().contains("/path/to/file.txt"));
  assert!(!standard_normalized.as_str().contains("/./"));
  assert!(!standard_normalized.as_str().contains("/../"));

  let uri_with_dots_localhost =
    Uri::parse("file://localhost/path/./to/../to/file.txt").unwrap();
  let localhost_normalized = uri_with_dots_localhost.normalize();
  assert!(localhost_normalized.as_str().contains("/path/to/file.txt"));
  assert!(!localhost_normalized.as_str().contains("/./"));
  assert!(!localhost_normalized.as_str().contains("/../"));
}

#[test]
fn test_uri_to_path_conversion() {
  // Create a temporary directory for a real file
  let temp_dir = tempfile::Builder::new()
    .prefix("laburnum-fs-test")
    .tempdir()
    .expect("Failed to create temp directory");

  let test_path = temp_dir.path().join("test.txt");
  std::fs::write(&test_path, "test").expect("Failed to write test file");

  // Standard way to create a file Uri (should be canonical for the platform)
  let canonical_uri = Uri::from_file_path(&test_path).unwrap();
  println!("Canonical Uri from path: {canonical_uri}");

  // Create localhost format that should be equivalent
  let localhost_uri_str = canonical_uri
    .as_str()
    .replace("file:///", "file://localhost/");
  let localhost_uri = Uri::parse(&localhost_uri_str).unwrap();

  println!("Canonical uri: {canonical_uri}");
  println!("Localhost uri: {localhost_uri}");

  // Convert back to paths
  let path_from_canonical = canonical_uri.to_file_path().unwrap();
  let path_from_localhost = localhost_uri.to_file_path().unwrap();

  // Both should convert to the same path, matching our original
  assert_eq!(path_from_canonical, test_path);
  assert_eq!(path_from_localhost, test_path);

  // Create PhysicalFileSystems with both formats
  let fs_canonical = PhysicalFileSystem::new(canonical_uri.clone()).unwrap();
  let fs_localhost = PhysicalFileSystem::new(localhost_uri.clone()).unwrap();

  // Test read operations to verify both formats work for creating filesystems
  assert!(fs_canonical.read(&canonical_uri).is_ok());
  assert!(fs_localhost.read(&localhost_uri).is_ok());
  assert!(fs_canonical.read(&localhost_uri).is_ok());
  assert!(fs_localhost.read(&canonical_uri).is_ok());
}

#[test]
fn test_file_uri_localhost() {
  // According to the Uri spec, file:///path is equivalent to file://localhost/path

  // Create a test file
  let temp_dir = tempfile::Builder::new()
    .prefix("laburnum-fs-test")
    .tempdir()
    .expect("Failed to create temp directory");

  let test_path = temp_dir.path().join("test.txt");
  let test_content = "localhost test";
  std::fs::write(&test_path, test_content).expect("Failed to write test file");

  // Create canonical Uri
  let canonical_uri = Uri::from_file_path(&test_path).unwrap();

  // Create localhost Uri
  let localhost_str = if canonical_uri.as_str().starts_with("file:///") {
    canonical_uri
      .as_str()
      .replace("file:///", "file://localhost/")
  } else {
    canonical_uri
      .as_str()
      .replace("file://", "file://localhost/")
  };
  let localhost_uri = Uri::parse(&localhost_str).unwrap();

  println!("Canonical uri: {canonical_uri}");
  println!("Localhost uri: {localhost_uri}");

  // Both should convert back to the same path
  let path1 = canonical_uri.to_file_path().unwrap();
  let path2 = localhost_uri.to_file_path().unwrap();

  assert_eq!(path1, test_path);
  assert_eq!(path2, test_path);

  // Create filesystems and verify they can read the file with both Uri formats
  let fs1 = PhysicalFileSystem::new(canonical_uri.clone()).unwrap();
  let content1 = fs1.read(&canonical_uri).unwrap();
  let content2 = fs1.read(&localhost_uri).unwrap();

  assert_eq!(content1, test_content.as_bytes());
  assert_eq!(content2, test_content.as_bytes());
}