Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user