feathersjs-ecosystem / feathers-swagger

Add documentation to your FeatherJS services and feed them to Swagger UI.
MIT License
225 stars 63 forks source link

Provide two [req/res] schema for different operations #183

Closed bwgjoseph closed 4 years ago

bwgjoseph commented 4 years ago

Hi,

Wondering if it's possible to provide two schema (request and response) to the schemasGenerator or docs or model to automatically use the provided schema for every service req/res.

My current setup is something like this...

class Test extends Service<TestData> implements ServiceSwaggerAddon {
  constructor(options: Partial<MongooseServiceOptions>, app: Application) {
    super(options);
  }

  docs: ServiceSwaggerOptions = {
    description: 'test',
  };

  model = {
    title: 'test',
    description: 'test desc',
    type: 'object',
    required: [
      'text',
    ],
    properties: {
      text: {
        type: 'string',
        description: 'The message text2',
        uppercase: true,
      },
      userId: {
        type: 'string',
        description: 'The id of the user that send the message',
      },
      testInfo: {
        $ref: 'common.json#/definitions/testInfo',
      },
    },
  };
}
app.configure(
  swagger({
    openApiVersion: 3,
    docsPath: '/swagger/docs',
    docsJsonPath: '/swagger/jsondocs',
    uiIndex: true,
    specs: {
      info: {
        title: 'A test',
        description: 'A description',
        version: '1.0.0',
      },
    },
    defaults: {
      schemasGenerator(service, model, modelName) {
        return {
          [model]: service.model,
          [`${model}_list`]: {
            title: `${modelName} list`,
            type: 'array',
            items: {
              $ref: `#/components/schemas/${model}`,
            },
          },
        };
      },
    },
  }),
);

Doing so, will give this...

image

Which is good but what I would like to achieve is that I am able to indicate both model or something to indicate the responseBody for all.

Is the only way to do it is to customize each service definition to something like this? schema and schemaRes are being defined in another place and imported in.

docs: ServiceSwaggerOptions = {
    description: 'test',
    schemas: {
      test: schema,
      testResponse: schemaRes,
    },
    operations: {
      all: {
        'requestBody.content': {
          'application/json': {
            schema: {
              $ref: '#/components/schemas/test',
            },
          },
        },
        'responses.200.content': {
          'application/json': {
            schema: {
              $ref: '#/components/schemas/testResponse',
            },
          },
        },
      },
    },
  };

I guess I cannot apply to all as well. I probably have to do for each of the operations - find, get, post, etc to indicate the respective request and response schema. Am I right? Or is there a better way to do this?

The reason for this is that usually, the request and response schema are different, and by default, feeding in one schema for both will make it confusing for consumer, right?

So in /get, you will see like this.

image

In /post, you will see this. Note, the request and response are different schema.

image

And so on for the rest if necessary...

So without having to manually define each and every operations of the request and response, I provide the request and response schema, and each of the operations will know which one to use and display?

Hope I'm making myself clear... Will provide more information if required.

Mairu commented 4 years ago

Hi,

so there is the possibility to define the response and request schemas that should be used. For that, you can use the refs option of the service or create a getOperationsRefs (generator) ( https://github.com/feathersjs-ecosystem/feathers-swagger/blob/master/lib/openapi.js#L95 ) next to the schemasGenerator to dynamically create them with some options from the service.

So I guess you are looking for something like this:

app.configure(
  swagger({
    openApiVersion: 3,
    docsPath: '/swagger/docs',
    docsJsonPath: '/swagger/jsondocs',
    uiIndex: true,
    specs: {
      info: {
        title: 'A test',
        description: 'A description',
        version: '1.0.0',
      },
    },
    defaults: {
      getOperationsRefs(model, service) {
        const modelList = `${model}_list`;
        const modelRequest = `${model}_request`;
        // you only have to return those, that you want to change (from the default ones), so the request ones would be enough. I list all here for reference
        return {
          findResponse: modelList,
          getResponse: model,
          createRequest: modelRequest,
          createResponse: model,
          updateRequest: modelRequest,
          updateResponse: model,
          updateMultiRequest: modelList,
          updateMultiResponse: modelList,
          patchRequest: modelRequest,
          patchResponse: model,
          patchMultiRequest: modelRequest,
          patchMultiResponse: modelList,
          removeResponse: model,
          removeMultiResponse: modelList,
          filterParameter: model,
          sortParameter: ''
        };
      },
      schemasGenerator(service, model, modelName) {
        return {
          [model]: service.model,
          [`${model}_request`]: service.model, // create a different model for requests
          [`${model}_list`]: {
            title: `${modelName} list`,
            type: 'array',
            items: {
              $ref: `#/components/schemas/${model}`,
            },
          },
        };
      },
    },
  }),
);

If only needed for some specific services, then you could just use the the refs option. (Example: https://github.com/feathersjs-ecosystem/feathers-swagger/blob/master/example/openapi-v3/customMethods.js#L100 )

bwgjoseph commented 4 years ago

Thanks! But I'm still some trouble getting it to display the correct output. Not sure which portion I got it wrong.

So I have this as the class...

class Test extends Service<TestData> implements ServiceSwaggerAddon {
  constructor(options: Partial<MongooseServiceOptions>, app: Application) {
    super(options);
  }

  docs: ServiceSwaggerOptions = {
    description: 'test',
  };

  // Request
  model = schema;

  // Response
  responseModel = schemaRes;
}

Then swagger configuration as such..

app.configure(
  swagger({
    openApiVersion: 3,
    docsPath: '/swagger/docs',
    docsJsonPath: '/swagger/jsondocs',
    uiIndex: true,
    specs: {
      info: {
        title: 'A test',
        description: 'A description',
        version: '1.0.0',
      },
    },
    defaults: {
      getOperationRefs(model, service) {
        const modelList = `${model}_list`;
        const modelResponse = `${model}_response`;

        return {
          findResponse: modelList,
          getResponse: model,
          createRequest: model,
          createResponse: modelResponse,
        };
      },
      schemasGenerator(service, model, modelName) {
        return {
          [model]: service.model, // request
          [`${model}_response`]: service.responseModel,
          [`${model}_list`]: {
            title: `${modelName} list`,
            type: 'array',
            items: {
              $ref: `#/components/schemas/${model}_response`,
            },
          },
        };
      },
    },
  }),
);

But I'm getting the same request and response within POST

image

image

Any idea where I did wrongly?

Mairu commented 4 years ago

You used getOperationRefs, which misses an s. It has to be getOperationsRefs.

bwgjoseph commented 4 years ago

This is the typescript definition

image

However, I do see the code is written with s

https://github.com/feathersjs-ecosystem/feathers-swagger/blob/2992aa3850e261dc653d6fd603907d04e8edff53/lib/openapi.js#L93

Not sure if this is the issue.

Mairu commented 4 years ago

It is an error in the typescript definition then, that has to be fixed.

Has it worked with the s?

bwgjoseph commented 4 years ago

Yup, seem like it...

I went to change to include s in the index.d.ts

image

And I get back the correct result

image

Not sure if getOperationArgs is intended to be without s, maybe you want to keep it consistent?

Mairu commented 4 years ago

I think I had something in mind using different namings, but it looks kind of odd.

The one will/can create refs for multiple operations, the other one will create operation arguments, used by the generators for the operations.

Changing anything would be a BC break, I think just fix the typescript definition is sufficient.

bwgjoseph commented 4 years ago

Agreed. Hopefully, the release will come soon.

Thanks.

Mairu commented 4 years ago

Fixed the typings of getOperationArgs in 1.1.1