(&self, request: WorkspaceGlobRequest)
| 774 | #[async_trait] |
| 775 | impl WorkspaceSearch for S3WorkspaceBackend { |
| 776 | async fn glob(&self, request: WorkspaceGlobRequest) -> Result<WorkspaceGlobResult> { |
| 777 | validate_relative_pattern(&request.pattern, "glob pattern")?; |
| 778 | let pattern = glob::Pattern::new(&request.pattern) |
| 779 | .map_err(|e| anyhow!("Invalid glob pattern '{}': {}", request.pattern, e))?; |
| 780 | // The `glob` crate's `Pattern::matches` is more permissive than the |
| 781 | // filesystem walker behind `glob::glob` — `*` happily matches across |
| 782 | // `/`. To stay consistent with the local backend (where `*.rs` does |
| 783 | // NOT recurse into subdirectories), require an explicit `**` for |
| 784 | // tree-wide matches; otherwise skip any key containing `/`. |
| 785 | let recursive = request.pattern.contains("**"); |
| 786 | |
| 787 | let (entries, scan_truncated) = self |
| 788 | .list_recursive_under(&request.base, self.max_objects_scanned) |
| 789 | .await?; |
| 790 | if scan_truncated { |
| 791 | tracing::debug!( |
| 792 | "S3 glob scan truncated at {} objects under s3://{}/{}", |
| 793 | self.max_objects_scanned, |
| 794 | self.bucket, |
| 795 | self.list_prefix_for(&request.base) |
| 796 | ); |
| 797 | } |
| 798 | |
| 799 | let mut matches = Vec::new(); |
| 800 | for (rel, _size) in entries { |
| 801 | if !recursive && rel.contains('/') { |
| 802 | continue; |
| 803 | } |
| 804 | if pattern.matches(&rel) { |
| 805 | matches.push(join_workspace_path(&request.base, &rel)); |
| 806 | } |
| 807 | } |
| 808 | matches.sort_by(|a, b| a.as_str().cmp(b.as_str())); |
| 809 | Ok(WorkspaceGlobResult { matches }) |
| 810 | } |
| 811 | |
| 812 | async fn grep(&self, request: WorkspaceGrepRequest) -> Result<WorkspaceGrepResult> { |
| 813 | use futures::stream::StreamExt; |
nothing calls this directly
no test coverage detected