app-json-settings 2.7.0

Tiny typed JSON settings persistence for Rust applications.
Documentation
# Save behavior

The Rust snippets on this page are illustrative fragments, not compiled or
run by CI — see [Testing guide](testing.md#verification-boundary).

`save()` serializes the complete settings value to JSON in memory and then
writes the resulting JSON to the configured settings path.

## Default: atomic save

Since 2.3.0, the default save mode is `SaveMode::Atomic`.

```rust
use app_json_settings::{ConfigManager, SaveMode};

let manager = ConfigManager::<Settings>::for_app("my-app")?;
assert_eq!(manager.save_mode(), SaveMode::Atomic);
```

Atomic save uses this sequence:

1. Serialize the settings value fully in memory.
2. Create the settings directory if needed.
3. Write JSON to a unique temporary file in the same directory.
4. Flush the temporary file.
5. Replace the destination path with the temporary file.
6. Best-effort sync the parent directory where supported.
7. Best-effort remove the temporary file if an error occurs before replacement.

This avoids exposing a partially written final settings file if the process stops
while writing the new JSON content.

## Permissions on Unix

Since 2.5.0, atomic save preserves the existing settings file's permission
bits on Unix, and the temporary file is created owner-only (`0600`) so its
content is never briefly readable by other local users while it is being
written.

* If the target file already exists, its mode is applied to the temporary
  file before the replace step.
* If no target file exists yet, the new file keeps the `0600` it was created
  with.
* Applying the target's mode is best-effort and fail-secure: if reading or
  applying the mode fails (for example, on a filesystem that does not model
  permission bits, such as FAT or some network mounts), the file stays at
  `0600` rather than falling back to a more permissive default. The result is
  never more permissive than intended, only possibly more restrictive.
* **Group-shared caveat.** A settings file under a shared `with_root_dir()`
  path is created owner-only on first save. This only bites once — running
  `chmod g+r` on the file after creation fixes it permanently, because every
  later save preserves whatever mode the file already has.

This does not make the crate a secret store. Applications with real
secret-handling requirements should use a platform keychain.

## Direct save

Direct save is still available:

```rust
let manager = ConfigManager::<Settings>::for_app("my-app")?
    .with_direct_save();
```

or:

```rust
let manager = ConfigManager::<Settings>::for_app("my-app")?
    .with_save_mode(SaveMode::Direct);
```

Direct save writes directly to the final file path. This matches the 2.2.0
behavior and may be useful for debugging, unusual filesystems, or applications
that intentionally want simple overwrite semantics.

## Platform notes

On Unix-like platforms, atomic replacement uses `rename` with the temporary file
in the same directory as the final file.

On Windows, atomic replacement uses a small internal `MoveFileExW` wrapper with
replace-existing and write-through flags. This avoids adding the `windows` crate
to the default dependency set.

On other targets, replacement of an existing file is not claimed as atomic. Such
targets should use `SaveMode::Direct` or receive a target-specific replacement
implementation before claiming strong atomic replacement behavior.

## Limits

Atomic save is not journaling. It does not guarantee recovery from every storage
failure, filesystem bug, or hardware failure. It is a pragmatic reliability
improvement for the common settings-file case.