Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: setup quality checks #148

Merged
merged 18 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/code-quality-checks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Code Quality Checks
on: pull_request

jobs:
quality-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: 21
- name: Pull GraphQL Submodule
run: ./scripts/graphql/init.sh
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Run Checks
id: build
run: gradle clean build
- name: Upload Coverage Report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: code/build/reports/kover
4 changes: 4 additions & 0 deletions code/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ plugins {

/* Test Reporting */
id 'org.jetbrains.kotlinx.kover' version "0.9.0"

/* Linting */
id "org.jlleitschuh.gradle.ktlint" version "12.1.2"
}

kotlin {
Expand Down Expand Up @@ -48,3 +51,4 @@ apply from: "tasks-gradle/sdk-properties.gradle"
apply from: "tasks-gradle/apollo.gradle"
apply from: "tasks-gradle/publishing.gradle"
apply from: "tasks-gradle/dokka.gradle"
apply from: "tasks-gradle/lint.gradle"
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ import java.io.IOException
* @param authManager The [BearerAuthenticationManager] used for making authentication requests execution and parsing.
*/
class BearerAuthenticationInterceptor(
private val authManager: BearerAuthenticationManager
private val authManager: BearerAuthenticationManager,
) : Interceptor {

private val lock = Any()

/**
Expand All @@ -56,9 +55,11 @@ class BearerAuthenticationInterceptor(

ensureValidAuthentication()

val authorizedRequest = request.newBuilder()
.addHeader("Authorization", authManager.getAuthorizationHeaderValue())
.build()
val authorizedRequest =
request
.newBuilder()
.addHeader("Authorization", authManager.getAuthorizationHeaderValue())
.build()

return chain.proceed(authorizedRequest)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ class BearerAuthenticationManager(
private val requestExecutor: AbstractRequestExecutor,
private val credentials: Credentials,
) : AuthenticationManager {

@Volatile
private var bearerTokenStorage = BearerTokenStorage.empty

Expand Down Expand Up @@ -74,42 +73,47 @@ class BearerAuthenticationManager(
*
* @return `true` if the token is near expiration, `false` otherwise.
*/
fun isTokenAboutToExpire(): Boolean = run {
bearerTokenStorage.isAboutToExpire()
}
fun isTokenAboutToExpire(): Boolean =
run {
bearerTokenStorage.isAboutToExpire()
}

/**
* Clears the stored authentication token.
*
* This method resets the internal token storage, effectively invalidating the current session.
*/
override fun clearAuthentication() = run {
bearerTokenStorage = BearerTokenStorage.empty
}
override fun clearAuthentication() =
run {
bearerTokenStorage = BearerTokenStorage.empty
}

/**
* Retrieves the stored token formatted as an `Authorization` header value.
*
* @return The token in the format `Bearer <token>` for use in HTTP headers.
*/
fun getAuthorizationHeaderValue(): String = run {
bearerTokenStorage.getAuthorizationHeaderValue()
}
fun getAuthorizationHeaderValue(): String =
run {
bearerTokenStorage.getAuthorizationHeaderValue()
}

/**
* Creates an HTTP request to fetch a new bearer token from the authentication server.
*
* @return A [Request] object configured with the necessary headers and parameters.
*/
private fun buildAuthenticationRequest(): Request = run {
Request.Builder()
.url(authUrl)
.method(Method.POST)
.body( RequestBody.create(mapOf("grant_type" to "client_credentials")))
.setHeader("Authorization", credentials.encodeBasic())
.setHeader("Content-Type", CommonMediaTypes.APPLICATION_FORM_URLENCODED.toString())
.build()
}
private fun buildAuthenticationRequest(): Request =
run {
Request
.Builder()
.url(authUrl)
.method(Method.POST)
.body(RequestBody.create(mapOf("grant_type" to "client_credentials")))
.setHeader("Authorization", credentials.encodeBasic())
.setHeader("Content-Type", CommonMediaTypes.APPLICATION_FORM_URLENCODED.toString())
.build()
}

/**
* Executes the authentication request and validates the response.
Expand All @@ -118,23 +122,26 @@ class BearerAuthenticationManager(
* @return The [Response] received from the server.
* @throws ExpediaGroupAuthException If the server responds with an error.
*/
private fun executeAuthenticationRequest(request: Request): Response = run {
requestExecutor.execute(request).apply {
if (!this.isSuccessful) {
throw ExpediaGroupAuthException(this.status, "Authentication failed")
private fun executeAuthenticationRequest(request: Request): Response =
run {
requestExecutor.execute(request).apply {
if (!this.isSuccessful) {
throw ExpediaGroupAuthException(this.status, "Authentication failed")
}
}
}
}

/**
* Stores the retrieved token in internal storage for subsequent use.
*
* @param tokenResponse The [TokenResponse] containing the token and its expiration time.
*/
private fun storeToken(tokenResponse: TokenResponse) = run {
bearerTokenStorage = BearerTokenStorage.create(
accessToken = tokenResponse.accessToken,
expiresIn = tokenResponse.expiresIn
)
}
private fun storeToken(tokenResponse: TokenResponse) =
run {
bearerTokenStorage =
BearerTokenStorage.create(
accessToken = tokenResponse.accessToken,
expiresIn = tokenResponse.expiresIn,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ class BearerTokenStorage private constructor(
val expiresIn: Long,
private val expirationBufferSeconds: Long,
private val clock: Clock,
private val expiryInstant: Instant
private val expiryInstant: Instant,
) {

/**
* Checks if the bearer token is about to expire.
*
Expand All @@ -47,9 +46,10 @@ class BearerTokenStorage private constructor(
*
* @return `true` if the token is about to expire; `false` otherwise.
*/
fun isAboutToExpire(): Boolean = run {
Instant.now(clock).isAfter(expiryInstant.minusSeconds(expirationBufferSeconds))
}
fun isAboutToExpire(): Boolean =
run {
Instant.now(clock).isAfter(expiryInstant.minusSeconds(expirationBufferSeconds))
}

/**
* Formats the bearer token as an `Authorization` header value.
Expand Down Expand Up @@ -80,25 +80,24 @@ class BearerTokenStorage private constructor(
accessToken: String,
expiresIn: Long,
expirationBufferSeconds: Long = DEFAULT_EXPIRATION_BUFFER_SECONDS,
clock: Clock = Clock.systemUTC()
clock: Clock = Clock.systemUTC(),
): BearerTokenStorage {
val expiryInstant = if (expiresIn >= 0) {
Instant.now(clock).plusSeconds(expiresIn)
} else {
Instant.EPOCH
}
val expiryInstant =
if (expiresIn >= 0) {
Instant.now(clock).plusSeconds(expiresIn)
} else {
Instant.EPOCH
}

return BearerTokenStorage(
accessToken = accessToken,
expiresIn = expiresIn,
expirationBufferSeconds = expirationBufferSeconds,
clock = clock,
expiryInstant = expiryInstant
expiryInstant = expiryInstant,
)
}
}

override fun toString(): String {
return "BearerTokenStorage(expiresIn=$expiresIn, expirationBufferSeconds=$expirationBufferSeconds, expiryInstant=$expiryInstant)"
}
override fun toString(): String = "BearerTokenStorage(expiresIn=$expiresIn, expirationBufferSeconds=$expirationBufferSeconds, expiryInstant=$expiryInstant)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule
@JsonIgnoreProperties(ignoreUnknown = true)
data class TokenResponse(
@JsonProperty("access_token") val accessToken: String,
@JsonProperty("expires_in") val expiresIn: Long
@JsonProperty("expires_in") val expiresIn: Long,
) {
companion object {
private val objectMapper = ObjectMapper()
.registerKotlinModule()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
private val objectMapper =
ObjectMapper()
.registerKotlinModule()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

/**
* Parses the response from the authentication server to extract token details.
Expand All @@ -52,13 +53,15 @@ data class TokenResponse(
* @throws ExpediaGroupResponseParsingException If the response cannot be parsed.
*/
fun parse(response: Response): TokenResponse {
val responseBody = response.body.getOrThrow {
ExpediaGroupResponseParsingException("Authenticate response body is empty or cannot be parsed")
}
val responseBody =
response.body.getOrThrow {
ExpediaGroupResponseParsingException("Authenticate response body is empty or cannot be parsed")
}

val responseString = responseBody.source().use {
it.readString(responseBody.mediaType()?.charset ?: Charsets.UTF_8)
}
val responseString =
responseBody.source().use {
it.readString(responseBody.mediaType()?.charset ?: Charsets.UTF_8)
}

return try {
objectMapper.readValue(responseString, TokenResponse::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import java.util.Base64
*/
data class Credentials(
private val key: String,
private val secret: String
private val secret: String,
) {
/**
* Encodes the credentials into a `Basic` authentication header value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkExce
*
* @param transport The transport implementation to use for executing requests
*/
abstract class AbstractRequestExecutor(protected val transport: Transport) : Disposable {
abstract class AbstractRequestExecutor(
protected val transport: Transport,
) : Disposable {
/**
* List of interceptors to be applied to requests in order.
*
Expand All @@ -77,11 +79,12 @@ abstract class AbstractRequestExecutor(protected val transport: Transport) : Dis
* @throws ExpediaGroupNetworkException If any network-related error occurs
*/
open fun execute(request: Request): Response {
val chainExecutor = InterceptorsChainExecutor(
interceptors = interceptors,
request = request,
transport = this.transport
)
val chainExecutor =
InterceptorsChainExecutor(
interceptors = interceptors,
request = request,
transport = this.transport,
)

return chainExecutor.proceed(request)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ internal object MetadataLoader {
/**
* Loads the SDK metadata from sdk.properties file and caches the results for future calls.
*/
fun load(): Metadata {
return cachedMetadata ?: synchronized(this) {
fun load(): Metadata =
cachedMetadata ?: synchronized(this) {
cachedMetadata ?: readPropertiesFile().also { cachedMetadata = it }
}
}

/**
* Clears the cached metadata
Expand All @@ -62,7 +61,7 @@ internal object MetadataLoader {
osName = System.getProperty("os.name", UNKNOWN),
osVersion = System.getProperty("os.version", UNKNOWN),
arch = System.getProperty("os.arch", UNKNOWN),
locale = Locale.getDefault().toString()
locale = Locale.getDefault().toString(),
)
}
}
Expand All @@ -76,9 +75,7 @@ data class Metadata(
val osName: String,
val osVersion: String,
val arch: String,
val locale: String
val locale: String,
) {
fun asUserAgentString(): String {
return "$artifactName/$version (Provider/$groupId; Java/$jdkVersion; Vendor/$jdkVendor; OS/$osName - $osVersion; Arch/$arch; Locale/$locale)"
}
fun asUserAgentString(): String = "$artifactName/$version (Provider/$groupId; Java/$jdkVersion; Vendor/$jdkVendor; OS/$osName - $osVersion; Arch/$arch; Locale/$locale)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ import com.expediagroup.sdk.core.interceptor.Interceptor
* Interceptor for setting required headers before executing the request
*/
class RequestHeadersInterceptor : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val metadata = MetadataLoader.load()

val request = chain.request().newBuilder()
.setHeader("User-Agent", metadata.asUserAgentString())
.build()
val request =
chain
.request()
.newBuilder()
.setHeader("User-Agent", metadata.asUserAgentString())
.build()

return chain.proceed(request)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.expediagroup.sdk.core.extension

inline fun <T> T?.getOrThrow(exceptionProvider: () -> Throwable): T {
return this ?: throw exceptionProvider()
}
inline fun <T> T?.getOrThrow(exceptionProvider: () -> Throwable): T = this ?: throw exceptionProvider()

fun Boolean?.orFalseIfNull(): Boolean = this == true

Expand Down
Loading
Loading