-
-
Notifications
You must be signed in to change notification settings - Fork 12
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
feat: add form support and better routing; implement TODO app example #7
Conversation
|
||
async fn remove_todo(request: Request) -> Result<Response, Error> { | ||
let todo_id = request.path_param("todo_id").expect("todo_id not found"); | ||
let todo_id = todo_id.parse::<usize>().expect("todo_id is not a number"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would probably be nice to have an ergonomic API to get path parameters with type safety (similar to Form
I guess), but this is definitely out of scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the fn(request) -> response)
was inspired by Django's approach, but I think I'd rather go with Rocket's /Axum's approach of variables as function arguments, I think those seem more convenient to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, to be clear and for comparison purposes:
- Django uses function parameters to pass URL params (docs)
- Rocket uses function parameters to pass URL params (docs)
- Axum seems to be using a special tuple type that is passed as a parameter to the function (docs)
So, seems like there are two main approaches, and I'm not quite sure which one would be better for us. I've added a task to the project for now, as this is out of scope of this PR, as I've mentioned: https://github.com/orgs/flareon-rs/projects/2/views/7?pane=issue&itemId=75286758
} | ||
} | ||
|
||
impl FormField for CharField { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation will probably be moved into some macro in the next PRs as it's pretty generic, but an in-place implementation is fine as long as we only have one field type (which is the case for now).
f3c13a3
to
5ed7b02
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for now, few comments in the code
let listener = tokio::net::TcpListener::bind(address_str).await.unwrap(); | ||
|
||
let handler = |request: axum::extract::Request| async move { | ||
pass_to_axum(&project, request) | ||
pass_to_axum(&project, Request::new(request, project.clone())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think cloning project on each request is the way to go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not cloning FlareonProject
, but rather Arc<FlareonProject>
, which is much cheaper: https://github.com/flareon-rs/flareon/pull/7/files/eadabfac46a1702bb174fc0ef5809a49951577b7#diff-da48df69bb298faf68bd58ae6ad3a1e4df72c8d7733bd5849b62d595d9747a80R211
pub mod prelude; | ||
#[doc(hidden)] | ||
pub mod private; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If private shouldn't that be pub(crate)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The answer is inside the module documentation (somewhat, maybe it needs clarification):
/// Re-exports of some of the Flareon dependencies that are used in the macros. /// /// This is to avoid the need to add them as dependencies to the crate that uses /// the macros.
The problem is that when you generate some code inside a proc macro, the code end up being "pasted" inside the consumer's crate, not our crate. In order to use things like async_trait
there, either the user has to declare it as their dependency, or we can just create this sort of "public-hidden" module that re-exports stuff that the generated code is using.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way - this seems to be a common practice in many libraries, e.g. https://github.com/serde-rs/serde/blob/master/serde/src/lib.rs#L313-L316
self.inner.headers().get(axum::http::header::CONTENT_TYPE) | ||
} | ||
|
||
pub async fn form_data(&mut self) -> Result<Bytes, Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the name is confusing, wouldn't data
just be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps. But then won't it be confusing when we want to have a method that returns a raw request body? data()
sounds very much like it could be just returning raw data, while this method specifically parses the URL/request body as a HTML form, hence the name.
</head> | ||
<body> | ||
<h1>TODO List</h1> | ||
<form id="todo-form" action="{{ flareon::reverse_str!(request, "add-todo") }}" method="post"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, but I think that flareon
filters/actions should be in scope by default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but I think sadly this will require forking askama
library, or at least creating some wrapper proc macro. Forking askama
to make it more convenient to use with flareon is on our roadmap, and I think it's out of scope for this PR, so I'd keep it as is for now.
Route::with_handler_and_name( | ||
"/todos/:todo_id/remove", | ||
Arc::new(Box::new(remove_todo)), | ||
"remove-todo", | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that /remove
is only because we don't have support for DELETE
yet, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically in web development, unless developing a REST service (or some other service that's not directly web-browser-facing), you only ever use GET and POST, really. I think one of the reasons is that it's defined as idempotent, and in case of this TODO app, it's clearly not (successive requests to /todos/0/delete
will eventually remove all of the TODOs). There seem to be some very good historical reasons as well, but I'm not sure how important these are today.
|
||
async fn remove_todo(request: Request) -> Result<Response, Error> { | ||
let todo_id = request.path_param("todo_id").expect("todo_id not found"); | ||
let todo_id = todo_id.parse::<usize>().expect("todo_id is not a number"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the fn(request) -> response)
was inspired by Django's approach, but I think I'd rather go with Rocket's /Axum's approach of variables as function arguments, I think those seem more convenient to use.
Quite a big PR, but at this stage of the project it's difficult to make any significant changes while also separating it into logical blocks.
This adds: