Repository navigation
Keep config permissions and hide config text in ts prebid client errors #1291
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
base: main
Are you sure you want to change the base?
Changes from all commits
1f9ab09
1cf83d9
8b8445e
09b1e21
d61637a
46608aa
59f7c69
7f1501c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,3 +36,48 @@ pub enum CliError { | |||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| impl Error for CliError {} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Formats a TOML error's location without the parser's message. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// Parser and deserializer messages quote the offending source line or value, | ||||||||||||||||||||||||||||||||
| /// which can hold data an operator keeps private, so only the 1-based line and | ||||||||||||||||||||||||||||||||
| /// column of the error within `source` are reported. | ||||||||||||||||||||||||||||||||
| pub(crate) fn toml_error_location(source: &str, span: Option<core::ops::Range<usize>>) -> String { | ||||||||||||||||||||||||||||||||
| let Some(start) = span.map(|span| span.start) else { | ||||||||||||||||||||||||||||||||
| return String::new(); | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| let before = source.get(..start).unwrap_or(source); | ||||||||||||||||||||||||||||||||
| let line = before.matches('\n').count() + 1; | ||||||||||||||||||||||||||||||||
| let column = before | ||||||||||||||||||||||||||||||||
| .rsplit('\n') | ||||||||||||||||||||||||||||||||
| .next() | ||||||||||||||||||||||||||||||||
| .map_or(0, |tail| tail.chars().count()) | ||||||||||||||||||||||||||||||||
| + 1; | ||||||||||||||||||||||||||||||||
| format!(" at line {line}, column {column}") | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[cfg(test)] | ||||||||||||||||||||||||||||||||
| mod tests { | ||||||||||||||||||||||||||||||||
| use super::*; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||
| fn toml_error_location_reports_line_and_column() { | ||||||||||||||||||||||||||||||||
| let source = "a = 1\nb = \"x\n"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let location = toml_error_location(source, Some(10..11)); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||
| location, " at line 2, column 5", | ||||||||||||||||||||||||||||||||
| "should map offset to line and column" | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⛏ nitpick — Nothing pins that the column counts characters. Swapping
Suggested change
Verified in a scratch worktree: passes at this head, fails with the byte-count mutation, and |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||
| fn toml_error_location_is_empty_without_span() { | ||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||
| toml_error_location("a = 1", None), | ||||||||||||||||||||||||||||||||
| "", | ||||||||||||||||||||||||||||||||
| "should omit location without span" | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| //! Filesystem helpers shared by `ts` subcommands. | ||
|
|
||
| use std::fs; | ||
| use std::io::Write as _; | ||
| use std::path::Path; | ||
|
|
||
| /// Writes `contents` to `path` atomically: a same-directory temp file is | ||
| /// written and fsynced, then renamed over the target, then the directory entry | ||
| /// is fsynced. | ||
| /// | ||
| /// A plain `fs::write` truncates the destination before writing, so a full disk | ||
| /// or an interrupted run would leave the target empty or half-written. `rename` | ||
| /// within a directory is atomic, so a reader sees either the old file or the | ||
| /// complete new one. If any step fails, the temp file is removed on drop. | ||
| /// | ||
| /// The temp file is created 0600. When the target already exists, its mode | ||
| /// bits are carried onto the replacement; a new file stays 0600. Because the | ||
| /// target is replaced by rename, its owner, group, ACLs and hard links are not | ||
| /// preserved, and a symlink at `path` is replaced by a regular file rather than | ||
| /// updating the file it points to. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns the underlying I/O error when the temp file cannot be created, | ||
| /// written, synced, or renamed over `path`. | ||
| pub(crate) fn write_file_atomically(path: &Path, contents: &str) -> std::io::Result<()> { | ||
|
dhruv8sh marked this conversation as resolved.
|
||
| let directory = path | ||
| .parent() | ||
| .filter(|parent| !parent.as_os_str().is_empty()) | ||
| .unwrap_or_else(|| Path::new(".")); | ||
|
|
||
| let mut temp = tempfile::Builder::new() | ||
| .prefix(".ts-write-") | ||
| .tempfile_in(directory)?; | ||
| temp.write_all(contents.as_bytes())?; | ||
| temp.as_file().sync_all()?; | ||
| if let Ok(metadata) = fs::metadata(path) { | ||
| temp.as_file().set_permissions(metadata.permissions())?; | ||
| } | ||
| temp.persist(path).map_err(|error| error.error)?; | ||
|
dhruv8sh marked this conversation as resolved.
|
||
|
|
||
| // Best-effort durability for the rename itself. Opening a directory handle | ||
| // is not portable (Windows rejects it), and the content is already safely | ||
| // on disk either way, so a failure here is not worth failing the command. | ||
| let _ = fs::File::open(directory).and_then(|handle| handle.sync_all()); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn write_file_atomically_removes_temp_file_when_rename_fails() { | ||
| let temp = tempfile::TempDir::new().expect("should create temp dir"); | ||
| let target = temp.path().join("occupied"); | ||
| fs::create_dir(&target).expect("should create directory at target"); | ||
| fs::write(target.join("child"), "x").expect("should populate target directory"); | ||
|
|
||
| write_file_atomically(&target, "contents") | ||
| .expect_err("should fail to rename over a non-empty directory"); | ||
|
|
||
| let entries: Vec<_> = fs::read_dir(temp.path()) | ||
| .expect("should list temp dir") | ||
| .map(|entry| entry.expect("should read entry").file_name()) | ||
| .collect(); | ||
| assert_eq!( | ||
| entries, | ||
| vec![std::ffi::OsString::from("occupied")], | ||
| "should leave no temp file behind after a failed write" | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏ nitpick —
splice_creative_slotsis the one redacted parse site without a no-echo test. Restoring: {error}to the message at line 630 passes all 352prebid_bundle,fs_util,errorandaudittests, and so does passing""instead ofexistingtotoml_error_location(the error then saysline 1, column 1). Same shape as the test above:Verified in a scratch worktree: passes at this head, fails under both mutations, and
cargo fmt --check,cargo clippy-cliand the fulltrusted-server-clisuite pass with it applied.