ePages-de / restdocs-api-spec

Adds API specification support to Spring REST Docs
MIT License
386 stars 101 forks source link

Improve failure output to make debugging /failure fixing in tests simpler #144

Open de-jcup opened 3 years ago

de-jcup commented 3 years ago

Origin situation

I accidently defined a wrong type in my test (I used String.class instead of JsonFieldType.STRING) which led to a resources.json containing:

, {
      "attributes" : { },
      "description" : "my text...",
      "ignored" : false,
      "path" : "content[].metaData.xyz",
      "type" : "java.lang.String",
      "optional" : true
    }, {

which leads to gradle failure with:

 Task openapi3 FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task 'openapi3'.
> unknown field type java.lang.string

That was all output.

Of course this is my fault, but to find the problem wasn't easy - at the beginning I did not recognize that the resource.json output inside build/generated-snippets/myTargetFolder is full used to generate openapi parts and there you can find the location of the problem etc. etc.

Wanted

It would be nice to have

ozscheyge commented 3 years ago

Hey @de-jcup ,

thanks for the report and sorry that this caused you trouble!

Yeah, it's an unfortunate "user" (developer) error. By design (using restdocs field descriptors to generate the raw format and using different gradle plugins to generate the respective target formats) there are some constraints for a solution:

Imo, the last point would be overkill. I'd leave this open for further discussion/suggestions, but will eventually close it as "won't do" if no one steps up.

de-jcup commented 3 years ago

Hello @ozscheyge ,

To include test/source line info in the gradle plugin error, we'd need to use reflection/debug symbols to store this information in the resource.json snippet Imo, the last point would be overkill.

I absolutely agree... this would be too much effort - at least for the error handling

Suggestions

Simple show file path

Scope: after resources.json has been generated and users do generate output from it (e.g. openapi3.yaml) - so inside processing existing 'resource.json' files

I haven't looked at the sources exactly and I have no real experience with Kotlin at the moment - but normally the currently fetched resources.json file path should be available while doing generation of output. So when just remembering last read resources.json file and showing the full path in case of error should be possible without much effort?

Show related JSON object as output / hint

Scope: after resources.json has been generated and users do generate output from it (e.g. openapi3.yaml) - so inside processing existing 'resource.json' files

In error case show complete JSON elements from current JSON object. (in my example before this would be

{
      "attributes" : { },
      "description" : "my text...",
      "ignored" : false,
      "path" : "content[].metaData.xyz",
      "type" : "java.lang.String",
      "optional" : true
}

So its more clear for users what resource part is the problem.

Together with the existing error message (+ maybe path information) it should be very easy to locate problematic part. This should be possible without additional debug meta data or reflection usage.

virtualdogbert commented 3 years ago

I'm running into a similar problem right now, but I can't seem to figure out where the issue is orignating from:

Caused by: java.lang.IllegalArgumentException: unknown field type false
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator$FieldDescriptorWithSchemaType.typeToSchema(JsonSchemaFromFieldDescriptorsGenerator.kt:251)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator$FieldDescriptorWithSchemaType.jsonSchemaType(JsonSchemaFromFieldDescriptorsGenerator.kt:206)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator.handleEndOfPath(JsonSchemaFromFieldDescriptorsGenerator.kt:170)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator.access$handleEndOfPath(JsonSchemaFromFieldDescriptorsGenerator.kt:26)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator$traverse$1$1.apply(JsonSchemaFromFieldDescriptorsGenerator.kt:91)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator$traverse$1$1.apply(JsonSchemaFromFieldDescriptorsGenerator.kt:26)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator$traverse$1.accept(JsonSchemaFromFieldDescriptorsGenerator.kt:89)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator$traverse$1.accept(JsonSchemaFromFieldDescriptorsGenerator.kt:26)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator.traverse(JsonSchemaFromFieldDescriptorsGenerator.kt:83)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator.generateSchema(JsonSchemaFromFieldDescriptorsGenerator.kt:32)
    at com.epages.restdocs.apispec.jsonschema.JsonSchemaFromFieldDescriptorsGenerator.generateSchema$default(JsonSchemaFromFieldDescriptorsGenerator.kt:28)
    at com.epages.restdocs.apispec.openapi2.OpenApi20Generator.responseModel2Response(OpenApi20Generator.kt:480)
    at com.epages.restdocs.apispec.openapi2.OpenApi20Generator.resourceModels2Operation(OpenApi20Generator.kt:312)
    at com.epages.restdocs.apispec.openapi2.OpenApi20Generator.resourceModels2Path(OpenApi20Generator.kt:225)
    at com.epages.restdocs.apispec.openapi2.OpenApi20Generator.generatePaths(OpenApi20Generator.kt:182)
    at com.epages.restdocs.apispec.openapi2.OpenApi20Generator.generate$restdocs_api_spec_openapi_generator(OpenApi20Generator.kt:69)
    at com.epages.restdocs.apispec.openapi2.OpenApi20Generator.generateAndSerialize(OpenApi20Generator.kt:95)
    at com.epages.restdocs.apispec.gradle.OpenApiTask.generateSpecification(OpenApiTask.kt:28)
    at com.epages.restdocs.apispec.gradle.ApiSpecTask.aggregateResourceModels(ApiSpecTask.kt:49)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:48)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:41)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:28)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:704)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:671)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:284)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
    at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:273)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:258)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$200(ExecuteActionsTaskExecuter.java:67)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:145)
    at org.gradle.internal.execution.impl.steps.ExecuteStep.execute(ExecuteStep.java:49)
    at org.gradle.internal.execution.impl.steps.CancelExecutionStep.execute(CancelExecutionStep.java:34)
    at org.gradle.internal.execution.impl.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:69)
    at org.gradle.internal.execution.impl.steps.TimeoutStep.execute(TimeoutStep.java:49)
    at org.gradle.internal.execution.impl.steps.CatchExceptionStep.execute(CatchExceptionStep.java:33)
    at org.gradle.internal.execution.impl.steps.CreateOutputsStep.execute(CreateOutputsStep.java:50)
    at org.gradle.internal.execution.impl.steps.SnapshotOutputStep.execute(SnapshotOutputStep.java:43)
    at org.gradle.internal.execution.impl.steps.SnapshotOutputStep.execute(SnapshotOutputStep.java:29)
    at org.gradle.internal.execution.impl.steps.CacheStep.executeWithoutCache(CacheStep.java:134)
    at org.gradle.internal.execution.impl.steps.CacheStep.lambda$execute$3(CacheStep.java:83)
    at org.gradle.internal.execution.impl.steps.CacheStep.execute(CacheStep.java:82)
    at org.gradle.internal.execution.impl.steps.CacheStep.execute(CacheStep.java:36)
    at org.gradle.internal.execution.impl.steps.PrepareCachingStep.execute(PrepareCachingStep.java:33)
    at org.gradle.internal.execution.impl.steps.StoreSnapshotsStep.execute(StoreSnapshotsStep.java:38)
    at org.gradle.internal.execution.impl.steps.StoreSnapshotsStep.execute(StoreSnapshotsStep.java:23)
    at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:96)
    at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:89)
    at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:52)
    at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:36)
    at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:34)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:91)
    ... 32 more

Any ideas on how I can track this down.

ozscheyge commented 3 years ago

Hey @virtualdogbert ,

would you mind posting your resource.json of the snippet which might be affected? You'll find those in your project directory under

build/generated-snippets/{snippetName}/resource.json

If you have plenty of snippets, this grep might help:

grep --include resource.json -rn "type.*:.*false" build/
virtualdogbert commented 3 years ago

I have a lot of snippets, been working hard, but your grep, made me realize why my find wasn't working and now I can track things down. It was a JsonFieldType.array instead of JsonFieldType.ARRAY

After fighting some merge conflicts on the branch this was an easy fix. Thanks.