Skip to content

Commit

Permalink
Provide a way to configure the API.
Browse files Browse the repository at this point in the history
Right now there is no way to provide any configuration by the users.
You can now configure the level of logging in the library.
  • Loading branch information
rubenquadros committed Apr 24, 2024
1 parent 814264b commit 5a08975
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 14 deletions.
15 changes: 13 additions & 2 deletions kovibes/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ koverReport {
filters {
excludes {
classes(
"io.github.rubenquadros.kovibes.response.*",
"io.github.rubenquadros.kovibes.request.*"
"io.github.rubenquadros.kovibes.api.response.*",
"io.github.rubenquadros.kovibes.api.request.*"
)
annotatedBy("io.github.rubenquadros.kovibes.api.ExcludeFromCoverage")
}
Expand All @@ -103,6 +103,17 @@ koverReport {
verify {
rule {
isEnabled = true

filters {
excludes {
classes(
"io.github.rubenquadros.kovibes.api.response.*",
"io.github.rubenquadros.kovibes.api.request.*"
)
annotatedBy("io.github.rubenquadros.kovibes.api.ExcludeFromCoverage")
}
}

bound {
minValue = 82
metric = MetricType.LINE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import kotlin.io.encoding.Base64

/**
* AuthStorage is responsible to store the [accessToken].
*
*/
internal class AuthStorage {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.rubenquadros.kovibes.api

import io.github.rubenquadros.kovibes.api.config.Config
import io.github.rubenquadros.kovibes.api.config.logger.LogLevel

/**
* ConfigProvider is responsible for providing the different [Config].
*/
internal class ConfigProvider {

private var config: Config? = null

/**
* Init the config provider.
*
* @param config
*/
fun init(config: Config) {
this.config = config
}

/**
* Return the log level for the API.
*
* @return [LogLevel]
*/
fun getLogLevel(): LogLevel {
if (config == null) error("Method init has not been called.")
return config!!.logLevel
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.rubenquadros.kovibes.api

import io.github.rubenquadros.kovibes.api.artist.ArtistApiImpl
import io.github.rubenquadros.kovibes.api.browse.BrowseApiImpl
import io.github.rubenquadros.kovibes.api.config.Config
import io.github.rubenquadros.kovibes.api.playlist.PlaylistApiImpl
import io.github.rubenquadros.kovibes.api.recommendations.RecommendationsApiImpl
import io.github.rubenquadros.kovibes.api.search.SearchApiImpl
Expand All @@ -13,11 +14,14 @@ object KoVibesApi {

private val authStorage: AuthStorage by lazy { AuthStorage() }

private val configProvider: ConfigProvider by lazy { ConfigProvider() }

private val ktorService: KtorService by lazy {
KtorService(
authStorage = authStorage,
ktorEngine = { getKtorEngine() },
ktorLogger = { getKtorLogger() }
ktorLogger = { getKtorLogger() },
logLevel = { configProvider.getLogLevel() }
)
}

Expand All @@ -36,15 +40,23 @@ object KoVibesApi {
*
* See the [Doc to generate Client ID and Client Secret](https://developer.spotify.com/documentation/web-api/tutorials/getting-started#:~:text=of%20your%20choice.-,Set%20Up%20Your%20Account,-Login%20to%20the).
*
* See [Config] to know the default configuration.
*
* @param clientId
* @param clientSecret
* @param config
* @return [SpotifyService]
*/
fun createSpotifyService(clientId: String, clientSecret: String): SpotifyService {
fun createSpotifyService(
clientId: String,
clientSecret: String,
config: Config = Config()
): SpotifyService {
validateClientId(clientId)
validateClientSecret(clientSecret)

authStorage.init(clientId, clientSecret)
configProvider.init(config)
return spotifyService
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.rubenquadros.kovibes.api

import io.github.rubenquadros.kovibes.api.config.logger.LogLevel
import io.github.rubenquadros.kovibes.api.config.logger.toKtorLogLevel
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.HttpClientEngine
Expand All @@ -8,7 +10,6 @@ import io.ktor.client.plugins.auth.providers.BearerTokens
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.HttpRequestBuilder
Expand All @@ -31,11 +32,13 @@ import kotlinx.serialization.json.Json
*
* @param ktorEngine
* @param ktorLogger
* @param logLevel
*/
internal class KtorService(
ktorEngine: () -> HttpClientEngine,
ktorLogger: () -> Logger,
private val authStorage: AuthStorage
logLevel: () -> LogLevel,
private val authStorage: AuthStorage,
) {
val client: HttpClient by lazy {
HttpClient(ktorEngine()) {
Expand All @@ -48,7 +51,7 @@ internal class KtorService(

install(Logging) {
logger = ktorLogger()
level = LogLevel.ALL
level = logLevel().toKtorLogLevel()
}

install(ContentNegotiation) {
Expand Down Expand Up @@ -112,6 +115,7 @@ internal class KtorService(
}

@Serializable
@ExcludeFromCoverage
internal data class TokenResponse(
@SerialName("access_token")
val accessToken: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.rubenquadros.kovibes.api.config

import io.github.rubenquadros.kovibes.api.config.logger.LogLevel

/**
* This represents all the configurations of the [io.github.rubenquadros.kovibes.api.KoVibesApi].
*
* If no configurations are provided explicitly then the default values are taken.
*/
data class Config(
val logLevel: LogLevel = LogLevel.NONE
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.rubenquadros.kovibes.api.config.logger

/**
* Log level decides the logging level of the entire API.
*/
enum class LogLevel {
/**
* Everything is logged - [HEADERS], [BODY] and [INFO]
*/
ALL,

/**
* Only headers are logged.
*/
HEADERS,

/**
* Only body is logged.
*/
BODY,

/**
* Only info logs are provided - request type and response status.
*/
INFO,

/**
* No logs are provided.
* This is the default log level.
*/
NONE
}

/**
* @suppress
* Map [LogLevel] to [io.ktor.client.plugins.logging.LogLevel]
*/
internal fun LogLevel.toKtorLogLevel(): io.ktor.client.plugins.logging.LogLevel {
return when(this) {
LogLevel.ALL -> io.ktor.client.plugins.logging.LogLevel.ALL
LogLevel.HEADERS -> io.ktor.client.plugins.logging.LogLevel.HEADERS
LogLevel.BODY -> io.ktor.client.plugins.logging.LogLevel.BODY
LogLevel.INFO -> io.ktor.client.plugins.logging.LogLevel.INFO

Check warning on line 43 in kovibes/src/commonMain/kotlin/io/github/rubenquadros/kovibes/api/config/logger/Logger.kt

View check run for this annotation

Codecov / codecov/patch

kovibes/src/commonMain/kotlin/io/github/rubenquadros/kovibes/api/config/logger/Logger.kt#L40-L43

Added lines #L40 - L43 were not covered by tests
LogLevel.NONE -> io.ktor.client.plugins.logging.LogLevel.NONE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.rubenquadros.kovibes.api.test

import io.github.rubenquadros.kovibes.api.AuthStorage
import io.github.rubenquadros.kovibes.api.KtorService
import io.github.rubenquadros.kovibes.api.config.logger.LogLevel
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.respond
import io.ktor.client.plugins.logging.Logger
Expand All @@ -15,7 +16,8 @@ object MockKtorService {
return KtorService(
authStorage = AuthStorage(),
ktorEngine = { createMockEngine(mockConfig) },
ktorLogger = { Logger.SIMPLE }
ktorLogger = { Logger.SIMPLE },
logLevel = { LogLevel.NONE }
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@ import io.github.rubenquadros.kovibes.api.models.AlbumInfo
import io.github.rubenquadros.kovibes.api.models.ArtistInfo
import io.github.rubenquadros.kovibes.api.models.ExternalUrls
import io.github.rubenquadros.kovibes.api.models.ImageInfo
import io.github.rubenquadros.kovibes.api.playlist.models.RestrictionInfo
import kotlinx.serialization.json.Json
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

const val errorResponsePath = "error.json"

const val clientId = "client_id"

const val clientSecret = "client_secret"

const val authToken = "auth_token"

@OptIn(ExperimentalEncodingApi::class)
internal val encodedCred = Base64.encode(
source = "$clientId:$clientSecret".toByteArray()
)

internal val imageInfo = ImageInfo(
height = 200,
width = 200,
url = "https://vibesync.image.png"
)

internal val restrictionInfo = RestrictionInfo(
reason = "Reason for restriction"
)

internal val albumInfo = AlbumInfo(
albumType = "album",
albumGroup = "album",
Expand All @@ -29,7 +47,8 @@ internal val albumInfo = AlbumInfo(
spotify = "spotify"
),
images = listOf(imageInfo),
artists = listOf()
artists = listOf(),
restrictions = restrictionInfo
)

internal val artistInfo = Json.decodeFromString<ArtistInfo>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.github.rubenquadros.kovibes.api.test.authstorage

import io.github.rubenquadros.kovibes.api.AuthStorage
import io.github.rubenquadros.kovibes.api.test.authToken
import io.github.rubenquadros.kovibes.api.test.clientId
import io.github.rubenquadros.kovibes.api.test.clientSecret
import io.github.rubenquadros.kovibes.api.test.encodedCred
import kotlin.test.Test
import kotlin.test.assertTrue

class AuthStorageTest {

private val authStorage = AuthStorage()

@Test
fun `initially the auth token is not available`() {
assertTrue { authStorage.getAccessToken().isEmpty() }
}

@Test
fun `when auth storage is initialised then then encoded credentials is available`() {
authStorage.init(clientId, clientSecret)

assertTrue { authStorage.getEncodedCredentials() == encodedCred }
}

@Test
fun `when token is updated then it is stored`() {
assertTrue { authStorage.getAccessToken().isEmpty() }

authStorage.updateAccessToken(authToken)

assertTrue { authStorage.getAccessToken() == authToken }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.rubenquadros.kovibes.api.test.config

import io.github.rubenquadros.kovibes.api.ConfigProvider
import io.github.rubenquadros.kovibes.api.config.Config
import io.github.rubenquadros.kovibes.api.config.logger.LogLevel
import kotlin.test.Test
import kotlin.test.assertFails
import kotlin.test.assertTrue

class ConfigProviderTest {

private val configProvider = ConfigProvider()

@Test
fun `retrieving the config before initializing the provider throws error`() {
val error = assertFails { configProvider.getLogLevel() }

assertTrue { error is IllegalStateException }
assertTrue { error.message == "Method init has not been called." }
}

@Test
fun `we are able to retrieve the configurations`() {
LogLevel.entries.forEach {
configProvider.init(config = Config(it))
val logLevel = configProvider.getLogLevel()

assertTrue { logLevel == it }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import io.github.rubenquadros.kovibes.api.models.PlaylistInfo
import io.github.rubenquadros.kovibes.api.models.PlaylistOwner
import io.github.rubenquadros.kovibes.api.models.PlaylistTracks
import io.github.rubenquadros.kovibes.api.models.TrackInfo
import io.github.rubenquadros.kovibes.api.playlist.models.RestrictionInfo
import io.github.rubenquadros.kovibes.api.test.albumInfo
import io.github.rubenquadros.kovibes.api.test.artistInfo
import io.github.rubenquadros.kovibes.api.test.imageInfo
import io.github.rubenquadros.kovibes.api.test.restrictionInfo
import kotlin.test.Test
import kotlin.test.assertTrue

Expand All @@ -34,9 +34,7 @@ class ApiMapperTest {

@Test
fun `restriction info is mapped to restrictions`() {
val restrictionInfo = RestrictionInfo(
reason = "Reason for restriction"
)
val restrictionInfo = restrictionInfo

val restrictions = restrictionInfo.toRestrictions()

Expand Down

0 comments on commit 5a08975

Please sign in to comment.