/* * 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()