adwhit / hsr

Fast APIs, fast
MIT License
17 stars 5 forks source link

Discussion: Amazing project #6

Open diegopy opened 4 years ago

diegopy commented 4 years ago

Hi,

I've been using FastAPI for Python and now that I'm using Rust more I was looking for similar frameworks. I've been playing with hsr and so far seems pretty good. It actually uses the opposite direction than FastAPI. There you annotate your code using Python decorators and type hints and FastAPI fully generates the openapi v3 schema from it, optionally embedding a SwaggerUI in the final runtime to have nice things like "Try it out" to test the API. hsr went for the codegen approach. I'm wondering if you considered both approaches and what where the reasons that made you go for codegen. I'm not sure myself which workflow is better, although I'd like to think that having the code as the single "source of truth" and generating everything from it has some appeal.

Thanks again for this project! Looking forward to hopefully contribute when I start using it.

adwhit commented 4 years ago

Hi, thanks for your comments.

I do intend to write a section along the lines of "Why does this exist??". Have been trying to improve the documentation over last few days. When I'm happy with it I'll do some kind of announcement.

To answer your question, I too have come from the python ecosystem and over the years I have settled on connexion as my preferred way of writing APIs. This project is basically my attempt to do connexion in Rust, but with the extra benefits that Rust can give you, namely performance and correctness.

Why do I prefer to define the spec then generate code, rather than write code then generate the spec? I think these two things are very different ways of working. Using something like hsr, you get a complete framework - JSON types, server code and client code. If I used hsr in multiple projects (say in a service-oriented architecture), they would all have the same predictable structure. As long as I know how to write a spec, I do not have to bother with a lot of the tedious details of the implementation.

Also, an OpenAPI spec is language-agnostic which is very useful in a multi-language environment. In particular, for the frontend, I can use something like swagger-to-ts to generate equivalent typescript types, then I can be confident that my backend and frontend can talk to each other. I have spent so much time over the years debugging API inconsistencies, I want it to stop.

In contrast, I find little value in generating an OpenAPI spec from code, as I do not find the spec to be very useful in- and of- itself - and it doesn't free me from doing the tedious bits, namely all the server and client boilerplate. I like to use OpenAPI as a (not that great) IDL, the pretty ui rendering is nice but not really the point.

diegopy commented 4 years ago

Thanks for your prompt reply. I see your point, and it's definitely a perspective I haven't considered. Having always the same project structure generated is really valuable. Please allow me to humbly suggest a feature that I found useful in my projects, and I'm not sure hsr supports: Complex parameter objects as specified here. OpenAPI supports passing objects encoded with json as query, path and header parameters. It's not super common but I had the need to do that in a project of mine. Now, the million dollar question: Do you think hsr is "production ready"? Basically, are there any areas that you feel need major work before being useful? Thanks again, and if you have a list of things that need addressing I would be happy to contribute with fixes and features implementation.

adwhit commented 4 years ago

Please allow me to humbly suggest a feature that I found useful in my projects, and I'm not sure hsr supports: Complex parameter objects as specified here

Interesting. At the moment I think only simple parameters would work (haven't tested anything more complex). The current implementation just does "whatever actix-web does" which I think relies on serde-urlencode so I would have to look more closely at how it works. I would like to support as much of the spec as possible although I think what you have suggested is relatively low priority.

Now, the million dollar question: Do you think hsr is "production ready"? Basically, are there any areas that you feel need major work before being useful?

I think the parts that are implemented 'work', and the underlying framework (actix-web) is certainly used in production. However there are still lots to do before it supports all the features that would be expected for production use. You can see an incomplete list https://github.com/adwhit/hsr/blob/master/TODO.md.

My aim is to implement everything bit-by-bit until it would be possible (at least in principal) to use the framework at my day job. The most conspicuous missing features in this regard are

I would of course be delighted to accept PRs for any features in the TODO, or indeed anything else you think could be improved. Or simply giving it a try and reporting pain points would also be valuable.

BTW, this isn't really documented yet but there is a nightly-only feature pretty which runs the generated code through rustfmt before returning it. This makes error messages a LOT more readable, so if you start hacking/debugging, you'll probably want to activate that feature.

brokenthorn commented 4 years ago

What about error handling. I see hsr wraps async fn's that return what are basically just model objects (and not even wrapped with Result or Option). What happens when inside those fn's you encounter an error? How does that propagate up to the request handler? How do you control what error gets returned to the client?

Basically, I'm asking how would one do proper error handling with hsr at the moment?

On a side note: hsr looks like a very interesting project. I wonder why it's not more popular. I guess people prefer the official openpi/swagger codegen tools.

adwhit commented 4 years ago

Hi, thanks for your interest.

Error handling: is a slightly tricky one. Versions 0.1 and 0.2 allowed the user to return Result from the handlers. But I found this did not map well to the OpenAPI spec. Instead, if your endpoint can return an error, you should create an Error response type and return that. Note that you can still use Result and Option internally but the final response must be one defined in the spec (but you can use a default error response at this point for generic 'it went wrong' type errors). Take a look at the petstore example to get the idea. Incidentally this should become a lot more ergonomic if/when try blocks are stabilized.

re: popularity - while this project works fine as it stands, in my view it is not really ready for public use until the above TODO items are completed, so I have made no effort to publicise it yet. When I find the time (hopefully in August) I will add the missing features and polish it a bit, then make some kind of announcement. But in the meantime, please feel free to give it a whirl, the existing API is not likely to change much.

brokenthorn commented 4 years ago

I sort of always have this question with all libraries in Rust that I've tried: how does this library do error handling?

There have been so many ways to do that and will probably continue to be more than one way of doing error handling with Rust, that I wished that more crates had a small section titled "how to handle errors in X crate code".

ATM, using crates like anyhow (which strives to stay close to std idiomatic Rust) and the idiomatic (???) 'make your own error type and implement Error' practice seem to me like the best way to do errors with Rust. Seems like it's wanting to converge on that standard but I'm very new to Rust so I might not know what I'm talking about here.

What are you opinions on this? Will you consider writing a section like that, on handling errors with hsr? Will the current way change? Have you started promoting the project yet?

diegopy commented 4 years ago

I'm not the hsr author, but my humble opinion on this as a general topic, which seems to be the consensus, is: anyhow for programs, thiserror for libraries. The thiserror crate facilitates the "Make your own error type" for library code. If you think about it it makes sense to have different approaches to the "program" code and "library" code. A program, particularly at the top level, is concerned with handling the error, and/or propagating them where they should be handled. That's where anyhow excels. A library on the other hand, is concerned with raising errors and, ideally, very specific errors that detail exactly what happened, and leave up to the "prorgam" using it on how to deal with it. The thiserror crate excels here.

adwhit commented 4 years ago

Pretty much agree with @diegopy here. For libraries, create your own precise types with thiserror. For applications, propagate errors with anyhow.

However, for this particular framework, error types will be defined by the user via the spec, so it isn't necessary to include library errors beyond that (at least in principle).

re: progress, haven't worked on this recently but it is still on my radar, just need to find a solid uninterrupted week or so to get it polished.

brokenthorn commented 4 years ago

I kind of switched my focus to async-std, tide, async-h1 and that side of the Rust HTTP async-await server story. Otherwise, I would have loved to try hsr and maybe even contribute. Right now I decided on HTTP RPC-style for my current project and have already started working.

Still an amazing project, at least in principle. I do wish it a lot of luck and success and I hope it will be a good fit for most people doing Rust web APIs.