Harden pincode lookups and retry postal http
All checks were successful
build-and-deploy / build-deploy (push) Successful in 33s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 33s
This commit is contained in:
@@ -22,29 +22,45 @@ class DataGovPincodeClient(
|
||||
fun resolve(pinCode: String): PincodeLookupResult {
|
||||
if (apiKey.isBlank()) return PincodeLookupResult(null, null, "NO_API_KEY", "data.gov.in", "Missing API key")
|
||||
return try {
|
||||
val url = UriComponentsBuilder.fromUriString(baseUrl)
|
||||
.queryParam("api-key", apiKey)
|
||||
.queryParam("format", "json")
|
||||
.queryParam("filters[pincode]", pinCode)
|
||||
.toUriString()
|
||||
val response = restTemplate.getForEntity(url, String::class.java)
|
||||
val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "data.gov.in")
|
||||
val resolved = parseCityState(body, pinCode)
|
||||
val status = if (resolved == null) "ZERO_RESULTS" else "OK"
|
||||
PincodeLookupResult(resolved, body, status, "data.gov.in")
|
||||
val first = fetch(pinCode)
|
||||
if (first.resolvedCityState != null) return first
|
||||
val second = fetch("${pinCode}.0")
|
||||
if (second.resolvedCityState != null) return second
|
||||
// Prefer more explicit mismatch status if records exist but don't match pin.
|
||||
if (first.status == "FILTER_MISMATCH") return first
|
||||
if (second.status == "FILTER_MISMATCH") return second
|
||||
first
|
||||
} catch (ex: Exception) {
|
||||
logger.warn("Data.gov.in lookup failed: {}", ex.message)
|
||||
PincodeLookupResult(null, null, "ERROR", "data.gov.in", ex.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCityState(body: String, pinCode: String): String? {
|
||||
private fun fetch(pinCodeValue: String): PincodeLookupResult {
|
||||
val url = UriComponentsBuilder.fromUriString(baseUrl)
|
||||
.queryParam("api-key", apiKey)
|
||||
.queryParam("format", "json")
|
||||
.queryParam("filters[pincode]", pinCodeValue)
|
||||
.toUriString()
|
||||
val response = restTemplate.getForEntity(url, String::class.java)
|
||||
val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "data.gov.in")
|
||||
val parsed = parseCityState(body, pinCodeValue)
|
||||
val status = when {
|
||||
parsed != null -> "OK"
|
||||
isFilterMismatch(body, pinCodeValue) -> "FILTER_MISMATCH"
|
||||
else -> "ZERO_RESULTS"
|
||||
}
|
||||
val error = if (status == "FILTER_MISMATCH") "Records did not match pin filter" else null
|
||||
return PincodeLookupResult(parsed, body, status, "data.gov.in", error)
|
||||
}
|
||||
|
||||
private fun parseCityState(body: String, pinCodeValue: String): String? {
|
||||
val root = objectMapper.readTree(body)
|
||||
val records = root.path("records")
|
||||
if (!records.isArray || records.isEmpty) return null
|
||||
val filtered = records.filter { record ->
|
||||
val recordPin = record.path("pincode").asText(null)
|
||||
recordPin?.trim() == pinCode
|
||||
recordPin?.trim() == pinCodeValue
|
||||
}
|
||||
if (filtered.isEmpty()) return null
|
||||
val chosen = chooseRecord(filtered) ?: return null
|
||||
@@ -61,6 +77,17 @@ class DataGovPincodeClient(
|
||||
return delivery ?: records.firstOrNull()
|
||||
}
|
||||
|
||||
private fun isFilterMismatch(body: String, pinCodeValue: String): Boolean {
|
||||
val root = objectMapper.readTree(body)
|
||||
val records = root.path("records")
|
||||
if (!records.isArray || records.isEmpty) return false
|
||||
val anyMatch = records.any { record ->
|
||||
val recordPin = record.path("pincode").asText(null)
|
||||
recordPin?.trim() == pinCodeValue
|
||||
}
|
||||
return !anyMatch
|
||||
}
|
||||
|
||||
private fun toTitleCase(value: String): String {
|
||||
return value.lowercase().split(Regex("\\s+")).joinToString(" ") { word ->
|
||||
word.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
|
||||
|
||||
@@ -19,21 +19,33 @@ class PostalPincodeClient(
|
||||
|
||||
fun resolve(pinCode: String): PincodeLookupResult {
|
||||
return try {
|
||||
val url = UriComponentsBuilder.fromUriString(baseUrl)
|
||||
.path("/pincode/{pin}")
|
||||
.buildAndExpand(pinCode)
|
||||
.toUriString()
|
||||
val response = restTemplate.getForEntity(url, String::class.java)
|
||||
val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "postalpincode.in")
|
||||
val resolved = parseCityState(body)
|
||||
val status = if (resolved == null) "ZERO_RESULTS" else "OK"
|
||||
PincodeLookupResult(resolved, body, status, "postalpincode.in")
|
||||
val first = fetch(baseUrl, pinCode)
|
||||
if (first.resolvedCityState != null) return first
|
||||
if (first.status == "ERROR" && baseUrl.startsWith("https://")) {
|
||||
val httpUrl = baseUrl.replaceFirst("https://", "http://")
|
||||
val second = fetch(httpUrl, pinCode)
|
||||
if (second.resolvedCityState != null) return second
|
||||
return second
|
||||
}
|
||||
first
|
||||
} catch (ex: Exception) {
|
||||
logger.warn("Postalpincode lookup failed: {}", ex.message)
|
||||
PincodeLookupResult(null, null, "ERROR", "postalpincode.in", ex.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetch(base: String, pinCode: String): PincodeLookupResult {
|
||||
val url = UriComponentsBuilder.fromUriString(base)
|
||||
.path("/pincode/{pin}")
|
||||
.buildAndExpand(pinCode)
|
||||
.toUriString()
|
||||
val response = restTemplate.getForEntity(url, String::class.java)
|
||||
val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "postalpincode.in")
|
||||
val resolved = parseCityState(body)
|
||||
val status = if (resolved == null) "ZERO_RESULTS" else "OK"
|
||||
return PincodeLookupResult(resolved, body, status, "postalpincode.in")
|
||||
}
|
||||
|
||||
private fun parseCityState(body: String): String? {
|
||||
val root = objectMapper.readTree(body)
|
||||
if (!root.isArray || root.isEmpty) return null
|
||||
|
||||
Reference in New Issue
Block a user