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

[Server] support interface and implementation classes for API controllers #426

Open
wing328 opened this issue Jul 1, 2018 · 11 comments
Open

Comments

@wing328
Copy link
Member

wing328 commented Jul 1, 2018

Description

For auto-generated server code, we want to generate 2 files for each API controller file:

  1. an interface, which will be overwritten by code generation
  2. an implementation class, which will not be overwritten by code generation

Java Spring has already implemented this:
https://github.com/openapitools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache
https://github.com/openapitools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache

The goal is to avoid application/business logic being overwritten by code generation so that there's less overhead when adding/deleting/updating endpoint definition.

Other server generators should leverage similar design.

openapi-generator version

Latest master

Related issues/PRs

swagger-api/swagger-codegen#5431

Suggest a fix/enhancement

If anyone wants to contribute the enhancement, please reply to let us know.

@jmini
Copy link
Member

jmini commented Jul 1, 2018

In my opinion this is called the generation gap pattern


I did not check all the java servers, but for jersey2 this is already the case:
Under src/main/java the stuff you edit (and that you no longer regenerate).
Example: PetApiServiceImpl

The Framework classes (jaxrs) are under src/gen/java.
Example: PetApiService (this is a the parent class of PetApiServiceImpl)

The content of src/gen/java should be regenerated after each changes of the input specification.


For me this works as expected and as described here.

@bjgill
Copy link
Contributor

bjgill commented Jul 2, 2018

rust-server already does this - it generates a standalone library. A server-side user imports the library and implements the API (a Rust trait), whilst a client-side user imports the library and uses the API. The autogenerated code also includes example client/server integrations that users can copy from to make their integration a little easier.

@tnmtechnologies
Copy link
Contributor

For jaxrs-resteay source code generation, jaxrs annotations are set at class level (see api.mustache).

As proposed, jaxrs annotations could be set at interface level. Of course we still need the class implementation which would inherit from the interface.
This interface could also be used for client source code as described in Resteasy documentation (RESTEasy Proxy Framework).

@jmini
Copy link
Member

jmini commented Jul 4, 2018

I seems that having annotation on interface is a jaxrs-resteasy thing. JaxRS-Jersey2 does not seems to support it. See How to annotate JAX-RS on an interface while using Jersey.

@akhaisin
Copy link

akhaisin commented Jul 5, 2018

From what I experienced neither standalone JaxRS-Jersey2 nor Spring Web could not get you having annotations only on interfaces.
Spring project has this ticket for years https://jira.spring.io/browse/SPR-11055
Spring Framework/Web 5.0.7 supports @RequestParam on interfaces, but not @RequestBody nor @PathVariable.

For Jersey2, as mentioned before such approach also does not work.

I was able get such separation of generated code and implementations by combining Jersey2 and Spring.
There is an ext in Jersey project for this - org.glassfish.jersey.ext:jersey-spring4
With org.springframework.boot:spring-boot-starter-jersey setting it up is even easier and less boilerplate is needed. Check example bellow.

I'd like to have Spring Web based only generated code, and not introducing Jersey2 dependencies at all. When Spring team fixes SPR-11055, transition to Spring Web based annotations on interfaces should be easy - almost as just change generator language.

Lets say PetApi.java is kept in separate module/jar. It could be manually created or regenerated from OpenApi every build.
//---------------- PetApi.java

@Path("/pet")
public interface PetApi {
    @POST
    @Consumes({ "application/json", "application/xml" })
    void addPet(Pet pet);

    @GET
    @Path("/{petId}")
    @Produces({ "application/xml", "application/json" })
    Pet getPetById(@PathParam("petId") Long petId);
}

PetApiImpl.java could be kept in separate jar/module and is under source version control.
//---------------- PetApiImpl.java

import org.springframework.stereotype.Component;

@Component
public class PetApiImpl implements PetApi {

    @Override
    public void addPet(Pet pet) {
        // do stuff 
    }
    @Override
    Pet getPetById(Long petId){
        // do stuff
        // return found Pet
    }
}

//---------------- JerseyConfiguration.java

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

@Component
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
        register(PetApiImpl.class);
    }
}

@ybelenko
Copy link
Contributor

I would love to implement this approach in PHP server generator. It was the most frustrating thing when I worked with SwaggerCodegen. You always need to be careful to not override your current progress. I've ended up with condition in router:

if (class_exists('\MyNamespace\Api\UserApi')) {
    // use implementation
} else {
    // use generated stub
}

