Skip to content

Commit 06a33af

Browse files
authored
Merge pull request #174 from RADAR-base/fix/oura-requests
Use safe null-aware access for Oura data fields
2 parents 49ee00c + 0ca584c commit 06a33af

File tree

12 files changed

+130
-97
lines changed

12 files changed

+130
-97
lines changed

kafka-connect-oura-source/src/main/java/org/radarbase/connect/rest/oura/user/OuraServiceUserRepository.kt

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -251,25 +251,53 @@ class OuraServiceUserRepository : OuraUserRepository() {
251251
crossinline builder: HttpRequestBuilder.() -> Unit,
252252
): T =
253253
withContext(Dispatchers.IO) {
254+
val requestBuilder = HttpRequestBuilder()
255+
builder(requestBuilder)
256+
logger.info("Making HTTP request: ${requestBuilder.method} ${requestBuilder.url}")
257+
254258
val response = client.request(builder)
259+
logger.info("Response status: ${response.status}")
255260
val contentLength = response.contentLength()
256-
val hasBody = contentLength != null && contentLength > 0
261+
val transferEncoding = response.headers["Transfer-Encoding"]
262+
val hasBody = (contentLength != null && contentLength > 0) ||
263+
(transferEncoding != null && transferEncoding.contains("chunked"))
264+
val responseBody = try {
265+
response.bodyAsText()
266+
} catch (e: Exception) {
267+
"Error reading body: ${e.message}"
268+
}
269+
257270
if (response.status == HttpStatusCode.NotFound) {
271+
logger.error("HTTP 404 Not Found: ${response.request.url}")
258272
throw NoSuchElementException("URL " + response.request.url + " does not exist")
259-
} else if (!response.status.isSuccess() || !hasBody) {
260-
val message =
261-
buildString {
262-
append("Failed to make request (HTTP status code ")
263-
append(response.status)
264-
append(')')
265-
if (hasBody) {
266-
append(": ")
267-
append(response.bodyAsText())
268-
}
269-
}
273+
} else if (!response.status.isSuccess()) {
274+
val message = "HTTP ${response.status.value} error: $responseBody"
275+
logger.error(message)
270276
throw HttpResponseException(message, response.status.value)
277+
} else if (!hasBody) {
278+
logger.warn(
279+
"HTTP ${response.status.value} OK but no body content. Returning empty result.",
280+
)
281+
// Handle successful responses with empty body
282+
@Suppress("UNCHECKED_CAST")
283+
return@withContext when (T::class) {
284+
String::class -> "" as T
285+
List::class -> emptyList<Any>() as T
286+
else -> mapper.readValue<T>("{}")
287+
}
288+
}
289+
290+
try {
291+
val result = mapper.readValue<T>(responseBody)
292+
logger.info("Successfully parsed response as ${T::class.simpleName}")
293+
result
294+
} catch (e: Exception) {
295+
logger.error(
296+
"Failed to parse response body as ${T::class.simpleName}: ${e.message}",
297+
)
298+
logger.error("Response body that failed to parse: $responseBody")
299+
throw e
271300
}
272-
mapper.readValue<T>(response.bodyAsText())
273301
}
274302

