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

Deserialization issue when getting self service flow #75

Closed
JeffreyThijs opened this issue May 19, 2021 · 7 comments
Closed

Deserialization issue when getting self service flow #75

JeffreyThijs opened this issue May 19, 2021 · 7 comments

Comments

@JeffreyThijs
Copy link

I am getting a deserialization issue when calling getSelfServiceLoginFlow (i think any flow will have this issue though) when using both v0.6.3-alpha.1 of kratos and this java client. Previously, I was using v0.5.4-alpha.1 and everything worked fine and I was now looking to upgrade to v0.6. The payload changed of the flows changed betweem these versions so this might be the cause of the issue. I tried it both with the migrated and a clean database but it made no difference. Any ideas?

Error log:

2021-05-19 10:38:11,486 INFO  [io.alo.ser.KratosService_Subclass] (executor-thread-199) requesting getSelfServiceLoginFlow with flow id: b74da03c-86b4-4ad6-b77c-fbe57323cde4
2021-05-19 10:38:11,506 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-199) HTTP Request to /auth/login?flow=b74da03c-86b4-4ad6-b77c-fbe57323cde4 failed, error id: 6617a238-58c3-4259-b3f2-633fac1cfbd9-1: org.jboss.resteasy.spi.UnhandledException: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 548 path $.ui.nodes[0].attributes.value
        at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
        at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
        at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:218)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:519)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
        at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:138)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:41)
        at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:93)
        at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:231)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at java.base/java.lang.Thread.run(Thread.java:829)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 548 path $.ui.nodes[0].attributes.value
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:226)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
        at com.google.gson.Gson.fromJson(Gson.java:932)
        at com.google.gson.Gson.fromJson(Gson.java:897)
        at com.google.gson.Gson.fromJson(Gson.java:846)
        at sh.ory.kratos.JSON.deserialize(JSON.java:146)
        at sh.ory.kratos.ApiClient.deserialize(ApiClient.java:812)
        at sh.ory.kratos.ApiClient.handleResponse(ApiClient.java:1018)
        at sh.ory.kratos.ApiClient.execute(ApiClient.java:942)
        at sh.ory.kratos.api.AdminApi.getSelfServiceLoginFlowWithHttpInfo(AdminApi.java:895)
        at sh.ory.kratos.api.AdminApi.getSelfServiceLoginFlow(AdminApi.java:872)
        at io.aloxy.services.KratosService.getLoginFlow(KratosService.java:50)
        at io.aloxy.services.KratosService_Subclass.getLoginFlow$$superaccessor3(KratosService_Subclass.zig:527)
        at io.aloxy.services.KratosService_Subclass$$function$$8.apply(KratosService_Subclass$$function$$8.zig:33)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
        at io.aloxy.services.KratosService_Subclass.getLoginFlow(KratosService_Subclass.zig:484)
        at io.aloxy.services.KratosService_ClientProxy.getLoginFlow(KratosService_ClientProxy.zig:128)
        at io.aloxy.resources.KratosResource.browser(KratosResource.java:44)
        at io.aloxy.resources.KratosResource_Subclass.browser$$superaccessor2(KratosResource_Subclass.zig:378)
        at io.aloxy.resources.KratosResource_Subclass$$function$$4.apply(KratosResource_Subclass$$function$$4.zig:33)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
        at io.aloxy.resources.KratosResource_Subclass.browser(KratosResource_Subclass.zig:317)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:408)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:69)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
        ... 18 more
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 548 path $.ui.nodes[0].attributes.value
        at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:386)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:215)
        ... 76 more
@aeneasr aeneasr transferred this issue from ory/kratos-client-java May 19, 2021
@aeneasr
Copy link
Member

aeneasr commented May 19, 2021

Dupe of #74

@aeneasr
Copy link
Member

aeneasr commented May 19, 2021

Shit, I made a long post that explained the issue but apparently I closed the wrong window. Anyways, the gist of it is that polymorphism with simple types is simply extremely buggy in basically all the generators except for Golang (because I fixed it there) and TypeScript. Apparently, the generator expects polymorphism to always be a JSON object 🤷

