Stylianos Gakis
03/08/2024, 3:38 PMtype Flow {
id: ID!
currentStep: FlowStep!
progress: FlowProgress
context: FlowContext!
}
I wanted to build an interceptor to simply observe any request that may ever return Flow
, and take the currentStep
from inside of it to log it for some specific reasons. Does this sound possible?Stylianos Gakis
03/08/2024, 3:38 PMpublic class Flow {
public companion object {
public val type: ObjectType = ObjectType.Builder(name = "Flow").build()
}
}
I thought maybe this was a job for “alwaysGenerateTypesMatching” but with no luck. That just updated the builder definition, like
public class FlowBuilder(
customScalarAdapters: CustomScalarAdapters,
) : ObjectBuilder(customScalarAdapters) {
public var id: String by __fields
public var currentStep: FlowStepMap by __fields
...
Is there a way for me to be able to do something like
class ClaimFlowFinishedApolloInterceptor : ApolloInterceptor {
override fun <D : Operation.Data> intercept(
request: ApolloRequest<D>,
chain: ApolloInterceptorChain,
): Flow<ApolloResponse<D>> {
return chain.proceed(request).onEach { apolloResponse ->
apolloResponse.data?.safeCast<schema.type.Flow>()?.let {
it.currentStep.also { log it here }
}
}
}
}
Or am I thinking of this completely wrong somehow?Stylianos Gakis
03/08/2024, 3:38 PMmbonnin
03/08/2024, 3:39 PMalwaysGenerateTypesMatching
are very slim and are mainly there to access __typename
in a type safe wayStylianos Gakis
03/08/2024, 3:39 PMfragment ClaimFlowStepFragment on Flow {
currentStep {
...
So I think that it looks like I can do a cast as apolloResponse.data?.safeCast<ClaimFlowStepFragment>()?.let {
instead, and then I seem to have access to it 👀
Because on that fragment I am explicitly asking for currentStep
, so the right type is genrated for membonnin
03/08/2024, 3:40 PMmbonnin
03/08/2024, 3:40 PMmbonnin
03/08/2024, 3:41 PMThis only works with responseBased codegenapolloResponse.data?.safeCast<ClaimFlowStepFragment>
mbonnin
03/08/2024, 3:41 PMStylianos Gakis
03/08/2024, 3:41 PMcom.apollographql.apollo3.compiler.MODELS_RESPONSE_BASED
👀mbonnin
03/08/2024, 3:41 PMStylianos Gakis
03/08/2024, 3:42 PMmbonnin
03/08/2024, 3:42 PMmbonnin
03/08/2024, 3:42 PMdata
into a fragmentmbonnin
03/08/2024, 3:43 PMmbonnin
03/08/2024, 3:43 PMmbonnin
03/08/2024, 3:44 PMresponseBased
, you can't intercept nested fieldsmbonnin
03/08/2024, 3:46 PMcurrentStep
?
query {
myFlow {
progress
# no currentStep here
}
}
mbonnin
03/08/2024, 3:47 PMJsonReader
implementation that checks __typename: "Flow"
and logs currentStep
if presentmbonnin
03/08/2024, 3:47 PMStylianos Gakis
03/08/2024, 3:48 PMflowClaimFooNext(input: FlowClaimFooInput!): Flow!
flowClaimBarNext(input: FlowClaimBarInput!): Flow!
flowClaimBazNext(input: FlowClaimBazInput!): Flow!
and so on for 10+ of them.
And in our context all of them try to get the same ...ClaimFlowStepFragment
out of it.
So maybe through all those caveats and limitations if all 3 are true:
• this being response_based
• not nested
• has to use the fragment
then it should work 😄mbonnin
03/08/2024, 3:49 PMStylianos Gakis
03/08/2024, 3:49 PMval data: D?,
, where would I get the opportunity to do the Json reading instead?mbonnin
03/08/2024, 3:51 PMData
instance is parsed from a JsonReader
. We could add APIs to customize that JsonReader
so that side effects like logging are possiblembonnin
03/08/2024, 3:51 PMData
is constructed from itStylianos Gakis
03/08/2024, 3:52 PMdata
type right?mbonnin
03/08/2024, 3:52 PMApolloClient
global thingmbonnin
03/08/2024, 3:53 PMJsonReader
per-ApolloCall
actuallymbonnin
03/08/2024, 3:53 PMmbonnin
03/08/2024, 3:53 PMapolloClient.query(myQuery)
.jsonReader(LoggingJsonReader)
.execute()
mbonnin
03/08/2024, 3:54 PMApolloClient.Builder()
.jsonRreader(LoggingJsonReader)
.serverUrl(...)
.build()
Stylianos Gakis
03/08/2024, 3:58 PMStylianos Gakis
03/08/2024, 4:01 PMClaimFlowStepFragment
back from mutations should not affect the way I write the interceptor I suppose, right?mbonnin
03/08/2024, 4:01 PMStylianos Gakis
03/08/2024, 4:04 PMdata
actually, isn’t that always just Mutation.Data
for me, but I need to go 1 level deep in order to get what I want here?
But I can’t quite do that in a generic way right?mbonnin
03/08/2024, 4:05 PMdata.flowClaimBazNext
🤔mbonnin
03/08/2024, 4:06 PMStylianos Gakis
03/08/2024, 4:06 PMmbonnin
03/08/2024, 4:07 PMmbonnin
03/08/2024, 4:07 PMStylianos Gakis
03/08/2024, 4:08 PMmbonnin
03/08/2024, 4:08 PMStylianos Gakis
03/08/2024, 4:09 PMStylianos Gakis
03/08/2024, 4:10 PMmbonnin
03/08/2024, 4:10 PMStylianos Gakis
03/08/2024, 4:12 PMmbonnin
03/08/2024, 4:12 PMclass FlowClaimBazNext(currentStep: CurrentStep, id: String) {
init {
println(currentStep)
}
}
mbonnin
03/08/2024, 4:13 PMStylianos Gakis
03/08/2024, 4:15 PMStylianos Gakis
03/08/2024, 4:16 PMmbonnin
03/08/2024, 4:16 PMJsonReader
route, you'll need __typename
so make sure to include it in the fragment or set addTypename.set("alwaus")
)mbonnin
03/08/2024, 4:17 PMmbonnin
03/08/2024, 4:18 PMmbonnin
03/08/2024, 4:19 PMresponse.data.visit { if (it) is FlowFragment { doStuff() }}
but that sounds even more involved than all previous versions 😅mbonnin
03/08/2024, 4:20 PMdata.normalize()
and check the records there for a record with __typename: Flow
, this is probably the easiest waymbonnin
03/08/2024, 4:20 PMmbonnin
03/08/2024, 4:22 PMFlow
and have typesafe code that knows where to look them upmbonnin
03/08/2024, 4:24 PMfun Data.forEachCurrentStep(block: (currentStep) -> Unit) {
when (this) {
is FlowClaimBarNext -> block(data.flowClaimBarNext.currentStep)
is FlowClaimBazNext -> block(data.flowClaimBazNext.currentStep)
// ....
}
}
mbonnin
03/08/2024, 4:25 PMapollo-ast
+ kotlinpoet
exercise + it's all typesafe and very small runtime penalty. If I wanted to automate this, this is probably the route I'll takembonnin
03/08/2024, 4:30 PMStylianos Gakis
03/08/2024, 4:31 PMclass ClaimInterceptor(
private val selfServiceCompletedEventManager: SelfServiceCompletedEventManager,
) : ApolloInterceptor {
override fun <D : Operation.Data> intercept(
request: ApolloRequest<D>,
chain: ApolloInterceptorChain,
): Flow<ApolloResponse<D>> {
return chain.proceed(request).onEach {
it.dataAssertNoErrors.forEachCurrentStep {
if (it.currentStep.asFlowClaimSuccessStep() != null) {
selfServiceCompletedEventManager.completedSelfServiceSuccessfully()
}
}
}
}
}
inline fun <D : Operation.Data> D.forEachCurrentStep(block: (ClaimFlowStepFragment) -> Unit) {
when (this) {
is FlowClaimConfirmEmergencyMutation.Data -> block(this.flowClaimConfirmEmergencyNext)
else -> {}
}
}
Already looks like it would run. So your suggestion would be to get that forEachCurrentStep
function auto-generated so that I don’t need to keep track that I am not missing out on any of the possible types?mbonnin
03/08/2024, 4:31 PMcurrentStep
areStylianos Gakis
03/08/2024, 4:31 PMmbonnin
03/08/2024, 4:32 PMYou said that this should only be possible with 4.x right?Actually I think you can do that in v3. You don't need Apollo compiler plugins for this since it does not touch the existing models, it's purely additive
mbonnin
03/08/2024, 4:33 PMforEachCurrentStep
mbonnin
03/08/2024, 4:34 PMApolloCompilerPlugin
to expose the GraphQL AST so you don't pay the parsing price twice but unless you have a looooot of operations, parsing should be too expensivembonnin
03/08/2024, 4:36 PMJsonReader
and ApolloCompilerPlugin.onAst(graphqlAst)
APIs. If you find you need them or anything else, do not hesitate to open an issueStylianos Gakis
03/08/2024, 4:36 PMmbonnin
03/12/2024, 1:05 PMStylianos Gakis
03/12/2024, 1:55 PM