I would extend your list with:

  1. an interface, which will be overwritten by code generation
  2. mock class, which is skipped by router when mockingEnabled: false
  3. an implementation class, which will not be overwritten by code generation

With this approach we can generate complete mock API right away.

And I think we need to do the same thing with models. ModelInterface -> ModelMock -> ModelImplementation

Dumb question: Does generator create implementation class for the first time? Or maybe better not to do anyting in src folder at all, so user need to create implementation class manually?

@etherealjoy
Copy link
Contributor

cpp-qt5-qhttpengine-server is implementing this for c++. The method createApiHandlers
https://github.com/OpenAPITools/openapi-generator/blob/master/samples/server/petstore/cpp-qt5-qhttpengine-server/server/src/handlers/OAIApiRouter.h#L68

has to be overridden and that the handlers inherited that is all.

@ybelenko
Copy link
Contributor

ybelenko commented Oct 14, 2018

After discussion with @wing328 I decided that abstract class is better than interface for PHP server stubs.
Example with interface:

interface UserApi
{

    /**
     * Operation description
     * 
     * @param ServerRequestInterface $request  Request
     * @param ResponseInterface      $response Response
     * @param array|null             $args     Path arguments
     *
     * @return ResponseInterface
     */
    public function getUserById($request, $response, $arguments);
}

Example with abstract class:

abstract class AbstractUserApi
{

    /**
     * Operation description
     * 
     * @param ServerRequestInterface $request  Request
     * @param ResponseInterface      $response Response
     * @param array|null             $args     Path arguments
     *
     * @return ResponseInterface
     */
    public function getUserById($request, $response, $arguments)
    {
        /// stubs for parsing all request parameters which user can copypaste to implementation
        $userId = $arguments['userId'];        

        /// need to force user to overwrite thit method
        throw new Exception('How about extend AbstractUserApi by \OpenAPIServer\Api\UserApi class implementing getUserById as a GET method?');

        /// or return 501 Not implemented response
        return $request->withStatus(501)->write('How about extend AbstractUserApi by \OpenAPIServer\Api\UserApi class implementing getUserById as a GET method?');
    }
}

Question to community.
What do you think is better by default in just generated stubs, throw an Exception or return 501 Not implemented response ❓

@AgronAerco
Copy link

cpp-qt5-qhttpengine-server is implementing this for c++. The method createApiHandlers https://github.com/OpenAPITools/openapi-generator/blob/master/samples/server/petstore/cpp-qt5-qhttpengine-server/server/src/handlers/OAIApiRouter.h#L68

has to be overridden and that the handlers inherited that is all.

Can you please provide an example?

@kg3634
Copy link

kg3634 commented Oct 20, 2023

Description

For auto-generated server code, we want to generate 2 files for each API controller file:

  1. an interface, which will be overwritten by code generation
  2. an implementation class, which will not be overwritten by code generation

Java Spring has already implemented this: https://github.com/openapitools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache https://github.com/openapitools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache

The goal is to avoid application/business logic being overwritten by code generation so that there's less overhead when adding/deleting/updating endpoint definition.

Other server generators should leverage similar design.

openapi-generator version

Latest master

Related issues/PRs

swagger-api/swagger-codegen#5431

Suggest a fix/enhancement

If anyone wants to contribute the enhancement, please reply to let us know.

Hi @wing328,
I was trying to implement the enhancement. Could you please advice for the below:
I tried it for java spring server with below steps but it is overriding both interface and implementation class.

  1. yaml file without post method
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            maximum: 100
            format: int32
      responses:
        '200':
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Pets:
      type: array
      maxItems: 100
      items:
        $ref: "#/components/schemas/Pet"
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
  1. generated server from below command:
    java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i sample.yaml -g spring -o springserver
  2. added post method in yaml file and generated code(executed same command given in step2).
  post:
      summary: Create a pet
      operationId: createPets
      tags:
        - pets
      responses:
        '201':
          description: Null response
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error" 

As a result : It overwrote interface and implementation class for spring as well.
[test@master-node api]$ pwd
/home/test/openapi-generator/springserver/src/main/java/org/openapitools/api
[testu@master-node api]$ ls
ApiUtil.java PetsApiController.java PetsApi.java

Query: I hope I am trying correct procedure for reproducing the issue, If yes then this is also not implemented for java spring as well, if yes should I try the enhancement for java spring as part of @Hacktoberfest ?

@Philzen
Copy link
Contributor

Philzen commented Dec 1, 2023

@wing328 With 7.1 shipped (which included #16945), this can be closed now, correct?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment