stoicflame / enunciate

Build-time enhancement tool for Java-based Web services projects
http://enunciate.webcohesion.com/
Other
481 stars 200 forks source link

How can subtypes of an abstract return type be included in the output? #1209

Open cw2501 opened 1 month ago

cw2501 commented 1 month ago

Hello, I have a question concerning the documentation of subtypes, which themselves are not being used in the API directly.

This is our setup:

A Spring RestController defines the endpoint

@GetMapping(path = {"/actor/{actorId}"})
public AbstractActorDto getActor(@PathVariable("actorId") UUID actorId)

The AbstractActorDto defines a discriminator field so the type can be determined.

@JsonDeserialize(using = ActorDeserializer.class)
public abstract class AbstractActorDto
{
     private ActorEnum actorType;
     ... 

Problem is well... there's 69 subclasses, some of them multilayered with additional abstracts inbetween 😬 I know of the @JsonSeeAlso annotation which would link the subtypes and lead to their inclusion in the apidocs, but maybe there's a more elegant way of including them all?

Best regards Chris

stoicflame commented 1 month ago

Have you tried the api-classes configuration element?

cw2501 commented 1 month ago

I have. What I forgot to mention in the initial post is that the DTOs reside in a different module than our REST endpoints. I tried including the missing DTOs directly:

<api-classes>
    <include pattern="our.domain.backend.impl.**" />

but that didn't make a difference. If I mention subclasses with @JsonSeeAlso they are included, so it's not a visibility problem.

Our module structure looks like this:

Maybe we already got it wrong with our module configuration?

stoicflame commented 1 month ago

What I forgot to mention in the initial post is that the DTOs reside in a different module than our REST endpoints.

Yeah, that's fine.

You may have to annotate your subclasses with some kind of Jackson annotation just so Enunciate knows it's a JSON data type.

You're building with Maven, yes? Try running with mvn -X (verbose) flag and look for messages like:

our.domain.backen.impl.MyClass isn't a potential Jackson type because (reason).
stoicflame commented 1 month ago

If there's no "reason" that your types are being excluded, then it's probably just that Enunciate can't tell that they're supposed to be JSON types because there's no reference to them as JSON types from any JSON types or endpoints. So you probably just need to add some Jackson annotation so Enunciate knows to include them as JSON types. I'd suggest something like:

@com.fasterxml.jackson.annotation.JsonClassDescription("description of class here")
public class MyDto {
  //...
}
cw2501 commented 1 month ago

I'll give that a try tomorrow. Thank you for your help!

cw2501 commented 1 month ago

Hi Ryan, I've given your suggestion a try but I'm afraid there's no change in the behaviour.

I have referenced the file directly <include pattern="our.domain.backend.impl.planning.actor.dto.ArrowDto" />

and added the annotation @JsonClassDescription to the dto

package our.domain.backend.impl.planning.actor.dto;
...
@Data
@JsonClassDescription("description of class here")
public class ArrowDto extends AbstractActorDto

I've then run the maven build with the -X option but strangely enough the above DTO is not being mentioned anywhere in the log and I'm kind of stuck now. Might this have anything to do with the AbstractActor's custom deserializer? @JsonDeserialize(using = ActorDeserializer.class)? But then... as soon as I use the @JsonSeeAlso, the DTO appears in the apidocs as expected.

cw2501 commented 1 month ago

I unleashed the debugger and figured out, that Enunciate.loadApiReflections(...) only returned entries from the sourcePath, but none from the classPath.

It seems that our api-classes exclude pattern was responsible for this. Our strategy was to exclude everything with <exclude pattern="**.*" /> and then add includes pointed at our own packages.

This seemed valid, as the wiki entry 'Excluding Including Classes' states "Note that includes take precedence over excludes". Is this no longer valid or did we misinterpret its meaning?