Code Monkey home page Code Monkey logo

axum_typed_multipart's Introduction

axum_typed_multipart

.github/workflows/release.yml .github/workflows/audit.yml codecov License: MIT

Designed to seamlessly integrate with Axum, this crate simplifies the process of handling multipart/form-data requests in your web application by allowing you to parse the request body into a type-safe struct.

Documentation

Documentation and installation instructions are available on docs.rs

Release process

When a SemVer compatible git tag is pushed to the repo a new version of the package will be published to crates.io.

Contributing

Direct push to the main branch is not allowed, any updates require a pull request to be opened. After all status checks pass the PR will be eligible for review and merge.

Commit messages should follow the Conventional Commits specification.

The project comes with an optional pre-configured development container with all the required tools. For more information on how to use it please refer to https://containers.dev

To make sure your changes match the project style you can install the pre-commit hooks with pre-commit install. This requires pre-commit to be installed on your system.

License

Copyright (c) 2023 Lorenzo Murarotto [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

axum_typed_multipart's People

Contributors

dependabot[bot] avatar gengteng avatar murar8 avatar nerodono avatar reneklacan avatar wowkster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

axum_typed_multipart's Issues

Add custom response for errors

impl IntoResponse for TypedMultipartError {
fn into_response(self) -> Response {
(self.get_status(), self.to_string()).into_response()
}
}

It would be nice to be able to customize the way TypedMultipartErrors are handled, such as being able to return the error as JSON instead of raw text.

If you have any ideas of a good way to implement this I will happily make a PR if necessary :)

Naming convention error

When deriving from TryFromMultipart, I get the following warning:
Variable `__field__name__` should have snake_case name, e.g. `__field_name`

This is my struct:

#[derive(TryFromMultipart)]
struct ParsePdf {
    pdf: FieldData<TempFile>,
}

Changing the prefix of `NamedTempFile` within `FieldData`

Hello, quick question, would it be possible to implement the possibility to change the prefix of NamedTempFile within FieldData?
With the tempfile crate you can change the prefix using: https://docs.rs/tempfile/3.10.1/tempfile/struct.NamedTempFile.html#method.with_prefix
Afaik, currently with the usage of axum_typed_multipart, there's no way to specify the prefix, I'd have to change to FieldData<Bytes> and use the NamedTempFile builder or modify the existing tempfile created.

Upload example out-of-date with file persistence

Excellent crate first of all. Adding this here in case I have some time later to fix the issue. The upload example is currently out-of-date. Specifically the fact that persist() is no longer available.

async fn upload_asset(
TypedMultipart(UploadAssetRequest { image, author }): TypedMultipart<UploadAssetRequest>,
) -> StatusCode {
let file_name = image.metadata.file_name.unwrap_or(String::from("data.bin"));
let path = Path::new("/tmp").join(author).join(file_name);
match image.contents.persist(path) {
Ok(_) => StatusCode::CREATED,
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}

I couldn't find a changelog for this and it seems like FieldData::contents is now just T. I'll likely move around this using axum's example and if it works well, update the example to reflect it.

Can't use reserved keyword as a struct field

Hi, this crate convert the field name as is, so I get this error:

image

#[derive(TryFromMultipart)]
pub struct CreateRtypeInput {
    /// The record type
    pub r#type: String,
}
pub async fn post(
    ctx: Extension<Arc<ServerContext>>,
    TypedMultipart(input::CreateRtypeInput { r#type }): TypedMultipart<input::CreateRtypeInput>,
) -> Result<Response, crate::Error> {

Thank you for TypedMultipart ❤️

Field name with square brackets

It seems that it's a common practice to use trailing square brackets (e.g., names[]=Foo) when one wants to send multiple values with the same field name. For examples, here is the PHP manual for uploading multiple files and I found the same recommendations during internet search (1, 2).

This would mean that for the following program:

use axum::{http::StatusCode, routing::post, Router};
use axum_typed_multipart::{TryFromMultipart, TypedMultipart};

#[derive(TryFromMultipart)]
struct RequestData {
    names: Vec<String>,
}

async fn handler(TypedMultipart(RequestData { names }): TypedMultipart<RequestData>) -> StatusCode {
    println!("names = {:?}", names);
    StatusCode::OK
}

#[tokio::main]
async fn main() {
    // build our application with a single route
    let app = Router::new().route("/", post(handler));

    // run it with hyper on localhost:3000
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

the following request:

curl -X POST --form "names[]=Foo" --form "names[]=Bar" http://localhost:3000

prints names = ["Foo", "Bar"].

Rocket and its FromForm derive trait supports this.

I think that supporting just names[] is a good start, later it would be nice to support things like names[0] (explicit ordering) or even names[foo] -> HashMap.

If this is desired feature, I could try to send a PR. In that case and advice and pointers into the codebase would be appreciated.

Limit Vec lengh in request body

#[form_data(field_name = "pic", limit = "5MiB")]
pub pics: Vec<FieldData<NamedTempFile>>,

How do I limit the vec size here? I can't see the accepted form_data attributes. I want the vec size to be no more than 8.

Does not work with Axum 0.7

When trying to use with Axum 0.7.1 the following error occurs during compilation:

error[E0119]: conflicting implementations of trait `FromRequest<_, axum_core::extract::private::ViaParts>` for type `BaseMultipart<_, _>`
  --> /home/czarek/.cargo/git/checkouts/axum_typed_multipart-89f50b984a04aef4/0baf693/src/base_multipart.rs:59:1
   |
59 | impl<S, B, T, R> FromRequest<S, B> for BaseMultipart<T, R>
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `axum_core`:
           - impl<S, T> FromRequest<S, axum_core::extract::private::ViaParts> for T
             where S: std::marker::Send, S: Sync, T: FromRequestParts<S>;
   = note: downstream crates may implement trait `axum::extract::FromRequestParts<_>` for type `base_multipart::BaseMultipart<_, _>`
   = note: upstream crates may add a new impl of trait `axum::body::HttpBody` for type `axum_core::extract::private::ViaParts` in future versions

error[E0119]: conflicting implementations of trait `FromRequest<_, axum_core::extract::private::ViaParts>` for type `TypedMultipart<_>`
  --> /home/czarek/.cargo/git/checkouts/axum_typed_multipart-89f50b984a04aef4/0baf693/src/typed_multipart.rs:51:1
   |
51 | impl<T, S, B> FromRequest<S, B> for TypedMultipart<T>
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `axum_core`:
           - impl<S, T> FromRequest<S, axum_core::extract::private::ViaParts> for T
             where S: std::marker::Send, S: Sync, T: FromRequestParts<S>;
   = note: downstream crates may implement trait `axum::extract::FromRequestParts<_>` for type `typed_multipart::TypedMultipart<_>`
   = note: upstream crates may add a new impl of trait `axum::body::HttpBody` for type `axum_core::extract::private::ViaParts` in future versions

For more information about this error, try `rustc --explain E0119`.
error: could not compile `axum_typed_multipart` (lib) due to 2 previous errors
warning: build failed, waiting for other jobs to finish...

How to implement TryFromChunks for external types?

I'm trying to parse a Multipart form into a struct that contains fields that are not defined in my crate, so i can't implement TryFromChunks for them. However they do implement FromStr, but implementing TryFromChunks for any types that implements FromStr would create implementation conflicts for most types...

These types also implement Deserialize. Maybe using serde::Deserialize instead of TryFromChunks could be an option (eg. behind a feature flag)?

Error in macro TryFromMultipart in [email protected]

Error in macro TryFromMultipart in [email protected]

4 | #[derive(TryFromMultipart)]
  |          ^^^^^^^^^^^^^^^^
  |          |
  |          expected `axum::extract::multipart::Multipart`, found `Multipart`
  |          help: change the parameter type to match the trait: `&'life0 mut axum::extract::multipart::Multipart`
  |
  = note: expected signature `fn(&'life0 mut axum::extract::multipart::Multipart) -> Pin<_>`
             found signature `fn(&'life0 mut Multipart) -> Pin<_>`
  = note: this error originates in the derive macro `TryFromMultipart` (in Nightly builds, run with -Z macro-backtrace for more info)

libunwind: malformed __unwind_info at 0x1898DECA8 bad second level page

Provide full example

Hi, I am having hard time using this crate. The Axum route can't accept the function with this error:

error[E0277]: the trait bound `fn(axum_typed_multipart::TypedMultipart<ttl::model::input::CreateTtlInput>, axum::Extension<std::sync::Arc<context::ServerContext>>) -> impl std::future::Future<Output = std::result::Result<axum::Json<ttl::model::TtlResponse>, errors::Error>> {ttl::query::post}: axum::handler::Handler<_, _, _>` is not satisfied
   --> src/routes.rs:51:38
    |
51  |         .route("/ttl/add/:ttl", post(ttl::query::post));
    |                                 ---- ^^^^^^^^^^^^^^^^ the trait `axum::handler::Handler<_, _, _>` is not implemented for fn item `fn(axum_typed_multipart::TypedMultipart<ttl::model::input::CreateTtlInput>, axum::Extension<std::sync::Arc<context::ServerContext>>) -> impl std::future::Future<Output = std::result::Result<axum::Json<ttl::model::TtlResponse>, errors::Error>> {ttl::query::post}`
    |                                 |
    |                                 required by a bound introduced by this call
    |
    = help: the following other types implement trait `axum::handler::Handler<T, S, B>`:
              <axum::handler::Layered<L, H, T, S, B, B2> as axum::handler::Handler<T, S, B2>>
              <axum::routing::MethodRouter<S, B> as axum::handler::Handler<(), S, B>>
note: required by a bound in `axum::routing::post`
   --> /var/home/user/.local/share/cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.6.12/src/routing/method_routing.rs:407:1
    |
407 | top_level_handler_fn!(post, POST);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `post`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
        .route("/ttl/list/:id", get(ttl::query::get))
        .route("/ttl/add/:ttl", post(ttl::query::post)); // ⚠️ the line that causes the error
pub async fn post(
    TypedMultipart(input::CreateTtlInput { ttl }): TypedMultipart<input::CreateTtlInput>,
    ctx: Extension<Arc<ServerContext>>,
) -> Result<Json<model::TtlResponse>, crate::Error> {

I am sure it is just my mistake for not having a clue to pass the function to the route. The full example such this https://github.com/juhaku/utoipa/tree/master/examples/todo-axum or this https://github.com/juhaku/utoipa#examples would be very helpful.

Thanks for TypedMultipart ❤️

Make rejection field in BaseMultipart pub

Hi, I'm working on axum-valid and trying to integrate axum with validify. I need to extract the data from a BaseMultipart initially parsed from the request as the payload. Then validate this payload, construct the actual data using the validated payload, and repackage it into a new BaseMultipart.

I would like to request making the rejection field in the BaseMultipart struct pub. Having it as pub would allow me to do this more easily. Here is a minimal reproduction of what I'm trying to do:

// file `typed_multipart.rs`
#[cfg(feature = "validify")]
impl<T: validify::Validify, R> crate::HasValidify for BaseMultipart<T, R> {
    type Validify = T;
    type PayloadExtractor = BaseMultipart<T::Payload, R>;

    fn from_validified(data: Self::Validify) -> Self {
        BaseMultipart {
            data,
            rejection: Default::default(), // ❌: need rejection to be pub
        }
    }
}

// file `validify.rs`
/// Trait for types that can supply a reference that can be modified and validated using `validify`.
///
/// Extractor types `T` that implement this trait can be used with `Validified`.
///
pub trait HasValidify: Sized {
    /// Inner type that can be modified and validated using `validify`.
    type Validify: Validify;
    ///
    type PayloadExtractor: PayloadExtractor<Payload = <Self::Validify as Validify>::Payload>;
    ///
    fn from_validified(v: Self::Validify) -> Self;
}

#[async_trait]
impl<State, Extractor> FromRequestParts<State> for Validified<Extractor>
where
    State: Send + Sync,
    Extractor: HasValidify,
    Extractor::Validify: Validify,
    Extractor::PayloadExtractor: FromRequestParts<State>,
{
    type Rejection =
        ValidifyRejection<<Extractor::PayloadExtractor as FromRequestParts<State>>::Rejection>;

    async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
        let payload = Extractor::PayloadExtractor::from_request_parts(parts, state)
            .await
            .map_err(ValidifyRejection::Inner)?;
        Ok(Validified(Extractor::from_validified(
            Extractor::Validify::validify(payload.get_payload())?,
        )))
    }
}
// and impl FromRequest for Validified ...

Please let me know if this change would break any existing interfaces or backwards compatibility. If so, I'm happy to discuss potential solutions. I'm open to your feedback and can amend this request accordingly.

Thanks for your consideration! Let me know if you would like any clarification or have any other questions.

allow newtype idiom

When I have e.g.

#[derive(Deserialize, TryFromMultipart)]
struct Form {
  test: MyNewtype,
}

struct MyNewtype(String)

It complains that MyNewtype does not implement TryFromChunks. How can I use a newtype without writing a custom implementation?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.