Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-03-03 17:43:04 -05:00
parent a405b38507
commit 5e28d0bd8c
14 changed files with 3535 additions and 8 deletions
@@ -0,0 +1,160 @@
# Data Model: Safety, Reliability & Idiomatic Refactor
**Feature Branch**: `001-safety-reliability-refactor`
**Date**: 2026-03-03
## Entities
### HostsFile
Represents the system hosts file as parsed content sections.
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Path | `string` | Absolute path to the hosts file | OS-specific; determined by platform package |
| OriginalContent | `[]string` | Raw lines from the file as read | Immutable after read; used for change detection |
| PrefixContent | `[]string` | Lines before the managed block start marker | Preserved verbatim on write |
| ManagedContent | `[]string` | Lines between start and end markers (exclusive) | Modified by add/delete operations |
| PostfixContent | `[]string` | Lines after the managed block end marker | Preserved verbatim on write |
| HasManagedBlock | `bool` | Whether a valid managed block was found | `true` if both start and end markers present in order |
**Validation Rules:**
- If start marker is present, end marker must also be present (and vice versa).
- End marker must appear after start marker.
- Duplicate start or end markers are not permitted.
- Start marker at line index 0 is a valid position (must use sentinel, not zero-check).
**State Transitions:**
```
[File Read] → Parse → [No Managed Block] → Add entries → [Has Managed Block] → Write
→ [Has Managed Block] → Add/Delete entries → [Updated Managed Block] → Write
→ [Corrupt Block] → ERROR (refuse to modify)
```
### DNSEntry
A single IP-to-hostname mapping in the hosts file.
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| IP | `string` | IPv4 address | Must be a valid IPv4 address; result of DNS resolution |
| Hostname | `string` | Fully qualified domain name | As provided by user (without trailing dot) |
**String Representation:** `<IP>\t<Hostname>` (tab-separated, one per line in hosts file)
**Validation Rules:**
- IP must be non-empty and a valid IPv4 address.
- Hostname must be non-empty and not contain whitespace.
- Entries are deduplicated by their full string representation (IP + hostname pair).
### ManagedBlock
The delimited section of the hosts file owned by this tool.
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| StartMarker | `string` | Comment line marking block start | `# DNSHelper <<-> START CONFIG` (constant) |
| EndMarker | `string` | Comment line marking block end | `# DNSHelper <->> END CONFIG` (constant) |
| Entries | `[]DNSEntry` | DNS entries within the block | Zero or more; deduplicated |
**Rules:**
- The managed block is only written if there are entries to include.
- When all entries are removed (delete all), the entire block (markers included) is removed.
- The block is always written as: start marker, entries (one per line), end marker.
### ResolveResult
The outcome of resolving a single hostname against a DNS server.
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Hostname | `string` | The hostname that was queried | As provided by user |
| IPs | `[]string` | Resolved IPv4 addresses | Deduplicated; empty if resolution failed |
| Error | `error` | Resolution error, if any | Non-nil on failure (timeout, NXDOMAIN, etc.) |
**State Transitions:**
```
[Query Sent] → A records returned → [Success: IPs populated]
→ CNAME returned → Follow chain (max 10 depth) → [Success or Error]
→ NXDOMAIN / timeout / error → [Failure: Error populated]
```
### BackupFile
A temporary safety copy of the hosts file created before modification.
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Path | `string` | Absolute path to backup file | In executable's directory |
| SourcePath | `string` | Path of the file that was backed up | The hosts file path |
| Timestamp | `string` | Date stamp portion of filename | Format: `YYYYMMDD` |
| RandomSuffix | `string` | Short random string for uniqueness | 4 hex characters (e.g., `a7f3`) |
**Naming Convention:** `hosts.bak.<Timestamp>-<RandomSuffix>` (e.g., `hosts.bak.20260303-a7f3`)
**Lifecycle:**
```
[Pre-Write] → Create backup copy → [Write Success] → Delete backup
→ [Write Failure] → Original intact, clean up backup
```
### LockFile
A file-based mutex for serializing concurrent access.
| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Path | `string` | Absolute path to lock file | In executable's directory; `.dns-helper.lock` |
| PID | `int` | Process ID of the lock holder | Written for debugging; not used for staleness |
| CreatedAt | `time.Time` | When the lock was acquired | Approximated by file ModTime |
**Parameters:**
- Stale timeout: 2 minutes
- Retry interval: 200ms
- Max wait: 2 seconds (10 retries)
**Lifecycle:**
```
[Pre-Lock] → Attempt create (O_EXCL) → [Acquired] → Hold during R/M/W → [Release: delete]
→ [Exists, not stale] → Retry (up to 2s) → [Acquired or Error]
→ [Exists, stale] → Break stale → Retry → [Acquired or Error]
```
## Relationships
```
CLI (main.go)
├── validates input (host, server, subcommand)
├── calls Resolver to get ResolveResult[] (before lock)
├── acquires LockFile
├── reads HostsFile → parses ManagedBlock
├── modifies ManagedBlock (add DNSEntry[] / delete by hostname)
├── creates BackupFile
├── writes HostsFile atomically (temp + rename)
├── deletes BackupFile on success
└── releases LockFile
```
## Operation Flows
### Add Flow
1. Validate: `-host` and `-server` required and non-empty.
2. Resolve: For each hostname, query DNS server → collect `ResolveResult[]`.
3. Lock: Acquire lock file.
4. Read: Read hosts file → parse into sections.
5. Modify: Remove existing entries for the specified hostnames from managed content. Add new `DNSEntry[]` from resolve results.
6. Deduplicate: Remove duplicate entries by full line content.
7. Backup: Copy current hosts file to backup location.
8. Write: Assemble full content (prefix + managed block + postfix) → write to temp file → rename over hosts file.
9. Cleanup: Delete backup on success. Release lock.
10. Report: Print summary (entries added/updated). Exit non-zero if any hostname failed to resolve.
### Delete Flow
1. Validate: `-host` required and non-empty, OR `all` keyword.
2. Lock: Acquire lock file.
3. Read: Read hosts file → parse into sections.
4. Modify: If specific hosts — remove matching lines from managed content. If `all` — clear managed content entirely (block markers will not be written).
5. Backup: Copy current hosts file to backup location.
6. Write: Assemble full content → write to temp file → rename.
7. Cleanup: Delete backup on success. Release lock.
8. Report: Print summary (entries removed). Report if no entries were found.