-
Notifications
You must be signed in to change notification settings - Fork 507
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
Controllers have state #130
Comments
You can have this with Dependency Injection / IoC. In the Inversify example we have:
Add this:
And decorate your controller this way:
Every Controller get's created per call. Please note - because of current limitations of tsoa you have to decorate all controllers. |
@egandro sure, but my proposal is to make controllers stateless. Currently it works something like: class Controller extends TsoaController {
public action() {
this.setStatus(200); // storing status in magical place
return {
name: 'John Doe',
count: 5,
};
}
}
const controller = new Controller();
const body = controller->action();
const status = controller.getStatus(); // retrieving status from magical place
response.status(status).send(JSON.stringify(body)); I propose to make it work like: class Controller extends TsoaController {
public action() {
return {
status: 200,
body: {
name: 'John Doe',
count: 5,
},
};
}
}
const controller = new Controller();
const response = controller->action();
response.status(response.status).send(JSON.stringify(response.body)); Advantage is that action returns required data for response (instead of retrieving part of them and storing other part in some magical place). So as for me this is more clear + it's less error prone (see my previous post). If you don't like my idea, feel free to close the ticket. Reason I created it is not that I couldn't make it work, reason is that I think it could work in better way. (as a side note, #94 is about wrong status code in some cases. As status code would be part of the response here, there would be no way you could have such a bug and reading status before resolving the promise...). |
Ok I understand what you like. A sort of Return Type (derived from an interface). If this interface contains status and body it will raw-pass this to the result. I like this approach. We also need this the Respond() Attribute is used on the method and this differs from the Return value. Question. Would it be ok for you to have it in this way?
|
yes, that looks awesome. |
Let's discuss 2 more things.
Any ideas on this? |
Do you have some concrete examples in mind? Status and body are required for each response probably, if user wants to have other fields (like headers, ...) they can create their own compatible interface and override the template generating the handler code. Extending of the handlebar template could be made easier probably. I am not familiar with handlebars, but there seems to be way to define blocks https://www.npmjs.com/package/handlebars-extend-block so instead of copy pasting the whole template and changing some parts, template could be split into blocks and user could then extend the base template and override only code handling the response (so on updates, they would have latest changes in their template except the block they changed ofc.
That would be nice. I guess the code could look something like: // -- code in tsoa --
interface TsoaResponse<T> {
status: number;
body: T;
}
// somewhere in the handler
new Controller()
.action()
.then((res: TsoaResponse<any>) => // let's make sure that user returns compatible interface
response.status(res.status).send(JSON.stringify(res.body))
);
// -- user code --
@DefaultGlobalErrorResponse('400')
interface ErrorResponse {
status?: string;
code?: string;
title?: string;
}
/**
* This interface is created by user so that he doesn't have to type
* TsoaResponse<ErrorResponse | UserResponse> each time.
*/
interface CustomTsoaResponse<T> {
status: number;
body: T | ErrorResponse;
}
interface UserResponse {
name: string;
count: number;
}
let successResponse: CustomTsoaResponse<UserResponse> = {
status: 200,
body: {
name: 'John Doe',
count: 5,
},
};
let errorResponse: CustomTsoaResponse<ErrorResponse> = {
status: 400,
body: {
title: 'Bad request',
},
};
class Controller extends TsoaController {
public async action(): Promise<CustomTsoaResponse<UserResponse>> {
return res.isSuccess() ? successResponse : errorResponse;
}
} ? |
Hello Nenadalem, I had the same Idea as you while taking a shower :) Extending the Controller with a default Error reporter. This solves the issue with the Base Interface for the Result. Nice one! We have to figure our a way to tell that the swagger file, as I am working with Client Code Generators that are swagger based. From point of view there is no swagger specification for the default error. We might (!) add this to the global Description Field in the swagger File. %%DefaultErrorClass%% NamespaceOfIt.DefaultErrorClass Let's carefully think before implementing this ;) |
As I see my example now class Controller extends TsoaController should be just class Controller (without extending the TsoaController as it is no longer needed - I forgot to remove it when copy pasting)
As I was thinking about it - I thought it would be added into each response: {
"paths": {
"$path": {
"$method": {
"responses": {
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/ErrorResponse"
}
}
}
}
}
}
} where Putting @DefaultGlobalErrorResponse('400', 'Bad request')
interface ErrorResponse {
status?: string;
code?: string;
title?: string;
}
class Controller {
public async action(): Promise<CustomTsoaResponse<UserResponse>> {
// ...
}
@Response<ErrorResponse>('400', 'Overriden message')
public async action2(): Promise<CustomTsoaResponse<UserResponse>> {
// ...
}
} would then generate responses for actions
|
What about Promise<CustomTsoaResponse> can also become
e.g. throw new Error("the error") we might need some
|
I guess that In which case you need to override the handler
So in case current handler would look like (taken from my previous example): // somewhere in the handler
new Controller()
.action()
.then((res: TsoaResponse<any>) => // let's make sure that user returns compatible interface
response.status(res.status).send(JSON.stringify(res.body))
); Your custom handler for // somewhere in the handler
new Controller()
.action()
.customThen((res: TsoaResponse<any>) => // let's make sure that user returns compatible interface
response.status(res.status).send(JSON.stringify(res.body))
); (or whatever else - notice the see #130 (comment) ( As for the interface TsoaResponse<T> {
status: number;
body: T;
}
interface UserResponse {
name: string;
count: number;
} You need to return it in following shape:
What about them? In my current project, I am not throwing uncaught errors in tsoa controllers (not intentionally at least). I am converting the into ErrorResponse and return is as js object (same way as I would return successful response). I am using import {RegisterRoutes} from 'pathToTheDir/routes';
import * as express from 'express';
// generated router
export const apiRouter = express.Router();
// registering routes to the apiRouter
RegisterRoutes(apiRouter);
// custom middleware to handle errors extending base error
// which I use for showing errors
apiRouter.use((err, req, res, next) => {
if (err instanceof ApiError) {
res.status(err.status).send(err.message);
} else {
next(); // we don't know what to do with the error - let's do nothing about it then, and leave it to next error handler
}
}); and then I can register my router in the index file or where I created the express app: import {apiRouter} from './router/api';
import * as express from 'express';
const app = express();
app.use('/api/v1', apiRouter); // now let's use the previously created router for api
// ... so as for me there is no need to add additional code to handle errors somehow (anybody can do that easilly using middleware or whatever else their routing library is using). In your case, the error would be object extending |
I'll try to look at it and send PR. |
Yes also agree with that - I have this middleware errorhandler ;) Let me print this page when I arrive in my office. Let's make a branch and try this! Do you have Write Access to this Repo? |
@egandro I don't have write access. |
I'll ask Luke. |
Done but it might take a day or two. |
Luke just gave you write access. Lets make a branch for this. |
thanks. I've created new pr #132 and closed old one. |
Looks like this was resolved with the linked PR - if this is still an issue let me know. |
If I want to set response code, I have to extend tsoa controller which have method
setStatus
. This requires controller to be instantiated on each request and leads to bugs if somebody forgets to do that (e.g. by specifying wrong scope in dependency injection container.Wouldn't it be better if status and possibly other values were returned by the action so that controllers could be stateless? You could get inspiration from ring-clojure https://github.com/ring-clojure/ring/wiki/Concepts#responses.
So the response could look like:
?
The text was updated successfully, but these errors were encountered: