krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

017785c4ce07cfe026a56d72963eb977fea06440

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-08-22T07:24:32Z

Build Inspection, Provenance and Failures straight from the snapshot

Grouping the parameters left both call sites spelling out the same 46 lines of
snapshot.x -> x copying, which is duplication SonarCloud was right to flag: the
two lists could drift apart and nothing would say so.

Each of those three groups is populated entirely from the snapshot, so they get
an init(snapshot:) and the call sites collapse to .init(snapshot: snapshot).
The other four groups genuinely differ between the two sites and stay explicit.

Declared in extensions so the structs keep their synthesized memberwise
initializers, which the tests and the remaining call sites use.
 DomainDig/DomainMonitoringService.swift | 55 ++--------------------
 DomainDig/DomainViewModel.swift         | 55 ++--------------------
 DomainDig/Models.swift                  | 83 +++++++++++++++++++++++++++++++++
 3 files changed, 89 insertions(+), 104 deletions(-)

diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift
index b902f75..e243b94 100644
--- a/DomainDig/DomainMonitoringService.swift
+++ b/DomainDig/DomainMonitoringService.swift
@@ -526,24 +526,7 @@ final class DomainMonitoringService {
                 trackedDomainID: trackedDomainID,
                 note: trackedDomains.first(where: { $0.id == trackedDomainID })?.note
             ),
