3

I'm trying to customize the springdoc-openapi, make it can work with my framework, but I meet two problems.

1. How to treat methods that do not start with is/get as properties of Model?

If users use my ORM framework by Java language, the property getters in the entity interface can either start with is/get like a traditional Java Bean, or don't start with is/get like a Java record, for example

@Entity
public interface Book {

   @Id
   long id();

   String name();

   int edition();

   BigDecimal price();

   @ManyToOne
   BookStore store();

   @ManyToMany
   List<Author> authors();
}

Here, the wording that does not start with is/get is used, which looks like a java record, not a traditional java bean.

However, doing this will cause swagger-ui to think that the model doesn't have any attributes. So I have to change the behavior of swagger.

After some research, I found that this behavior can be changed using io.swagger.v3.core.converter.ModelConverter, which is the most likely solution.

However, springdoc-openapi does not explain in detail how to use ModelConverter in the documentation. Ultimately, this goal was not achieved.

2. How to control the shape of dynamic objects in HTTP response?

My ORM is GraphQL-style, its entity objects are dynamic so that data structures of arbitrary shapes can be queried, just like GraphQL does. For example

@RestController
public class BookController {

    @AutoWired
    private JSqlClient sqlClient;

    // Query simple book objects
    @GetMapping("/books")
    public List<Book> books() {
        return sqlClient.getEntities().findAll(Book.class);
    }

    // Query complex book objects
    @GetMapping("/books/details")
    public List<Book> bookDetails() {
        return sqlClient.getEntities().findAll(
            // Like the request body of GraphQL
            BookFetcher$
               .allScalarFields()
               .store(
                   BookStoreFetcher.$.allScalarFields()
               )
               .authors(
                   AuthorFetcher.$.allScalars()
               )
        );
    }
}
  • The first query returns a list of simple book objects in the format {id, name, edition, price}
  • The second query returns a list of complex book objects in the format {id, name, edition, price, store: {id, name, website}, authors: {id, firstName, lastName, gender}}

Dynamic objects can vary in shape, and these are just two special cases.

I expect swgger to tell the client the shape of the object returned by each business scenario. So, I defined an annotation called @FetchBy. It should be used like this

@RestController
public class BookController {

    private static final Fetcher<Book> BOOK_DETAIL_FETCHER = 
          BookFetcher$
               .allScalarFields()
               .store(
                   BookStoreFetcher.$.allScalarFields()
               )
               .authors(
                   AuthorFetcher.$.allScalars()
               );

    @AutoWired
    private JSqlClient sqlClient;

    @GetMapping("/books")
    public List<Book> books() {
        return sqlClient.getEntities().findAll(Book.class);
    }

    @GetMapping("/books/details")
    public List<@FetchBy("BOOK_DETAIL_FETCHER") Book> bookDetails() {
        return sqlClient.getEntities().findAll(BOOK_DETAIL_FETCHER);
    }
}
  1. Declare the shape of the complex object as a static constant.

  2. The @FetchBy annotation uses the constant name to tell swgger the shape of the returned dynamic object.

After some research, I found that this behavior can be changed using org.springdoc.core.customizers.OperationCustomizer, which is the most likely solution.

However, I found that the schema tree of swagger is not consistent with the generic type definition tree in the java language. For example, Spring's ResponseEntity<> wrapper will be ignored by swagger and will be not parsed as a node of schema tree. Theoretically speaking, this ability of swagger can be customized infinitely, so the two trees may not always be consistent and difficult to analyze.

Tao Chen
  • 197
  • 10
  • 1
    I don't know how you solved your problem but I had to create custom converter inherited from `io.swagger.v3.core.converter.ModelConverter` for my case, which was support of some scala types and case classes. It was kinda hard because of lack of documentation and weak internet trail, and I cannot post it as an answer, because it is not the answer to your particular problem, but if you are still battling this just let me know. – dmitry Jan 10 '23 at 08:00
  • @dmitry not the original asker, but I have a use case that requires processing scala case classes too. Would you be able to share some more details on how you did this? – Frank Riccobono Jul 21 '23 at 18:52
  • @FrankRiccobono I implemented ModelConverter for scala classes and created clauses for container types like options and such. Too much code for the comment but I can share what I have in other ways if you still need it – dmitry Jul 24 '23 at 08:16
  • Yes, please do share if you can. – Frank Riccobono Jul 24 '23 at 15:59

0 Answers0