275303
companion object {

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraDailyActivityConverter.kt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ class OuraDailyActivityConverter(
3636
return OuraDailyActivity.newBuilder().apply {
3737
time = startTime.toEpochMilli() / 1000.0
3838
timeReceived = System.currentTimeMillis() / 1000.0
39-
id = data.get("id").textValue()
40-
activeCalories = data.get("active_calories").intValue()
39+
id = data.get("id")?.textValue()
40+
activeCalories = data.get("active_calories")?.intValue()
4141
contributorMeetDailyTargets =
4242
data.get("contributors")?.get("meet_daily_targets")?.intValue()
4343
contributorMoveEveryHour = data.get("contributors")?.get("move_every_hour")?.intValue()
@@ -46,20 +46,20 @@ class OuraDailyActivityConverter(
4646
contributorTrainingFrequency =
4747
data.get("contributors")?.get("training_frequency")?.intValue()
4848
contributorTrainingVolume = data.get("contributors")?.get("training_volume")?.intValue()
49-
equivalentWalkingDistance = data.get("equivalent_walking_distance").intValue()
50-
highActivityMetMinutes = data.get("high_activity_met_minutes").intValue()
51-
highActivityTime = data.get("high_activity_time").intValue()
52-
inactivityAlerts = data.get("inactivity_alerts").intValue()
53-
lowActivityMetMinutes = data.get("low_activity_met_minutes").intValue()
54-
lowActivityTime = data.get("low_activity_time").intValue()
55-
mediumActivityMetMinutes = data.get("medium_activity_met_minutes").intValue()
56-
mediumActivityTime = data.get("medium_activity_time").intValue()
57-
metersToTarget = data.get("meters_to_target").intValue()
58-
nonWearTime = data.get("non_wear_time").intValue()
59-
restingTime = data.get("resting_time").intValue()
60-
sedentaryMetMinutes = data.get("sedentary_met_minutes").intValue()
61-
sedentaryTime = data.get("sedentary_time").intValue()
62-
steps = data.get("steps").intValue()
49+
equivalentWalkingDistance = data.get("equivalent_walking_distance")?.intValue()
50+
highActivityMetMinutes = data.get("high_activity_met_minutes")?.intValue()
51+
highActivityTime = data.get("high_activity_time")?.intValue()
52+
inactivityAlerts = data.get("inactivity_alerts")?.intValue()
53+
lowActivityMetMinutes = data.get("low_activity_met_minutes")?.intValue()
54+
lowActivityTime = data.get("low_activity_time")?.intValue()
55+
mediumActivityMetMinutes = data.get("medium_activity_met_minutes")?.intValue()
56+
mediumActivityTime = data.get("medium_activity_time")?.intValue()
57+
metersToTarget = data.get("meters_to_target")?.intValue()
58+
nonWearTime = data.get("non_wear_time")?.intValue()
59+
restingTime = data.get("resting_time")?.intValue()
60+
sedentaryMetMinutes = data.get("sedentary_met_minutes")?.intValue()
61+
sedentaryTime = data.get("sedentary_time")?.intValue()
62+
steps = data.get("steps")?.intValue()
6363
targetCalories = data.get("target_calories").intValue()
6464
targetMeters = data.get("target_meters").intValue()
6565
totalCalories = data.get("total_calories").intValue()

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraDailyReadinessConverter.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,24 @@ class OuraDailyReadinessConverter(
3636
return OuraDailyReadiness.newBuilder().apply {
3737
time = startTime.toEpochMilli() / 1000.0
3838
timeReceived = System.currentTimeMillis() / 1000.0
39-
id = data.get("id").textValue()
39+
id = data.get("id")?.textValue()
4040
contributorActivityBalance =
4141
data.get("contributors")?.get("activity_balance")?.intValue()
4242
contributorBodyTemperature =
4343
data.get("contributors")?.get("body_temperature")?.intValue()
44-
contributorHrvBalance = data.get("contributors")?.get("hrv_balance")?.intValue()
44+
contributorHrvBalance =
45+
data.get("contributors")?.get("hrv_balance")?.intValue()
4546
contributorPreviousDayActivity =
4647
data.get("contributors")?.get("previous_day_activity")?.intValue()
4748
contributorPreviousNight = data.get("contributors")?.get("previous_night")?.intValue()
4849
contributorRecoveryIndex = data.get("contributors")?.get("recovery_index")?.intValue()
4950
contributorRestingHeartRate =
5051
data.get("contributors")?.get("resting_heart_rate")?.intValue()
5152
contributorSleepBalance = data.get("contributors")?.get("sleep_balance")?.intValue()
52-
day = data.get("day").textValue()
53-
score = data.get("score").intValue()
54-
temperatureDeviation = data.get("temperature_deviation").floatValue()
55-
temperatureTrendDeviation = data.get("temperature_trend_deviation").floatValue()
53+
day = data.get("day")?.textValue()
54+
score = data.get("score")?.intValue()
55+
temperatureDeviation = data.get("temperature_deviation")?.floatValue()
56+
temperatureTrendDeviation = data.get("temperature_trend_deviation")?.floatValue()
5657
}.build()
5758
}
5859

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraDailySleepConverter.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ class OuraDailySleepConverter(
3636
return OuraDailySleep.newBuilder().apply {
3737
time = startTime.toEpochMilli() / 1000.0
3838
timeReceived = System.currentTimeMillis() / 1000.0
39-
id = data.get("id").textValue()
39+
id = data.get("id")?.textValue()
4040
contributorDeepSleep = data.get("contributors")?.get("deep_sleep")?.intValue()
4141
contributorEfficiency = data.get("contributors")?.get("efficiency")?.intValue()
4242
contributorLatency = data.get("contributors")?.get("latency")?.intValue()
4343
contributorRemSleep = data.get("contributors")?.get("rem_sleep")?.intValue()
4444
contributorRestfulness = data.get("contributors")?.get("restfulness")?.intValue()
4545
contributorTiming = data.get("contributors")?.get("timing")?.intValue()
4646
contributorTotalSleep = data.get("contributors")?.get("total_sleep")?.intValue()
47-
day = data.get("day").textValue()
48-
score = data.get("score").intValue()
47+
day = data.get("day")?.textValue()
48+
score = data.get("score")?.intValue()
4949
}.build()
5050
}
5151

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraHeartRateConverter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class OuraHeartRateConverter(
3838
time = startTime.toEpochMilli() / 1000.0
3939
timeReceived = System.currentTimeMillis() / 1000.0
4040
source = data.get("source")?.textValue()?.classify()
41-
bpm = data.get("bpm").intValue()
41+
bpm = data.get("bpm")?.intValue()
4242
}.build()
4343
}
4444

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraPersonalInfoConverter.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ class OuraPersonalInfoConverter(
2929
return OuraPersonalInfo.newBuilder().apply {
3030
time = System.currentTimeMillis() / 1000.0
3131
timeReceived = System.currentTimeMillis() / 1000.0
32-
id = data.get("id").textValue()
33-
age = data.get("age").intValue()
34-
weight = data.get("weight").floatValue()
35-
height = data.get("height").floatValue()
36-
biologicalSex = data.get("biological_sex").textValue()
37-
email = data.get("email").textValue()
32+
id = data.get("id")?.textValue()
33+
age = data.get("age")?.intValue()
34+
weight = data.get("weight")?.floatValue()
35+
height = data.get("height")?.floatValue()
36+
biologicalSex = data.get("biological_sex")?.textValue()
37+
email = data.get("email")?.textValue()
3838
}.build()
3939
}
4040

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraRingConfigurationConverter.kt

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,20 @@ class OuraRingConfigurationConverter(
3838
}
3939
}
4040

41-
private fun JsonNode.toRingConfiguration(setupTime: Instant?): OuraRingConfiguration {
41+
private fun JsonNode.toRingConfiguration(
42+
setupTime: Instant?,
43+
): OuraRingConfiguration {
4244
val data = this
4345
return OuraRingConfiguration.newBuilder().apply {
4446
time = System.currentTimeMillis() / 1000.0
4547
timeReceived = System.currentTimeMillis() / 1000.0
46-
id = data.get("id").textValue()
47-
color = data.get("color").textValue()?.classifyColor()
48-
design = data.get("design").textValue()?.classifyDesign()
49-
firmwareVersion = data.get("firmware_version").textValue()
50-
hardwareType = data.get("hardware_type").textValue()?.classifyHardware()
51-
setUpAt =
52-
setupTime?.toEpochMilli()?.let {
53-
it / 1000.0
54-
}
55-
size = data.get("size").intValue()
48+
id = data.get("id")?.textValue()
49+
color = data.get("color")?.textValue()?.classifyColor()
50+
design = data.get("design")?.textValue()?.classifyDesign()
51+
firmwareVersion = data.get("firmware_version")?.textValue()
52+
hardwareType = data.get("hardware_type")?.textValue()?.classifyHardware()
53+
setUpAt = setupTime?.toEpochMilli()?.let { it / 1000.0 }
54+
size = data.get("size")?.intValue()
5655
}.build()
5756
}
5857

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraSessionConverter.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ class OuraSessionConverter(
3737
val data = this
3838
return OuraSession.newBuilder().apply {
3939
time = startTime.toEpochMilli() / 1000.0
40-
endTime = OffsetDateTime.parse(data.get("end_datetime").textValue())
41-
.toInstant().toEpochMilli() / 1000.0
40+
endTime = data.get("end_datetime")?.let {
41+
val endTime = OffsetDateTime.parse(it.textValue())
42+
endTime.toInstant().toEpochMilli() / 1000.0
43+
} ?: (startTime.toEpochMilli() / 1000.0)
4244
timeReceived = System.currentTimeMillis() / 1000.0
43-
id = data.get("id").textValue()
44-
type = data.get("type").textValue()?.classifyType()
45-
mood = data.get("mood").textValue()?.classifyMood()
45+
id = data.get("id")?.textValue()
46+
type = data.get("type")?.textValue()?.classifyType()
47+
mood = data.get("mood")?.textValue()?.classifyMood()
4648
}.build()
4749
}
4850

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraSleepConverter.kt

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ class OuraSleepConverter(
3737
return OuraSleep.newBuilder().apply {
3838
time = startTime.toEpochMilli() / 1000.0
3939
timeReceived = System.currentTimeMillis() / 1000.0
40-
id = data.get("id").textValue()
41-
averageBreath = data.get("average_breath").floatValue()
42-
averageHeartRate = data.get("average_heart_rate").floatValue()
43-
averageHrv = data.get("average_hrv").intValue()
44-
awakeTime = data.get("awake_time").intValue()
45-
bedtimeEnd = data.get("bedtime_end").textValue()
46-
bedtimeStart = data.get("bedtime_start").textValue()
47-
day = data.get("day").textValue()
48-
deepSleepDuration = data.get("deep_sleep_duration").intValue()
49-
efficiency = data.get("efficiency").intValue()
50-
latency = data.get("latency").intValue()
51-
lightSleepDuration = data.get("light_sleep_duration").intValue()
52-
lowBatteryAlert = data.get("low_battery_alert").booleanValue()
53-
lowestHeartRate = data.get("lowest_heart_rate").intValue()
54-
period = data.get("period").intValue()
40+
id = data.get("id")?.textValue()
41+
averageBreath = data.get("average_breath")?.floatValue()
42+
averageHeartRate = data.get("average_heart_rate")?.floatValue()
43+
averageHrv = data.get("average_hrv")?.intValue()
44+
awakeTime = data.get("awake_time")?.intValue()
45+
bedtimeEnd = data.get("bedtime_end")?.textValue()
46+
bedtimeStart = data.get("bedtime_start")?.textValue()
47+
day = data.get("day")?.textValue()
48+
deepSleepDuration = data.get("deep_sleep_duration")?.intValue()
49+
efficiency = data.get("efficiency")?.intValue()
50+
latency = data.get("latency")?.intValue()
51+
lightSleepDuration = data.get("light_sleep_duration")?.intValue()
52+
lowBatteryAlert = data.get("low_battery_alert")?.booleanValue()
53+
lowestHeartRate = data.get("lowest_heart_rate")?.intValue()
54+
period = data.get("period")?.intValue()
5555
readinessContributorActivityBalance =
5656
data.get("readiness")?.get("contributors")?.get("activity_balance")?.intValue()
5757
readinessContributorBodyTemperature =
@@ -73,13 +73,13 @@ class OuraSleepConverter(
7373
data.get("readiness")?.get("temperature_deviation")?.intValue()
7474
readinessTemperatureTrendDeviation =
7575
data.get("readiness")?.get("temperature_trend_deviation")?.intValue()
76-
readinessScoreDelta = data.get("readiness_score_delta").intValue()
77-
remSleepDuration = data.get("rem_sleep_duration").intValue()
78-
restlessPeriods = data.get("restless_periods").intValue()
79-
sleepScoreDelta = data.get("sleep_score_delta").intValue()
80-
timeInBed = data.get("time_in_bed").intValue()
81-
totalSleepDuration = data.get("total_sleep_duration").intValue()
82-
type = data.get("type").textValue()?.classifyType()
76+
readinessScoreDelta = data.get("readiness_score_delta")?.intValue()
77+
remSleepDuration = data.get("rem_sleep_duration")?.intValue()
78+
restlessPeriods = data.get("restless_periods")?.intValue()
79+
sleepScoreDelta = data.get("sleep_score_delta")?.intValue()
80+
timeInBed = data.get("time_in_bed")?.intValue()
81+
totalSleepDuration = data.get("total_sleep_duration")?.intValue()
82+
type = data.get("type")?.textValue()?.classifyType()
8383
}.build()
8484
}
8585

oura-library/src/main/kotlin/org/radarbase/oura/converter/OuraTagConverter.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ class OuraTagConverter(
4646
return OuraTag.newBuilder().apply {
4747
time = startTime.toEpochMilli() / 1000.0
4848
timeReceived = System.currentTimeMillis() / 1000.0
49-
id = data.get("id").textValue()
50-
day = data.get("day").textValue()
51-
text = data.get("text").textValue()
49+
id = data.get("id")?.textValue()
50+
day = data.get("day")?.textValue()
51+
text = data.get("text")?.textValue()
5252
tag = tagString
5353
}.build()
5454
}

0 commit comments

Comments
 (0)