-            inspection: HistoryEntry.Inspection(
-                dnsSections: snapshot.dnsSections,
-                sslInfo: snapshot.sslInfo,
-                httpHeaders: snapshot.httpHeaders,
-                reachabilityResults: snapshot.reachabilityResults,
-                ipGeolocation: snapshot.ipGeolocation,
-                emailSecurity: snapshot.emailSecurity,
-                mtaSts: snapshot.emailSecurity?.mtaSts,
-                ptrRecord: snapshot.ptrRecord,
-                redirectChain: snapshot.redirectChain,
-                subdomains: snapshot.subdomains,
-                extendedSubdomains: snapshot.extendedSubdomains,
-                dnsHistory: snapshot.dnsHistory,
-                portScanResults: snapshot.portScanResults,
-                hstsPreloaded: snapshot.hstsPreloaded,
-                availabilityResult: snapshot.availabilityResult,
-                suggestions: snapshot.suggestions
-            ),
+            inspection: .init(snapshot: snapshot),
             registration: HistoryEntry.Registration(
                 ownership: snapshot.ownership,
                 ownershipHistory: snapshot.ownershipHistory,
@@ -552,22 +535,7 @@ final class DomainMonitoringService {
             intelligence: HistoryEntry.Intelligence(
                 reputation: snapshot.reputation
             ),
-            provenance: HistoryEntry.Provenance(
-                appVersion: snapshot.appVersion,
-                resultSource: snapshot.resultSource,
-                dataSources: snapshot.dataSources,
-                provenanceBySection: snapshot.provenanceBySection,
-                availabilityConfidence: snapshot.availabilityConfidence,
-                ownershipConfidence: snapshot.ownershipConfidence,
-                subdomainConfidence: snapshot.subdomainConfidence,
-                emailSecurityConfidence: snapshot.emailSecurityConfidence,
-                geolocationConfidence: snapshot.geolocationConfidence,
-                isPartialSnapshot: snapshot.isPartialSnapshot,
-                validationIssues: snapshot.validationIssues,
-                resolverDisplayName: snapshot.resolverDisplayName,
-                resolverURLString: snapshot.resolverURLString,
-                totalLookupDurationMs: snapshot.totalLookupDurationMs
-            ),
+            provenance: .init(snapshot: snapshot),
             summary: HistoryEntry.Summary(
                 primaryIP: Self.primaryIPAddress(from: snapshot),
                 finalRedirectURL: snapshot.redirectChain.last?.url,
@@ -576,24 +544,7 @@ final class DomainMonitoringService {
                 httpGradeSummary: snapshot.httpSecurityGrade ?? snapshot.httpHeadersError,
                 changeSummary: changeSummary
             ),
-            failures: HistoryEntry.Failures(
-                errorDetails: snapshot.errorDetails,
-                sslError: snapshot.sslError,
-                httpHeadersError: snapshot.httpHeadersError,
-                reachabilityError: snapshot.reachabilityError,
-                ipGeolocationError: snapshot.ipGeolocationError,
-                emailSecurityError: snapshot.emailSecurityError,
-                ownershipError: snapshot.ownershipError,
-                ownershipHistoryError: snapshot.ownershipHistoryError,
-                ptrError: snapshot.ptrError,
-                redirectChainError: snapshot.redirectChainError,
-                subdomainsError: snapshot.subdomainsError,
-                extendedSubdomainsError: snapshot.extendedSubdomainsError,
-                dnsHistoryError: snapshot.dnsHistoryError,
-                domainPricingError: snapshot.domainPricingError,
-                reputationError: snapshot.reputationError,
-                portScanError: snapshot.portScanError
-            )
+            failures: .init(snapshot: snapshot)
         )
 
         history.insert(entry, at: 0)
diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift
index 258764a..5a444fc 100644
--- a/DomainDig/DomainViewModel.swift
+++ b/DomainDig/DomainViewModel.swift
@@ -1942,24 +1942,7 @@ final class DomainViewModel {
                 trackedDomainID: trackedDomainID,
                 note: currentHistoryEntry?.note
             ),
-            inspection: HistoryEntry.Inspection(
-                dnsSections: snapshot.dnsSections,
-                sslInfo: snapshot.sslInfo,
-                httpHeaders: snapshot.httpHeaders,
-                reachabilityResults: snapshot.reachabilityResults,
-                ipGeolocation: snapshot.ipGeolocation,
-                emailSecurity: snapshot.emailSecurity,
-                mtaSts: snapshot.emailSecurity?.mtaSts,
-                ptrRecord: snapshot.ptrRecord,
-                redirectChain: snapshot.redirectChain,
-                subdomains: snapshot.subdomains,
-                extendedSubdomains: snapshot.extendedSubdomains,
-                dnsHistory: snapshot.dnsHistory,
-                portScanResults: snapshot.portScanResults,
-                hstsPreloaded: snapshot.hstsPreloaded,
-                availabilityResult: snapshot.availabilityResult,
-                suggestions: snapshot.suggestions
-            ),
+            inspection: .init(snapshot: snapshot),
             registration: HistoryEntry.Registration(
                 ownership: snapshot.ownership,
                 ownershipHistory: snapshot.ownershipHistory,
@@ -1976,22 +1959,7 @@ final class DomainViewModel {
                 intelligenceTimeline: intelligence.timelineEvents,
                 reputation: snapshot.reputation
             ),
-            provenance: HistoryEntry.Provenance(
-                appVersion: snapshot.appVersion,
-                resultSource: snapshot.resultSource,
-                dataSources: snapshot.dataSources,
-                provenanceBySection: snapshot.provenanceBySection,
-                availabilityConfidence: snapshot.availabilityConfidence,
-                ownershipConfidence: snapshot.ownershipConfidence,
-                subdomainConfidence: snapshot.subdomainConfidence,
-                emailSecurityConfidence: snapshot.emailSecurityConfidence,
-                geolocationConfidence: snapshot.geolocationConfidence,
-                isPartialSnapshot: snapshot.isPartialSnapshot,
-                validationIssues: snapshot.validationIssues,
-                resolverDisplayName: snapshot.resolverDisplayName,
-                resolverURLString: snapshot.resolverURLString,
-                totalLookupDurationMs: snapshot.totalLookupDurationMs
-            ),
+            provenance: .init(snapshot: snapshot),
             summary: HistoryEntry.Summary(
                 primaryIP: Self.primaryIPAddress(from: snapshot),
                 finalRedirectURL: Self.finalRedirectTarget(from: snapshot),
@@ -2004,24 +1972,7 @@ final class DomainViewModel {
                 changeCount: domainDiff?.changeCount ?? changeSummary?.changedSections.count ?? 0,
                 severitySummary: changeSummary?.severity
             ),
-            failures: HistoryEntry.Failures(
-                errorDetails: snapshot.errorDetails,
-                sslError: snapshot.sslError,
-                httpHeadersError: snapshot.httpHeadersError,
-                reachabilityError: snapshot.reachabilityError,
-                ipGeolocationError: snapshot.ipGeolocationError,
-                emailSecurityError: snapshot.emailSecurityError,
-                ownershipError: snapshot.ownershipError,
-                ownershipHistoryError: snapshot.ownershipHistoryError,
-                ptrError: snapshot.ptrError,
-                redirectChainError: snapshot.redirectChainError,
-                subdomainsError: snapshot.subdomainsError,
-                extendedSubdomainsError: snapshot.extendedSubdomainsError,
-                dnsHistoryError: snapshot.dnsHistoryError,
-                domainPricingError: snapshot.domainPricingError,
-                reputationError: snapshot.reputationError,
-                portScanError: snapshot.portScanError
-            )
+            failures: .init(snapshot: snapshot)
         )
 
         if updateCurrentState {
diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift
index c81b8c0..a75957c 100644
--- a/DomainDig/Models.swift
+++ b/DomainDig/Models.swift
@@ -1267,6 +1267,86 @@ struct MonitoringPendingAlert: Codable, Identifiable, Equatable, Sendable {
     }
 }
 
+
+// Declared in an extension so the struct keeps its synthesized memberwise
+// initializer: adding one inside the body would suppress it.
+extension HistoryEntry.Inspection {
+    /// Every field here is copied straight off the snapshot, and both call
+    /// sites did exactly that. Naming it once keeps them from drifting.
+    init(snapshot: LookupSnapshot) {
+        self.init(
+            dnsSections: snapshot.dnsSections,
+            sslInfo: snapshot.sslInfo,
+            httpHeaders: snapshot.httpHeaders,
+            reachabilityResults: snapshot.reachabilityResults,
+            ipGeolocation: snapshot.ipGeolocation,
+            emailSecurity: snapshot.emailSecurity,
+            mtaSts: snapshot.emailSecurity?.mtaSts,
+            ptrRecord: snapshot.ptrRecord,
+            redirectChain: snapshot.redirectChain,
+            subdomains: snapshot.subdomains,
+            extendedSubdomains: snapshot.extendedSubdomains,
+            dnsHistory: snapshot.dnsHistory,
+            portScanResults: snapshot.portScanResults,
+            hstsPreloaded: snapshot.hstsPreloaded,
+            availabilityResult: snapshot.availabilityResult,
+            suggestions: snapshot.suggestions
+        )
+    }
+}
+
+// Declared in an extension so the struct keeps its synthesized memberwise
+// initializer: adding one inside the body would suppress it.
+extension HistoryEntry.Provenance {
+    /// Every field here is copied straight off the snapshot, and both call
+    /// sites did exactly that. Naming it once keeps them from drifting.
+    init(snapshot: LookupSnapshot) {
+        self.init(
+            appVersion: snapshot.appVersion,
+            resultSource: snapshot.resultSource,
+            dataSources: snapshot.dataSources,
+            provenanceBySection: snapshot.provenanceBySection,
+            availabilityConfidence: snapshot.availabilityConfidence,
+            ownershipConfidence: snapshot.ownershipConfidence,
+            subdomainConfidence: snapshot.subdomainConfidence,
+            emailSecurityConfidence: snapshot.emailSecurityConfidence,
+            geolocationConfidence: snapshot.geolocationConfidence,
+            isPartialSnapshot: snapshot.isPartialSnapshot,
+            validationIssues: snapshot.validationIssues,
+            resolverDisplayName: snapshot.resolverDisplayName,
+            resolverURLString: snapshot.resolverURLString,
+            totalLookupDurationMs: snapshot.totalLookupDurationMs
+        )
+    }
+}
+
+// Declared in an extension so the struct keeps its synthesized memberwise
+// initializer: adding one inside the body would suppress it.
+extension HistoryEntry.Failures {
+    /// Every field here is copied straight off the snapshot, and both call
+    /// sites did exactly that. Naming it once keeps them from drifting.
+    init(snapshot: LookupSnapshot) {
+        self.init(
+            errorDetails: snapshot.errorDetails,
+            sslError: snapshot.sslError,
+            httpHeadersError: snapshot.httpHeadersError,
+            reachabilityError: snapshot.reachabilityError,
+            ipGeolocationError: snapshot.ipGeolocationError,
+            emailSecurityError: snapshot.emailSecurityError,
+            ownershipError: snapshot.ownershipError,
+            ownershipHistoryError: snapshot.ownershipHistoryError,
+            ptrError: snapshot.ptrError,
+            redirectChainError: snapshot.redirectChainError,
+            subdomainsError: snapshot.subdomainsError,
+            extendedSubdomainsError: snapshot.extendedSubdomainsError,
+            dnsHistoryError: snapshot.dnsHistoryError,
+            domainPricingError: snapshot.domainPricingError,
+            reputationError: snapshot.reputationError,
+            portScanError: snapshot.portScanError
+        )
+    }
+}
+
 struct TrackedDomain: Codable, Identifiable, Equatable {
     let id: UUID
     var domain: String
@@ -2436,6 +2516,7 @@ struct HistoryEntry: Identifiable, Codable {
         var hstsPreloaded: Bool? = nil
         var availabilityResult: DomainAvailabilityResult? = nil
         var suggestions: [DomainSuggestionResult] = []
+
     }
 
     /// Who owns the domain and where it is hosted.
@@ -2474,6 +2555,7 @@ struct HistoryEntry: Identifiable, Codable {
         var resolverDisplayName: String
         var resolverURLString: String
         var totalLookupDurationMs: Int? = nil
+
     }
 
     /// Precomputed display values and change tracking.
@@ -2508,6 +2590,7 @@ struct HistoryEntry: Identifiable, Codable {
         var domainPricingError: String? = nil
         var reputationError: String? = nil
         var portScanError: String? = nil
+
     }
 
     init(identity: Identity,