feature: Enhance grpc json printing (#117)

* Enhance json printing for grpc calls

* Remove gson dependency

* Split grpc interceptor into three modules for both protobuf libraries

* Remove unwanted versioned files

* Abstract the grpc interceptor to avoid injecting the formatter

* fixed readme

* fixed pom for lit

* added to publish yml

---------

Co-authored-by: Florent Champigny <florent@bere.al>
Co-authored-by: Florent CHAMPIGNY <champigny.florent@gmail.com>
This commit is contained in:
Stephen Vinouze 2025-08-24 10:09:11 +02:00 committed by GitHub
parent a893b32ef1
commit 288372bdd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 522 additions and 226 deletions

View file

@ -15,6 +15,8 @@
<option value="$PROJECT_DIR$/flocon-base" />
<option value="$PROJECT_DIR$/flocon-no-op" />
<option value="$PROJECT_DIR$/grpc-interceptor" />
<option value="$PROJECT_DIR$/grpc-interceptor-base" />
<option value="$PROJECT_DIR$/grpc-interceptor-lite" />
<option value="$PROJECT_DIR$/ktor-interceptor" />
<option value="$PROJECT_DIR$/okhttp-interceptor" />
</set>

View file

@ -66,19 +66,20 @@ android {
}
}
val useMaven = false
val useMaven = true
dependencies {
if(useMaven) {
val floconVersion = "1.0.5"
val floconVersion = "1.1.2"
implementation("io.github.openflocon:flocon:$floconVersion")
//implementation("io.github.openflocon:flocon-no-op:$floconVersion")
implementation("io.github.openflocon:flocon-grpc-interceptor:$floconVersion")
implementation("io.github.openflocon:flocon-grpc-interceptor-lite:$floconVersion")
implementation("io.github.openflocon:flocon-okhttp-interceptor:$floconVersion")
implementation("io.github.openflocon:flocon-ktor-interceptor:$floconVersion")
} else {
debugImplementation(project(":flocon"))
releaseImplementation(project(":flocon-no-op"))
implementation(project(":okhttp-interceptor"))
implementation(project(":grpc-interceptor"))
implementation(project(":grpc-interceptor-lite"))
implementation(project(":ktor-interceptor"))
}
@ -106,7 +107,7 @@ dependencies {
// region grpc
implementation(libs.grpc.android)
implementation(libs.grpc.kotin.stub)
implementation(libs.grpc.kotlin.stub)
implementation(libs.grpc.protobuf.lite)
implementation(libs.grpc.okhttp)
implementation(libs.protobuf.kotlin.lite)

View file

@ -1,12 +1,11 @@
package io.github.openflocon.flocon.myapplication.grpc
import io.github.openflocon.flocon.grpc.FloconGrpcInterceptor
import io.github.openflocon.flocon.grpc.lite.FloconGrpcInterceptor
import io.grpc.CallOptions
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import io.grpc.examples.helloworld.GreeterGrpcKt
import io.grpc.examples.helloworld.helloRequest
import kotlin.getValue
object GrpcController {
val channel: ManagedChannel by lazy {
@ -19,7 +18,7 @@ object GrpcController {
.build()
}
val geeterClient by lazy {
val greeterClient by lazy {
GreeterGrpcKt.GreeterCoroutineStub(
channel = channel,
callOptions = CallOptions.DEFAULT,
@ -31,7 +30,7 @@ object GrpcController {
val request = helloRequest {
name = "florent"
}
val response = geeterClient.sayHello(request)
val response = greeterClient.sayHello(request)
return response.message
} catch (t: Throwable) {
t.printStackTrace()

View file

@ -51,7 +51,8 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
# for grpc
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
grpc-android = { group = "io.grpc", name = "grpc-android", version.ref = "grpc" }
grpc-kotin-stub = { group = "io.grpc", name = "grpc-kotlin-stub", version.ref = "grpcKotlin" }
grpc-okhttp = { group = "io.grpc", name = "grpc-okhttp", version.ref = "grpc" }
grpc-kotlin-stub = { group = "io.grpc", name = "grpc-kotlin-stub", version.ref = "grpcKotlin" }
grpc-protobuf-lite = { group = "io.grpc", name = "grpc-protobuf-lite", version.ref = "grpc" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "kotlinxCoroutinesBom" }
@ -65,7 +66,7 @@ okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp" }
org-jetbrains-kotlinx-kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
org-jetbrains-kotlinx-kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf"}
grpc-okhttp = { group = "io.grpc", name = "grpc-okhttp", version.ref = "grpc" }
protobuf-util = { group = "com.google.protobuf", name = "protobuf-java-util", version.ref = "protobuf" }
squareup-okhttp = { module = "com.squareup.okhttp3:okhttp" }
[plugins]

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,88 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
id("com.vanniktech.maven.publish") version "0.34.0"
}
android {
namespace = "io.github.openflocon.flocon.grpc.base"
compileSdk = 36
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation(project(":flocon-base"))
implementation(platform(libs.kotlinx.coroutines.bom))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.grpc.android)
}
mavenPublishing {
publishToMavenCentral(automaticRelease = true)
if (project.hasProperty("signing.required") && project.property("signing.required") == "false") {
// Skip signing
} else {
signAllPublications()
}
coordinates(
groupId = project.property("floconGroupId") as String,
artifactId = "flocon-grpc-interceptor-base",
version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String
)
pom {
name = "Flocon Grpc Interceptor"
description = project.property("floconDescription") as String
inceptionYear = "2025"
url = "https://github.com/openflocon/Flocon"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
id = "openflocon"
name = "Open Flocon"
url = "https://github.com/openflocon"
}
}
scm {
url = "https://github.com/openflocon/Flocon"
connection = "scm:git:git://github.com/openflocon/Flocon.git"
developerConnection = "scm:git:ssh://git@github.com/openflocon/Flocon.git"
}
}
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,6 @@
package io.github.openflocon.flocon.grpc
interface FloconGrpcBaseFormatter {
fun <T> format(message: T): String
}

View file

@ -0,0 +1,168 @@
package io.github.openflocon.flocon.grpc
import io.github.openflocon.flocon.FloconApp
import io.github.openflocon.flocon.FloconLogger
import io.github.openflocon.flocon.grpc.model.RequestHolder
import io.github.openflocon.flocon.grpc.model.toHeaders
import io.github.openflocon.flocon.plugins.network.FloconNetworkPlugin
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkRequest
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkResponse
import io.grpc.CallOptions
import io.grpc.Channel
import io.grpc.ClientCall
import io.grpc.ClientInterceptor
import io.grpc.ForwardingClientCall
import io.grpc.ForwardingClientCallListener
import io.grpc.Metadata
import io.grpc.MethodDescriptor
import io.grpc.Status
import kotlinx.coroutines.runBlocking
import java.util.UUID
abstract class FloconGrpcBaseInterceptor : ClientInterceptor {
private val floconGrpcPlugin = FloconGrpcPlugin()
abstract val floconGrpcFormatter: FloconGrpcBaseFormatter
override fun <ReqT : Any?, RespT : Any?> interceptCall(
method: MethodDescriptor<ReqT, RespT>,
callOptions: CallOptions,
next: Channel,
): ClientCall<ReqT, RespT> {
val networkPlugin = FloconApp.instance?.client?.networkPlugin
if (networkPlugin == null) {
// do not intercept if no network plugin, just call
return next.newCall(method, callOptions)
}
val callId = UUID.randomUUID().toString()
return LoggingForwardingClientCall(
floconGrpcPlugin = floconGrpcPlugin,
floconNetworkPlugin = networkPlugin,
callId = callId,
method = method,
next = next,
formatter = floconGrpcFormatter,
callOptions = callOptions,
)
}
}
private class LoggingForwardingClientCall<ReqT, RespT>(
private val floconNetworkPlugin: FloconNetworkPlugin,
private val floconGrpcPlugin: FloconGrpcPlugin,
private val callId: String,
private val method: MethodDescriptor<ReqT, RespT>,
private val next: Channel,
private val formatter: FloconGrpcBaseFormatter,
callOptions: CallOptions,
) : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
next.newCall(
method,
callOptions,
),
) {
val requestHolder = RequestHolder()
private var headers: Metadata? = null
override fun start(responseListener: Listener<RespT>, headers: Metadata) {
this.headers = headers
super.start(
LoggingClientCallListener(
floconGrpcPlugin = floconGrpcPlugin,
callId = callId,
requestHolder = requestHolder,
responseListener = responseListener,
formatter = formatter,
),
headers,
)
}
override fun sendMessage(message: ReqT) {
val request = FloconNetworkRequest(
url = next.authority(),
method = method.fullMethodName,
body = formatter.format(message),
startTime = System.currentTimeMillis(),
headers = headers?.toHeaders().orEmpty(),
size = 0, // TODO
isMocked = false, // cannot mock grpc
)
requestHolder.request.complete(request)
floconGrpcPlugin.reportRequest(
callId = callId,
request = request
)
floconNetworkPlugin.badQualityConfig?.let {
executeBadQuality(it)
}
super.sendMessage(message)
}
}
private class LoggingClientCallListener<RespT>(
private val floconGrpcPlugin: FloconGrpcPlugin,
private val callId: String,
responseListener: ClientCall.Listener<RespT>,
private val requestHolder: RequestHolder,
private val formatter: FloconGrpcBaseFormatter,
) : ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(
responseListener,
) {
private var headers: Metadata? = null
private var message: RespT? = null
override fun onClose(status: Status, trailers: Metadata) {
try {
runBlocking { requestHolder.request.await() }.let { request ->
status.cause?.let { cause ->
floconGrpcPlugin.reportResponse(
callId = callId,
request = request,
response = FloconNetworkResponse(
body = null,
headers = emptyMap(),
httpCode = null,
contentType = "grpc",
size = null,
grpcStatus = null,
error = cause.message ?: cause.javaClass.simpleName,
),
)
} ?: run {
floconGrpcPlugin.reportResponse(
callId = callId,
request = request,
response = FloconNetworkResponse(
body = formatter.format(message),
headers = (this.headers ?: trailers).toHeaders(),
httpCode = null,
contentType = "grpc",
size = 0L,
grpcStatus = status.code.toString(),
error = null,
),
)
}
}
} catch (t: Throwable) {
FloconLogger.logError("cannot find request for callId $callId", t)
}
super.onClose(status, trailers)
}
override fun onMessage(message: RespT) {
super.onMessage(message)
this.message = message
}
override fun onHeaders(headers: Metadata?) {
super.onHeaders(headers)
this.headers = headers
}
}

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,86 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
id("com.vanniktech.maven.publish") version "0.34.0"
}
android {
namespace = "io.github.openflocon.flocon.grpc.lite"
compileSdk = 36
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
api(project(":grpc-interceptor-base"))
implementation(libs.grpc.android)
implementation(libs.gson)
}
mavenPublishing {
publishToMavenCentral(automaticRelease = true)
if (project.hasProperty("signing.required") && project.property("signing.required") == "false") {
// Skip signing
} else {
signAllPublications()
}
coordinates(
groupId = project.property("floconGroupId") as String,
artifactId = "flocon-grpc-interceptor-lite",
version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String
)
pom {
name = "Flocon Grpc Interceptor Lite"
description = project.property("floconDescription") as String
inceptionYear = "2025"
url = "https://github.com/openflocon/Flocon"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
id = "openflocon"
name = "Open Flocon"
url = "https://github.com/openflocon"
}
}
scm {
url = "https://github.com/openflocon/Flocon"
connection = "scm:git:git://github.com/openflocon/Flocon.git"
developerConnection = "scm:git:ssh://git@github.com/openflocon/Flocon.git"
}
}
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,51 @@
package io.github.openflocon.flocon.grpc.lite
import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import io.github.openflocon.flocon.grpc.FloconGrpcBaseFormatter
class FloconGrpcFormatter : FloconGrpcBaseFormatter {
private val excluded = setOf(
"unknownFields",
"memoizedHashCode",
"bitField",
"memoizedSerializedSize",
"bytes",
)
private val defaultFieldExcluder: (name: String) -> Boolean = { name ->
var isExcluded = false
excluded.forEach { toExclude ->
if (name.startsWith(prefix = toExclude)) {
isExcluded = true
}
}
isExcluded
}
private val gson = buildGsonInstance(defaultFieldExcluder)
override fun <T> format(message: T): String =
gson.toJson(message)
private fun buildGsonInstance(
excluder: (name: String) -> Boolean,
): Gson {
return GsonBuilder().setPrettyPrinting()
.setFieldNamingStrategy {
it.name.removeSuffix("_")
}
.setExclusionStrategies(object : ExclusionStrategy {
override fun shouldSkipField(f: FieldAttributes): Boolean {
return excluder(f.name)
}
override fun shouldSkipClass(clazz: Class<*>): Boolean {
return false
}
}).create()
}
}

View file

@ -0,0 +1,9 @@
package io.github.openflocon.flocon.grpc.lite
import io.github.openflocon.flocon.grpc.FloconGrpcBaseFormatter
import io.github.openflocon.flocon.grpc.FloconGrpcBaseInterceptor
class FloconGrpcInterceptor : FloconGrpcBaseInterceptor() {
override val floconGrpcFormatter: FloconGrpcBaseFormatter = FloconGrpcFormatter()
}

View file

@ -35,13 +35,10 @@ android {
}
dependencies {
implementation(project(":flocon-base"))
api(project(":grpc-interceptor-base"))
implementation(platform(libs.kotlinx.coroutines.bom))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.gson)
implementation(libs.grpc.android)
implementation(libs.protobuf.util)
}

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,12 @@
package io.github.openflocon.flocon.grpc
import com.google.protobuf.MessageOrBuilder
import com.google.protobuf.util.JsonFormat
class FloconGrpcFormatter : FloconGrpcBaseFormatter {
private val printer = JsonFormat.printer().alwaysPrintFieldsWithNoPresence()
override fun <T> format(message: T): String =
(message as? MessageOrBuilder)?.let { printer.print(it) } ?: ""
}

View file

@ -1,212 +1,6 @@
package io.github.openflocon.flocon.grpc
import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import io.github.openflocon.flocon.FloconApp
import io.github.openflocon.flocon.FloconLogger
import io.github.openflocon.flocon.grpc.model.RequestHolder
import io.github.openflocon.flocon.grpc.model.toHeaders
import io.github.openflocon.flocon.plugins.network.FloconNetworkPlugin
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkRequest
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkResponse
import io.grpc.CallOptions
import io.grpc.Channel
import io.grpc.ClientCall
import io.grpc.ClientInterceptor
import io.grpc.ForwardingClientCall
import io.grpc.ForwardingClientCallListener
import io.grpc.Metadata
import io.grpc.MethodDescriptor
import io.grpc.Status
import kotlinx.coroutines.runBlocking
import java.util.UUID
class FloconGrpcInterceptor : FloconGrpcBaseInterceptor() {
private val excluded = setOf(
"unknownFields",
"memoizedHashCode",
"bitField",
"memoizedSerializedSize",
"bytes",
)
private val defaultFieldExcluder: (name: String) -> Boolean = { name ->
var isExcluded = false
excluded.forEach { toExclude ->
if (name.startsWith(prefix = toExclude)) {
isExcluded = true
}
}
isExcluded
}
class FloconGrpcInterceptor(
private val shouldExcludeField: (name: String) -> Boolean = defaultFieldExcluder,
) : ClientInterceptor {
private val floconGrpcPlugin = FloconGrpcPlugin()
private val gson = buildGsonInstance(shouldExcludeField)
override fun <ReqT : Any?, RespT : Any?> interceptCall(
method: MethodDescriptor<ReqT, RespT>,
callOptions: CallOptions,
next: Channel,
): ClientCall<ReqT, RespT> {
val networkPlugin = FloconApp.instance?.client?.networkPlugin
if (networkPlugin == null) {
// do not intercept if no network plugin, just call
return next.newCall(method, callOptions)
}
val callId = UUID.randomUUID().toString()
return LoggingForwardingClientCall(
floconGrpcPlugin = floconGrpcPlugin,
floconNetworkPlugin = networkPlugin,
callId = callId,
method = method,
next = next,
callOptions = callOptions,
gson = gson,
)
}
}
private class LoggingForwardingClientCall<ReqT, RespT>(
private val floconNetworkPlugin: FloconNetworkPlugin,
private val floconGrpcPlugin: FloconGrpcPlugin,
private val callId: String,
private val method: MethodDescriptor<ReqT, RespT>,
private val next: Channel,
callOptions: CallOptions,
private val gson: Gson,
) : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
next.newCall(
method,
callOptions,
),
) {
val requestHolder = RequestHolder()
private var headers: Metadata? = null
override fun start(responseListener: Listener<RespT>, headers: Metadata) {
this.headers = headers
super.start(
LoggingClientCallListener(
floconGrpcPlugin = floconGrpcPlugin,
callId = callId,
requestHolder = requestHolder,
responseListener = responseListener,
gson = gson
),
headers,
)
}
override fun sendMessage(message: ReqT) {
val request = FloconNetworkRequest(
url = next.authority(),
method = method.fullMethodName,
body = message?.toJson(gson = gson) ?: "",
startTime = System.currentTimeMillis(),
headers = headers?.toHeaders().orEmpty(),
size = 0, // TODO
isMocked = false, // cannot mock grpc
)
requestHolder.request.complete(request)
floconGrpcPlugin.reportRequest(
callId = callId,
request = request
)
floconNetworkPlugin.badQualityConfig?.let {
executeBadQuality(it)
}
super.sendMessage(message)
}
}
private class LoggingClientCallListener<RespT>(
private val floconGrpcPlugin: FloconGrpcPlugin,
private val callId: String,
responseListener: ClientCall.Listener<RespT>,
private val gson: Gson,
private val requestHolder: RequestHolder,
) : ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(
responseListener,
) {
private var headers: Metadata? = null
private var message: RespT? = null
override fun onClose(status: Status, trailers: Metadata) {
try {
runBlocking { requestHolder.request.await() }.let { request ->
status.cause?.let { cause ->
floconGrpcPlugin.reportResponse(
callId = callId,
request = request,
response = FloconNetworkResponse(
body = null,
headers = emptyMap(),
httpCode = null,
contentType = "grpc",
size = null,
grpcStatus = null,
error = cause.message ?: cause.javaClass.simpleName,
),
)
} ?: run {
floconGrpcPlugin.reportResponse(
callId = callId,
request = request,
response = FloconNetworkResponse(
body = message?.toJson(gson),
headers = (this.headers ?: trailers).toHeaders(),
httpCode = null,
contentType = "grpc",
size = 0L,
grpcStatus = status.code.toString(),
error = null,
),
)
}
}
} catch (t: Throwable) {
FloconLogger.logError("cannot find request for callId $callId", t)
}
super.onClose(status, trailers)
}
override fun onMessage(message: RespT) {
super.onMessage(message)
this.message = message
}
override fun onHeaders(headers: Metadata?) {
super.onHeaders(headers)
this.headers = headers
}
}
private fun buildGsonInstance(
excluder: (name: String) -> Boolean,
): Gson {
return GsonBuilder().setPrettyPrinting()
.setFieldNamingStrategy {
it.name.removeSuffix("_")
}
.setExclusionStrategies(object : ExclusionStrategy {
override fun shouldSkipField(f: FieldAttributes): Boolean {
return excluder(f.name)
}
override fun shouldSkipClass(clazz: Class<*>): Boolean {
return false
}
}).create()
}
private fun Any.toJson(gson: Gson): String = gson.toJson(this)
override val floconGrpcFormatter: FloconGrpcBaseFormatter = FloconGrpcFormatter()
}

View file

@ -2,7 +2,9 @@
:flocon-base:assembleRelease \
:flocon:assembleRelease \
:flocon-no-op:assembleRelease \
:grpc-interceptor-base:assembleRelease \
:grpc-interceptor:assembleRelease \
:grpc-interceptor-lite:assembleRelease \
:okhttp-interceptor:assembleRelease \
:ktor-interceptor:assembleRelease
@ -10,7 +12,9 @@
:flocon-base:publishToMavenLocal \
:flocon:publishToMavenLocal \
:flocon-no-op:publishToMavenLocal \
:grpc-interceptor-base:publishToMavenLocal \
:grpc-interceptor:publishToMavenLocal \
:grpc-interceptor-lite:publishToMavenLocal \
:okhttp-interceptor:publishToMavenLocal \
:ktor-interceptor:publishToMavenLocal \
-Psigning.required=false

View file

@ -21,4 +21,6 @@ include(":flocon")
include(":flocon-no-op")
include(":okhttp-interceptor")
include(":grpc-interceptor")
include(":grpc-interceptor-base")
include(":grpc-interceptor-lite")
include(":ktor-interceptor")