/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tamarasdk.api
import com.tamarasdk.error.BaseException
import com.tamarasdk.error.UnknownException
import com.tamarasdk.log.Logging
import retrofit2.Response
import java.util.regex.Pattern
/**
* Common class used by API responses.
* @param the type of the response object
*/
@Suppress("unused") // T is used in extending classes
sealed class ApiResponse {
companion object {
fun create(error: Throwable): ApiErrorResponse {
return ApiErrorResponse(BaseException.newInstance(0,error.message ?: "unknown error"))
}
fun create(response: Response): ApiResponse {
return if (response.isSuccessful) {
val body = response.body()
if (body == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(
body = body,
linkHeader = response.headers()?.get("link")
)
}
} else {
val code = response.code()
val msg = response.errorBody()?.string()
val errorMsg = if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(BaseException.newInstance(code,errorMsg ?: "unknown error"))
}
}
}
}
/**
* separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
*/
class ApiEmptyResponse : ApiResponse()
data class ApiSuccessResponse(
val body: T,
val links: Map
) : ApiResponse() {
constructor(body: T, linkHeader: String?) : this(
body = body,
links = linkHeader?.extractLinks() ?: emptyMap()
)
val nextPage: Int? by lazy(LazyThreadSafetyMode.NONE) {
links[NEXT_LINK]?.let { next ->
val matcher = PAGE_PATTERN.matcher(next)
if (!matcher.find() || matcher.groupCount() != 1) {
null
} else {
try {
matcher.group(1)?.let {
Integer.parseInt(it)
}
} catch (ex: NumberFormatException) {
Logging.w("cannot parse next page from %s", next)
null
}
}
}
}
companion object {
private val LINK_PATTERN = Pattern.compile("<([^>]*)>[\\s]*;[\\s]*rel=\"([a-zA-Z0-9]+)\"")
private val PAGE_PATTERN = Pattern.compile("\\bpage=(\\d+)")
private const val NEXT_LINK = "next"
private fun String.extractLinks(): Map {
val links = mutableMapOf()
val matcher = LINK_PATTERN.matcher(this)
while (matcher.find()) {
val count = matcher.groupCount()
if (count == 2) {
matcher.group(2)?.let { key->
matcher.group(1)?.let { value->
links[key] = value
}
}
}
}
return links
}
}
}
data class ApiErrorResponse(val error: BaseException) : ApiResponse()