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

Configurable response types/formats #436

Open
bajtos opened this Issue Jul 13, 2017 · 14 comments

Comments

Projects
None yet
10 participants
@bajtos
Member

bajtos commented Jul 13, 2017

At the moment, the return value of a controller method/router handler is converted to the response using the following hard-coded algorithm:

  • Object types (including arrays, but also Date, RegExp, Buffer) are converted to JSON and sent with "application/json" content-type.
  • String results are sent as-is with "text/plain" content-type.
  • Anything else is passed to response.write() as-is and no content-type is set.

We need a more flexible setup, we should at least honor "produces" configuration from the API specification.

Acceptance Criteria

  1. A controller method should be able to specify media type for the responses object with @operation. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#response-object

  2. A controller method should be able to return JS representation of the response body or a wrapper corresponding to the response objects.

  3. LoopBack should be able to match the response media types to the Accept request header to determine what content type to be produced for a given request. It will set Content-Type response header.

  4. Have built in support for application/json and text/plain media-types.

EDIT: Move to another task

  1. LoopBack runtime should define an extension point to register http response serializers that can be used to serialize the JS plain/wrapper object into a http response. A serializer should be able to handle certain media types, for example, json, xml, or protocol buffer. The serializer just has to deal with the http response object as a canonical representation.
@raymondfeng

This comment has been minimized.

Member

raymondfeng commented Jul 13, 2017

We probably should start to refactor https://github.com/strongloop/loopback-next/tree/master/packages/repository/src/types into its own package to form the LB.next typing system and leverage it for parameter parsing/conversion/serialization.

@dhmlau dhmlau added the Core-GA label Aug 23, 2017

@bajtos bajtos added the non-MVP label Nov 2, 2017

@bajtos

This comment has been minimized.

Member

bajtos commented Nov 2, 2017

Labelling as non-MVP, because we have a simple workaround available - users can use their own implementation of send sequence action.

@dustinbarnes

This comment has been minimized.

dustinbarnes commented Dec 4, 2017

@bajtos can you give an example of that? I'm currently fighting the content-type header (it really needs to send application/json when we return a single string), and using the sugar for @get(path, spec), but it seems to ignore whatever is put into the "produces" section of the spec object.

@bajtos

This comment has been minimized.

Member

bajtos commented Dec 5, 2017

@dustinbarnes Take a look at the default implementation of send() here and here.

Here is what you need to do:

  • Create your own provider returning a custom "send" action - you can start by copying our send provider and writeResultToResponse function.
  • Register your custom "send" action - I am not entirely sure how to do that now that REST server have been decoupled from the Application object. @kjdelisle could you please advise and ideally update our docs to show how to do this?
@kjdelisle

This comment has been minimized.

Contributor

kjdelisle commented Dec 5, 2017

@bajtos @dustinbarnes
Registering your custom send action involves injecting on the RestBindings.SequenceActions.SEND key with a custom Provider for Send.

application.ts

export class YourApp extends RepositoryMixin(Application) {
  constructor() {
    super();
    // Assume your controller setup and other items are in here as well.
    this.bind(RestBindings.SequenceActions.SEND).toProvider(CustomSendProvider);
  }

custom-send-provider.ts

import { Send } from "@loopback/rest";
import { Provider, BoundValue } from "@loopback/context";
import { writeResultToResponse } from "@loopback/rest";

export class CustomSendProvider implements Provider<BoundValue>{
  value(): Send | Promise<Send> {
    return writeResultToResponse; // Replace this with your own implementation.
  }
}

If you're implementing multiple servers in your application, binding this key at the Application-level context will make that binding the default for all of the servers connected to it. This means that if you wanted different custom providers for different RestServer instances, you'd want to perform those bindings elsewhere (like in an async override of the start method).

In this example, PrivateSendProvider might allow stack traces to be returned via the response, whereas PublicSendProvider will not.

in application.ts

async start() {
   const publicServer = await this.getServer('public');
   const privateServer = await this.getServer('private');
   publicServer.bind(RestBindings.SequenceActions.SEND).toProvider(PublicSendProvider);
   privateServer.bind(RestBindings.SequenceActions.SEND).toProvider(PrivateSendProvider);
   await super.start(); // Don't forget to call start!
}
@bajtos

This comment has been minimized.

Member

bajtos commented Jan 15, 2018

I created a follow-up issue to capture the instructions from Kevin in our docs - see #863.

@dhmlau

This comment has been minimized.

Contributor

dhmlau commented Mar 22, 2018

Does it fall under the "Validation and type conversion" epic #755 ?

@dhmlau dhmlau referenced this issue Mar 22, 2018

Closed

Monthly Milestone - May 2018 #1172

16 of 23 tasks complete
@bajtos

This comment has been minimized.

Member

bajtos commented Mar 23, 2018

I don't think so. As I understand #755, it deals with input parameters. This story deals with outputs.

I think the best epic to assign this story to is an epic for implementing a local in-process API Explorer and/or a capability to server static files.

@shimks

This comment has been minimized.

Contributor

shimks commented Apr 30, 2018

@bajtos Could we get an acceptance criteria for this issue? Thanks

@shimks

This comment has been minimized.

Contributor

shimks commented May 9, 2018

@bajtos ^^^

@raymondfeng

This comment has been minimized.

Member

raymondfeng commented May 14, 2018

Acceptance criteria:

  1. A controller method should be able to specify media type for the responses object with @operation. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#response-object
description: A complex object array response
content: 
  application/json:
    schema: 
      type: 
        $ref: '#/components/schemas/Customer'
  1. A controller method should be able to return JS representation of the response body or a wrapper corresponding to the response objects. For example:
export class CustomerController {

  @get('/:id', {responses: {...}})
  async findById(id: string): Customer {
    return await repo.findById(id);
  }
}

or

export class CustomerController {

  @get('/:id', {responses: {...}})
  async findById(id: string): RestResponse {
    return {
      statusCode: 200,
      headers: {...};
      content: {
        'application/json': customer,
      },
      links: {...};
    }
  }
}
  1. LoopBack should be able to match the response media types to the Accept request header to determine what content type to be produced for a given request. It will set Content-Type response header.

  2. LoopBack runtime should define an extension point to register http response serializers that can be used to serialize the JS plain/wrapper object into a http response. A serializer should be able to handle certain media types, for example, json, xml, or protocol buffer. For example:

serialize(ctx: RequestContext, contentType: string, responseObject) {
  // ...
}
@virkt25

This comment has been minimized.

Contributor

virkt25 commented May 15, 2018

Great acceptance criteria @raymondfeng. Just a discussion point ... I think we need to provide a way to provide some defaults for some formats if possible so the user doesn't need to define these for every operation. I'm thinking the defaults would be text/html / application/json ... Maybe octet-stream / xml as well?

Or perhaps the extension system you describe can generate the response in the format requested and if the header isn't set then we default to application/json and/or provide the user an option to set an Application wide response format to use unless otherwise requested.

@jannyHou

This comment has been minimized.

Contributor

jannyHou commented Jul 20, 2018

Just a discussion point ... I think we need to provide a way to provide some defaults for some formats if possible so the user doesn't need to define these for every operation.

👍 +1

@bajtos

This comment has been minimized.

Member

bajtos commented Jul 23, 2018

According to #1449, futher REST layer improvements #1452 are out of scope of 4.0 GA.

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