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 PMforEachCurrentStepmbonnin
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