So what I did was to remove the typing from the value (which means it can be any type) and re-generated the clients. Could you please check if this works in Java (I have no Java experience...). The generated code with the changes can be found here: https://github.com/ory/sdk/tree/openapi-fixy/clients/kratos/java

Thanks!

@JeffreyThijs
Copy link
Author

@aeneasr, thanks for the quick reply. I just quickly tested your fix and the login and registration flow responses are now deserialized correctly! Hopefully, this fix also solves the issues with the clients in other languages. Could you maybe point out what you exactly changed since db67552 contains a lot of changes in the generated files?

@aeneasr
Copy link
Member

aeneasr commented May 20, 2021

Ok that's awesome to hear! The problem is that we had a oneOf in the schema with simple types:

value:
  oneOf:
    - type: string
    - type: number
    - type: boolean

This is unfortunately not supported in most openapi-generator templates. There are quite a few issues on that (https://github.com/OpenAPITools/openapi-generator/search?q=oneof+string&type=issues - not all are relevant but maybe 10%).

What I did was basically removing that type and the leaving "value" type empty which implies that it is a polymorph type (could also be an object or array, which isn't the case actually, but better than something not working at all!)

@aeneasr
Copy link
Member

aeneasr commented May 20, 2021

Wohoo! I found the original tab with my findings


So I looked into this a bit more and I can't seem to come up with a good solution (also I don't have Java skills...). Basically the problem is this.

The value object of node.attributes.value is a polymorph simple type (string, bool, number, null). In OpenAPI Spec we use a ref and oneOf to express that:

          "value": {
            "$ref": "#/components/schemas/uiNodeInputAttributesValue"
          }

      "uiNodeInputAttributesValue": {
        "oneOf": [
          {
            "description": "The input's value.",
            "nullable": true,
            "type": "string"
          },
          {
            "description": "The input's value.",
            "nullable": true,
            "type": "number"
          },
          {
            "description": "The input's value.",
            "nullable": true,
            "type": "boolean"
          }
        ]
      },

Unfortunately we cannot do

      "value": {
        "oneOf": [
          {
            "description": "The input's value.",
            "nullable": true,
            "type": "string"
          },
          {
            "description": "The input's value.",
            "nullable": true,
            "type": "number"
          },
          {
            "description": "The input's value.",
            "nullable": true,
            "type": "boolean"
          }
        ]
      }

as that generates code in Java which would not compile due to a bug in openapi-generator. The interim step with the $ref appears to trick the Java generator into thinking that the element under value is an object while it really is a string, a number, or a boolean. This then causes the deserialization issue!

In Java, the value type is:

  public static final String SERIALIZED_NAME_VALUE = "value";
  @SerializedName(SERIALIZED_NAME_VALUE)
  private UiNodeInputAttributesValue value;

with a simple class

public class UiNodeInputAttributesValue {

backing it. UiNodeInputAttributesValue itself is basically seen as a dumb object instead of polymorphism. After digging through some issues, this appears to be a general problem / limitation of openapi-generator for several languages right now:

So in the end I think it's better to just not have any typing here even though the typing appears to be correct.

aeneasr added a commit to ory/kratos that referenced this issue May 20, 2021
aeneasr added a commit to ory/kratos that referenced this issue Jun 9, 2021
aeneasr added a commit to ory/kratos that referenced this issue Jun 15, 2021
@zhming0
Copy link

zhming0 commented Jun 18, 2021

@aeneasr thanks for the clarifying what is happening. Just so I understand, currently the best option for any user that's not using Golang nor Typescript is to wait for next release?

For the time being, I use db67552 directly, just thinking that maybe users need to be informed about it.

@aeneasr
Copy link
Member

aeneasr commented Jun 18, 2021

That or generate their own client using the openapi-generator and the spec at: https://github.com/ory/kratos/blob/master/spec/openapi.json

The 0.7 release is pretty close though: https://github.com/ory/kratos/milestone/9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants