From f150ff7cc5e99d1544d09d01f75446aabd01308c Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 20 Jan 2025 09:56:03 +0100 Subject: [PATCH 1/4] Add Exercise 5.1.1: Lettuce Crop --- .vscode/settings.json | 3 +- book/src/SUMMARY.md | 2 +- book/src/rust-for-web-servers.md | 127 ++ book/src/rust-for-web.md | 12 - .../1-lettuce-crop/Cargo.lock | 1616 +++++++++++++++++ .../1-lettuce-crop/Cargo.toml | 10 + .../1-lettuce-crop/assets/crop.webp | Bin 0 -> 8182 bytes .../1-lettuce-crop/assets/index.html | 37 + .../1-lettuce-crop/assets/styles.css | 71 + .../1-lettuce-crop/src/main.rs | 13 + 10 files changed, 1877 insertions(+), 14 deletions(-) create mode 100644 book/src/rust-for-web-servers.md delete mode 100644 book/src/rust-for-web.md create mode 100644 exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.lock create mode 100644 exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.toml create mode 100644 exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/crop.webp create mode 100644 exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/index.html create mode 100644 exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/styles.css create mode 100644 exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index c06767c..f934b47 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "exercises/4-multitasking/2-parallel-multitasking/2-mutex/Cargo.toml", "exercises/4-multitasking/3-asynchronous-multitasking/1-async-channels/Cargo.toml", "exercises/4-multitasking/3-asynchronous-multitasking/2-async-chat/Cargo.toml", + "exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.toml", "exercises/6-rust-for-systems-programming/1-foreign-function-interface/1-crc-in-c/Cargo.toml", "exercises/6-rust-for-systems-programming/1-foreign-function-interface/2-crc-in-rust/Cargo.toml", "exercises/6-rust-for-systems-programming/1-foreign-function-interface/3-qoi-bindgen/Cargo.toml", @@ -34,4 +35,4 @@ "exercises/8-embedded/4-embassy-framework/1-embassy-project/Cargo.toml", ], "rust-analyzer.check.allTargets": false, -} +} \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index c22f0b1..f803c6a 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -20,7 +20,7 @@ - [Asynchronous Multitasking](asynchronous-multitasking.md) - [Rust for Web]() - - [Rust for Web](rust-for-web.md) + - [Rust for Web Servers](rust-for-web-servers.md) - [Rust for Systems Programming]() - [Foreign Function Interface](foreign-function-interface.md) diff --git a/book/src/rust-for-web-servers.md b/book/src/rust-for-web-servers.md new file mode 100644 index 0000000..291e4a1 --- /dev/null +++ b/book/src/rust-for-web-servers.md @@ -0,0 +1,127 @@ +# Unit 5.1 - Rust for Web Servers + +Slides + +## Exercise 5.1.1: Lettuce Crop +In this exercise, we will build a simple web server with [`axum`](https://lib.rs/crates/axum) which allows users to upload images to crop them. You will learn how to serve static HTML pages along with their associated style sheets and images, and you will learn how to handle POST requests with multipart form data to receive the uploaded images. + +### 3.1.1.A Hello axum +In `exercises/5-rust-for-web/1-rust-for-web/1-lettuce-crop` we have set up the start of our web server. It currently only serves "Hello, world!" for GET requests on the main page. Run the program and go to [http://0.0.0.0:7000/](http://0.0.0.0:7000/) in your browser to see if it works. + +When defining the router, we can chain multiple routes to serve multiple end-points. Try adding a second route which serves GET requests on another page (e.g. `/hello`). + +### 3.1.1.B Serving static files +Currently, our web server only serves static strings. To serve static HTML documents, CSS style sheets, images and other files, we will use the [`ServeDir`](https://docs.rs/tower-http/latest/tower_http/services/struct.ServeDir.html) file server from `tower_http`. We can add this file server to our router as a fallback service to resolve any request which does not match any other defined route with our file server. + +Add a [`fallback_service`](https://docs.rs/axum/latest/axum/routing/struct.Router.html#method.fallback_service) to the router with a `ServeDir` that serves files from the `assets` folder. + +If you now go to [http://0.0.0.0:7000/index.html](http://0.0.0.0:7000/index.html) you should see the Lettuce Crop web page with appropriate styling and an image of a lettuce. + +By default, `ServeDir` will automatically append `index.html` when requesting a path that leads to a directory. This means that if you remove the "Hello, world!" route for `/` from the router, you will also see the Lettuce Crop page on the main page of the website! + +### 3.1.1.C POST requests and dynamic responses +On the Lettuce Crop page we have set up an HTML form, which when submitted sends a POST request to `/crop`: +```html +
+``` +POST requests are requests that can contain additional data to send to the server. In this case, the form data, consisting of an image and the max size value, will be sent along with the request. + +If you select an image and press the crop button, you will be redirected to `/crop`, which currently does not exist. If you open the browser's developer tools (right click > Inspect, or ctrl+shift+i) and go to the network tab, you should see the POST request which currently returns status 405 (Method Not Allowed). The `/crop` route is currently handled by our fallback service, which does not accept POST requests. If you go to [http://0.0.0.0:7000/crop](http://0.0.0.0:7000/crop) directly without using the form, the browser will instead send a regular GET request, which will return status 404 (Not Found). + +Let's add a route for `/crop` to our router which will handle the POST requests from the form. You can specify the route in the same way as we did for GET requests, but using `post` instead of `get`. + +Instead of returning a static string, we can also use a function to respond to requests. Define the following function and pass it to the `post` method for `/crop`: +```rust +async fn crop_image() -> String { + format!("Hi! This is still a work in progress. {}", 42) +} +``` + +### 3.1.1.D Handling uploaded files (multipart form data) +So how do we get the form data from the POST request? With `axum`, we use [extractors](https://docs.rs/axum/latest/axum/extract/) to get information about the request, such as headers, path names or query parameters. Normally, we would use the [`Form` extractor](https://docs.rs/axum/latest/axum/struct.Form.html) to get the submitted form data. However, because we want the user to be able to upload an image, we use the [multipart form data](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#multipart_form_submission) encoding, as specified by the `enctype` in the HTML `form` tag. + +To extract multipart form data in `axum`, we use the [`Multipart` extractor](https://docs.rs/axum/latest/axum/extract/struct.Multipart.html). Unlike the `Form` extractor, the `Multipart` extractor does not automatically deserialize the data into a convenient struct. Instead, we will have to manually loop through the fields and deserialize the data we need. + +Add `mut multipart: Multipart` as a parameter to our `crop_image` function to extract the multipart form data. Then, use the following loop to print all available fields that were included in the POST request: +```rust +while let Some(field) = multipart.next_field().await.unwrap() { + let name = field.name().unwrap().to_string(); + let bytes = field.bytes().await.unwrap(); + println!("{name}: {} bytes long", bytes.len()); +} +``` +Once you submit the form, it should show an `image` field containing the image data and a `max_size` field corresponding to the max size number input field in the form. + +Let's deserialize the two form fields: +- The `image` field consists of the bytes that make up the image. We will use an `ImageReader` from the `image` crate to read the image data: + ```rust + ImageReader::new(Cursor::new(bytes)).with_guessed_format().unwrap().decode() + ``` + This will return a `DynamicImage`, which can be a variety of different image formats. With the `image` crate we will be able to crop and resize this image. + +- The `max_size` field contains a number encoded a plain text. You can retrieve the text using `field.text()` instead of `field.bytes()`, and you can parse it into a number using [`.parse()`](https://doc.rust-lang.org/std/string/struct.String.html#method.parse). Let's make it a `u32`. + +We will leave it up to you to implement the logic to deserialize these two fields and turn them into a `DynamicImage` and a `u32` that can be used after we're done looping through all the fields. + +Change the string returned by `crop_image` to the following to verify that it works: +```rust +format!("Image size: {}x{}\nMax size: {}", image.width(), image.height(), max_size) +``` + +### 3.1.1.E Sending the cropped image as response +Let's crop the `DynamicImage` into a square using the following code: +```rust +let size = min(min(image.width(), image.height()), max_size); +let image = image.resize_to_fill(size, size, imageops::FilterType::Triangle); +``` +The size of the cropped square image is the minimum of the image's width, height and the configured maximum size. The `resize_to_fill` method will crop and resize the image to our size and center it appropriately. + +Now that we have cropped the image, we need to send it back to the client. We encode the image back into an image format with `write_to`; we've chosen to return the cropped images as WebP's: +```rust +let mut image_buffer = Vec::new(); +image + .write_to(&mut BufWriter::new(Cursor::new(&mut image_buffer)), ImageFormat::WebP) + .unwrap(); +``` + +To send these bytes as an image to the client, we will have to create a response with a proper content type header and our image buffer as a body. Update the `crop_image` to return a [`Response`](https://docs.rs/axum/latest/axum/response/type.Response.html) instead of a `String`, and construct a response with [`Response::builder()`](https://docs.rs/http/latest/http/response/struct.Builder.html). Set the ["content-type" header](https://docs.rs/http/latest/http/header/constant.CONTENT_TYPE.html) to match your chosen image format (for example `image/webp` for WebP images), and construct a body from the image buffer using [`Body::from`](https://docs.rs/axum/latest/axum/body/struct.Body.html#impl-From%3CVec%3Cu8%3E%3E-for-Body). + +If you now submit an image on the site, it should be returned to you cropped into a square! + +### 3.1.1.F Error handling & input validation +Currently, the handler likely contains many `.unwrap()`s, which may panic. Luckily, `axum` catches these panics from our handler and will keep running after printing the panic. However, the user will not get any proper response from `axum` when these panics happen. To give the client some feedback about what went wrong, we can implement some better error handling. + +Let's change our `crop_image` function to return a `Result`. This gives us the ability to return errors consisting of an HTTP status code and a static string. + +For example, let's say the user uploads a corrupted image. Then, the `.decode()` method of our `ImageReader` will return an error, causing the `.unwrap()` to panic. Let's replace the `.unwrap()` with a `.map_err` that notifies the user that they did a bad request: +```rs +.map_err(|_| (StatusCode::BAD_REQUEST, "Error: Could not decode image"))? +``` + +Similarly, you can also add appropriate error handling in other places, returning appropriate [HTTP status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). + +Currently, our the size of our cropped image is defined as the minimum of the original image's width and height, and the set `max_size` value. The `max_size` value has a maximum of 2048 set in the HTML form. However, you should never trust the data coming from the client-side as HTML and JavaScript code running on the client's device can easily be modified, and the client can send modified HTTP requests. So let's also return a `StatusCode::BAD_REQUEST` if `max_size` is larger than 2048. + +By default, there is a 2 MB limit for request bodies. If a user submits an image larger than this limit, the `.bytes()` call on the multipart field will return an error. In this case, we could return a `StatusCode::PAYLOAD_TOO_LARGE`. If you want to accept larger images, you can configure a larger limit by setting a custom [`DefaultBodyLimit`](https://docs.rs/axum/latest/axum/extract/struct.DefaultBodyLimit.html). + +### 3.1.1.G Serving files from memory (bonus) +Currently, the static files are served from the `assets` folder. Instead, we can also bundle these files into the binary with [memory-serve](https://docs.rs/memory-serve/latest/memory_serve/). Not only is it convenient to bundle all files into a single binary, but it can also improve performance! + +After adding memory-serve to your project with `cargo add memory-serve`, we can define a memory router as follows: +```rust +let memory_router = MemoryServe::new(load_assets!("assets")) + .index_file(Some("/index.html")) + .into_router(); +``` +Now we can use this memory router as fallback service instead of the `ServeDir`. + +If you build the project in release mode (`cargo build --release`), you will see the files in the `assets` folder being included in the binary! + + +## Exercise 5.1.2: Pastebin +This exercise is about writing a simple [pastebin](https://en.wikipedia.org/wiki/Pastebin) web server. The web server will again be powered by [`axum`](https://lib.rs/crates/axum). For this exercise, you will need to set up the project yourself. + +- Data is kept in memory. Bonus if you use a database or `sqlite`, but first make the app function properly without. +- Expose a route to which a POST request can be sent, that accepts some plain text, and stores it along with a freshly generated UUID. The UUID is sent in the response. You can use the [`uuid` crate](https://docs.rs/uuid/latest/uuid/) to generate UUIDs. +- Expose a route to which a GET request can be sent, that accepts a UUID and returns the plain text corresponding to the UUID, or a 404 error if it doesn't exist. +- Expose a route to which a DELETE request can be sent, that accepts a UUID and deletes the plain text corresponding to that UUID. diff --git a/book/src/rust-for-web.md b/book/src/rust-for-web.md deleted file mode 100644 index 51a77b7..0000000 --- a/book/src/rust-for-web.md +++ /dev/null @@ -1,12 +0,0 @@ -# Unit 5.1 - Rust for Web - -Slides - -## Exercise 5.1.1: Pastebin - -This exercise is about writing a simple [pastebin](https://en.wikipedia.org/wiki/Pastebin) web server. Like the quizzer app, you will need to set up the project yourself. This webserver will be powered by [`axum`](https://lib.rs/crates/axum). - -- Data is kept in memory. Bonus if you use a database or `sqlite`, but first make the app function properly without. -- Expose a route to which a POST request can be sent, that accepts some plain text, and stores it along with a freshly generated UUID. The UUID is sent in the response. You can use the [`uuid` crate](https://docs.rs/uuid/latest/uuid/) to generate UUIDs. -- Expose a route to which a GET request can be sent, that accepts a UUID and returns the plain text corresponding to the UUID, or a 404 error if it doesn't exist. -- Expose a route to which a DELETE request can be sent, that accepts a UUID and deletes the plain text corresonding to that UUID. diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.lock b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.lock new file mode 100644 index 0000000..33981dc --- /dev/null +++ b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.lock @@ -0,0 +1,1616 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "axum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "lettuce-crop" +version = "0.1.0" +dependencies = [ + "axum", + "image", + "tokio", + "tower-http", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.7.0", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.toml b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.toml new file mode 100644 index 0000000..3667627 --- /dev/null +++ b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lettuce-crop" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = { version = "0.8.1", features = ["multipart"] } +image = "0.25.5" +tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros"] } +tower-http = { version = "0.6.2", features = ["fs"] } diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/crop.webp b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/crop.webp new file mode 100644 index 0000000000000000000000000000000000000000..e537c54c9382fe6a58cbfbccf903568778c429b7 GIT binary patch literal 8182 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 literal 0 HcmV?d00001 diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/index.html b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/index.html new file mode 100644 index 0000000..65b0899 --- /dev/null +++ b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/index.html @@ -0,0 +1,37 @@ + + + + + + Lettuce Crop + + + + + + +
+
+
+

Lettuce Crop

+

Upload an image to make it square!

+
+
+ + +

+ + +

+

+ + + + +

+ + + + \ No newline at end of file diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/styles.css b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/styles.css new file mode 100644 index 0000000..2585a4a --- /dev/null +++ b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/assets/styles.css @@ -0,0 +1,71 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@100..900&display=swap'); + +html { + background-color: #a1a691; +} + +body { + max-width: 40em; + margin: 4em auto; + padding: 1.5em 4em; + font-family: "Noto Sans", Arial, Helvetica, sans-serif; + background-color: #fff; + box-shadow: 0.5em 0.5em 0em #0006; +} + +header { + display: flex; + align-items: center; + margin: 1.5em 0; +} + +.logo { + float: left; + margin-right: 1em; + width: 5em; +} + +hgroup>* { + margin: 0; + line-height: 1.2em; +} + +h1 { + color: #3f7a16; +} + +hgroup>p { + font-size: 1.25em; +} + +form { + text-align: center; + margin: 1em 0; +} + +.box { + background-color: #b4d385; + border-radius: 1em; + padding: 1em; +} + +.box>label { + display: block; + padding-bottom: 0.5em; +} + +input:not([type="file"]) { + border-radius: 4px; + border: 1px solid #727272; + padding: 0.5em 0.5em; + min-width: 6em; +} + +input[type="submit"], +input[type="file"] { + cursor: pointer; +} + +.divider { + margin: 0 0.5em; +} \ No newline at end of file diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs new file mode 100644 index 0000000..01bc07f --- /dev/null +++ b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs @@ -0,0 +1,13 @@ +use axum::{routing::get, Router}; + +#[tokio::main] +async fn main() { + // Specify available routes + let app = Router::new().route("/", get("Hello, world!")); + + // Serve the website on localhost + let addr = "0.0.0.0:7000"; + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + println!("Serving on http://{addr}"); + axum::serve(listener, app).await.unwrap(); +} From bf807c0c69f566e6dbced64373d7003dd46b3733 Mon Sep 17 00:00:00 2001 From: Michiel Date: Mon, 20 Jan 2025 17:06:51 +0100 Subject: [PATCH 2/4] Add Exercise 5.2.1: Lettuce Crop AWS --- book/src/SUMMARY.md | 1 + book/src/rust-in-the-cloud.md | 101 ++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 book/src/rust-in-the-cloud.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index f803c6a..77b1867 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Rust for Web]() - [Rust for Web Servers](rust-for-web-servers.md) + - [Rust in the Cloud](rust-in-the-cloud.md) - [Rust for Systems Programming]() - [Foreign Function Interface](foreign-function-interface.md) diff --git a/book/src/rust-in-the-cloud.md b/book/src/rust-in-the-cloud.md new file mode 100644 index 0000000..97acb66 --- /dev/null +++ b/book/src/rust-in-the-cloud.md @@ -0,0 +1,101 @@ +# Unit 5.2 - Rust in the Cloud + +## Exercise 5.2.1: Lettuce Crop AWS +In this exercise, we will port our Lettuce Crop website from exercise 5.1.1 to the cloud using [AWS Lambda](https://aws.amazon.com/lambda/). AWS Lambda allows you to run code in the cloud in a serverless configuration. This means that machines in Amazon's data centers will automatically start running your code when needed, which means you do not have to worry about managing servers, and you only pay for the compute time you use. + +
+ +For this exercise, the [AWS free tier](https://aws.amazon.com/free/) should be sufficient. However, please do remember to shut off your Lambdas once you are done testing to avoid any unexpected costs! See the [free tier page](https://console.aws.amazon.com/billing/home#/freetier) in the billing and cost management section of the AWS console to see how much of the free tier quotas you have left this month. + +
+ +### 3.2.1.A Setting up Cargo Lambda +To build for AWS Lambda with Rust, we will use [Cargo Lambda](https://www.cargo-lambda.info/). You can install Cargo Lambda with [Cargo Binstall](https://github.com/cargo-bins/cargo-binstall): +``` +cargo binstall cargo-lambda +``` +You may also need to install [Zig](https://ziglang.org/), which is used for [cross-compilation](https://www.cargo-lambda.info/guide/cross-compiling.html). Cargo Lambda will inform you if Zig is not installed when building your Lambda, in which case it will attempt to help you install it automatically via `pip` or `npm`. + + +Alternatively, you can use any of the other installation methods for Cargo Lambda found [here](https://www.cargo-lambda.info/guide/installation.html). + +### 3.2.1.B Axum router with Lambda HTTP +The [`lambda_runtime`](https://crates.io/crates/lambda_runtime/) crate provides the runtime for AWS Lambdas written in Rust. The [`lambda_http`](https://crates.io/crates/lambda_http) crate provides an abstraction layer on top of the `lambda_runtime` to make it easy to develop HTTP servers on AWS Lambda with Rust, which is ideal for small dynamic websites or REST APIs. + +Add `lambda_http` to the Rust project with: +``` +cargo add lambda_http +``` + +Since `lambda_http` is able to run `axum` routers, we only really need to change the main function to convert our Lettuce Crop server to a Lambda. We create our `Router` as usual, but instead of serving it with `axum::serve`, we run the `Router` with the `run` function from `lambda_http`: +```rust +use lambda_http::{run, tracing, Error}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing::init_default_subscriber(); + + let app = Router::new() + .route("/crop", post(crop_image)) + .fallback_service(ServeDir::new("assets")); + + run(app).await +} +``` +Update the main function as above and then try out the Lambda with: +``` +cargo lambda watch +``` +This will emulate the Lambda locally on your device, serving it on [http://localhost:9000/](http://localhost:9000/) by default. + +### 3.2.1.C Setting up a Lambda function in the AWS console +Now that we've tested our Lambda locally, let's create a Lambda function in the AWS console. Go to the [AWS Lambda page](https://console.aws.amazon.com/lambda/home) in the AWS console, and click "Create a function". Then, configure it as follows: + +1. Select "Author from scratch" +2. Give it a name, for example "lettuce-crop" +3. Select the "Amazon Linux 2023" runtime +4. Select "arm64" architecture (which offers lower costs compared to x86_64) +5. In "Additional Configurations" enable "Enable function URL", and select Auth type "NONE" to get a publicly accessible URL for your Lambda function + +Finally, click "Create function" and wait a few seconds for your Lambda to be created. + +### 3.2.1.D Building & deploying our Lambda function +Before we deploy our Lambda, we first have to build our project with the appropriate architecture: +``` +cargo lambda build --release --arm64 --output-format zip +``` +This will generate a `bootstrap.zip` in the `target/lambda/{project name}` folder, which we can upload in the AWS console to deploy our Lambda. + +However, this zip file does not contain our assets. If we want our Lambda to be able to serve our HTML document and the corresponding CSS file and image, we have to include these assets. Let's create a `CargoLambda.toml` config file to specify how we want to build our Lambda, and include the following: +```toml +[build] +arm64 = true +output_format = "zip" +include = ["assets/index.html", "assets/styles.css", "assets/crop.webp"] +``` +If we now build our Lambda with `cargo lambda build --release` we will get a zip that also contains our assets (we no longer need the `--arm64` and `--output-format` command line arguments, as these are now set in our config file). + +Alternatively, if you are using [memory-serve](https://docs.rs/memory-serve/latest/memory_serve/) to serve the assets, as described in exercise 5.1.1.G, you will not need to include the assets in the zip, as they already will be included in the binary. + +To deploy the Lambda, click the "Upload from" button in the "Code" tab for our Lambda in the AWS console. Then, upload the `bootstrap.zip` file. Now, the Lambda should be live! Open the function URL listed in the function overview at the top of the page to try it out! + +You can also use `cargo lambda deploy` to deploy your Lambda via the CLI. However, this does require you to set up [AWS credentials](https://www.cargo-lambda.info/guide/automating-deployments.html) first. + +Note that AWS Lambda only accepts files up to 50 MB, for larger projects you can instead upload to an [S3 bucket](https://aws.amazon.com/s3/). S3 does not have a free tier, but it does have a 12-month free trial. + +### 3.2.1.E Analyzing Lambda usage via CloudWatch +Now that our Lambda is up and running, let's take a look around the AWS console. If you go to the "Monitor" tab, you can see some metrics about the requests handled by the Lambda function. These basic metrics are automatically gathered by CloudWatch free of charge. + +If you scroll down to CloudWatch Logs, you will see recent invocations of the Lambda function. If you click on the log stream of one of these requests, you will see the logs produced while handling the request. The outputs from any `println!`'s or logs from the [`tracing`](https://docs.rs/tracing/latest/tracing/) crate should show up here. The free tier of CloudWatch allows you to store up to 5 GB of log data for free. + +You can also see a list of the most expensive invocations on the "Monitor" tab. The cost is measured in gigabyte-seconds, which is the amount of memory used for the duration it took to handle the request. The free tier for AWS Lambda gives you 1,000,000 requests and 400,000 gigabyte-seconds for free per month. + +By default, Lambdas are configured with 128 MB of memory, which can be increased in the "Configuration" tab (but it cannot be set lower than 128 MB). In this tab you can also configure the timeout for handling requests. By default, the Lambda will time out after 3 seconds, but this can be changed if needed. + +#### Where to go from here? + +- The [Rust Runtime for AWS Lambda GitHub repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples) contains a bunch of useful examples, which show for example how to [interact with S3 buckets](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-s3-thumbnail) or how to [create Lambda extensions](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/extension-custom-service). +- The [AWS SDK for Rust](https://docs.aws.amazon.com/sdk-for-rust/latest/dg/welcome.html) allows you to interact with AWS services via Rust. + +Remember to throttle or delete the Lambda function once you are done testing to prevent unexpected costs! From 73cea3d5c7efe73d3b51e04e3ca1ad3a212ac269 Mon Sep 17 00:00:00 2001 From: Michiel Date: Thu, 30 Jan 2025 10:27:06 +0100 Subject: [PATCH 3/4] Use IPv6 by default --- book/src/rust-for-web-servers.md | 6 ++++-- .../1-rust-for-web-servers/1-lettuce-crop/src/main.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/book/src/rust-for-web-servers.md b/book/src/rust-for-web-servers.md index 291e4a1..5587ac3 100644 --- a/book/src/rust-for-web-servers.md +++ b/book/src/rust-for-web-servers.md @@ -6,9 +6,11 @@ In this exercise, we will build a simple web server with [`axum`](https://lib.rs/crates/axum) which allows users to upload images to crop them. You will learn how to serve static HTML pages along with their associated style sheets and images, and you will learn how to handle POST requests with multipart form data to receive the uploaded images. ### 3.1.1.A Hello axum -In `exercises/5-rust-for-web/1-rust-for-web/1-lettuce-crop` we have set up the start of our web server. It currently only serves "Hello, world!" for GET requests on the main page. Run the program and go to [http://0.0.0.0:7000/](http://0.0.0.0:7000/) in your browser to see if it works. +In `exercises/5-rust-for-web/1-rust-for-web/1-lettuce-crop` we have set up the start of our web server. It currently only serves "Hello, world!" for GET requests on the main page. Run the program and go to [http://[::]:7000/](http://[::]:7000/) in your browser to see if it works. -When defining the router, we can chain multiple routes to serve multiple end-points. Try adding a second route which serves GET requests on another page (e.g. `/hello`). +Note that [http://[::]:7000/](http://[::]:7000/) is the default address for [IPv6](https://en.wikipedia.org/wiki/IPv6). If you do not want to use IPv6, you can use [http://0.0.0.0:7000/](http://0.0.0.0:7000/) instead. The website will also be available on local host, see for example [http://localhost:7000/](http://localhost:7000/), [http://127.0.0.1:7000/](http://127.0.0.1:7000/), or [http://[::1]:7000/](http://[::1]:7000/) (IPv6). + +In `main.rs` you can see the `Router` that is used to serve "Hello, world!". We can chain multiple routes to serve multiple end-points. Try adding a second route which serves GET requests on another page (e.g. `/hello`). ### 3.1.1.B Serving static files Currently, our web server only serves static strings. To serve static HTML documents, CSS style sheets, images and other files, we will use the [`ServeDir`](https://docs.rs/tower-http/latest/tower_http/services/struct.ServeDir.html) file server from `tower_http`. We can add this file server to our router as a fallback service to resolve any request which does not match any other defined route with our file server. diff --git a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs index 01bc07f..acebe71 100644 --- a/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs +++ b/exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/src/main.rs @@ -6,7 +6,7 @@ async fn main() { let app = Router::new().route("/", get("Hello, world!")); // Serve the website on localhost - let addr = "0.0.0.0:7000"; + let addr = "[::]:7000"; let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); println!("Serving on http://{addr}"); axum::serve(listener, app).await.unwrap(); From a13b781f7947fe183cad5bf95a01258b6669d49a Mon Sep 17 00:00:00 2001 From: Michiel Date: Fri, 24 Jan 2025 16:32:38 +0100 Subject: [PATCH 4/4] Add Exercise 5.3.1: Lettuce Crop WebAssembly --- .vscode/settings.json | 1 + book/src/SUMMARY.md | 1 + book/src/rust-in-the-browser.md | 105 ++ .../1-lettuce-crop-wasm/Cargo.lock | 1018 +++++++++++++++++ .../1-lettuce-crop-wasm/Cargo.toml | 11 + .../1-lettuce-crop-wasm/assets/crop.webp | Bin 0 -> 8182 bytes .../1-lettuce-crop-wasm/assets/index.html | 36 + .../1-lettuce-crop-wasm/assets/styles.css | 71 ++ .../1-lettuce-crop-wasm/src/lib.rs | 11 + 9 files changed, 1254 insertions(+) create mode 100644 book/src/rust-in-the-browser.md create mode 100644 exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.lock create mode 100644 exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.toml create mode 100644 exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/crop.webp create mode 100644 exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/index.html create mode 100644 exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/styles.css create mode 100644 exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/src/lib.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index f934b47..637bd69 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,6 +24,7 @@ "exercises/4-multitasking/3-asynchronous-multitasking/1-async-channels/Cargo.toml", "exercises/4-multitasking/3-asynchronous-multitasking/2-async-chat/Cargo.toml", "exercises/5-rust-for-web/1-rust-for-web-servers/1-lettuce-crop/Cargo.toml", + "exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.toml", "exercises/6-rust-for-systems-programming/1-foreign-function-interface/1-crc-in-c/Cargo.toml", "exercises/6-rust-for-systems-programming/1-foreign-function-interface/2-crc-in-rust/Cargo.toml", "exercises/6-rust-for-systems-programming/1-foreign-function-interface/3-qoi-bindgen/Cargo.toml", diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 77b1867..66e10ea 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -22,6 +22,7 @@ - [Rust for Web]() - [Rust for Web Servers](rust-for-web-servers.md) - [Rust in the Cloud](rust-in-the-cloud.md) + - [Rust in the Browser](rust-in-the-browser.md) - [Rust for Systems Programming]() - [Foreign Function Interface](foreign-function-interface.md) diff --git a/book/src/rust-in-the-browser.md b/book/src/rust-in-the-browser.md new file mode 100644 index 0000000..29cf1ca --- /dev/null +++ b/book/src/rust-in-the-browser.md @@ -0,0 +1,105 @@ +# Unit 5.3 - Rust in the Browser + +## Exercise 5.3.1: Lettuce Crop WebAssembly +In exercise 5.1.1, we build a web server that hosts an image cropping service. But do we really need to do this cropping on our server? Wouldn't it be much more privacy-friendly if we could do the image cropping in the user's browser instead of uploading images to our external server? + +In this exercise, we will create a new version of our Lettuce Crop website that crops images with [WebAssembly](https://webassembly.org/). WebAssembly allows you to run compiled code in a safe sandboxed environment in the browser. This means we will not need a dedicated server anymore, as the website will only consist of static files which we can be hosted using any HTTP server. You could even host it for free using [GitHub pages](https://pages.github.com/)! + +### 3.2.1.A Building with Wasm Pack +In `exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm` we have set up a basic WebAssembly project. As you can see in the `Cargo.toml`, the project has been configured as a dynamic library (`"cdylib"`). We've also added the `wasm-bindgen` crate as a dependency, which is used to generate WebAssembly bindings. + +To build the project, we will use `wasm-pack`. First, install `wasm-pack` with: +``` +cargo install wasm-pack +``` +Then, build the project with `wasm-pack`. Since we want to use it in the browser, we set the [wasm-pack target](https://rustwasm.github.io/docs/wasm-pack/commands/build.html#target) to `web`, and we tell it to put the generate files in the `assets/pkg` folder: +``` +wasm-pack build --target web --out-dir assets/pkg +``` + +Now, a bunch of files should appear in the `assets/pkg` folder: +- A `.wasm` file, which contains the compiled WebAssembly code +- Some `.d.ts` files, which describe the TypeScript types of the generated bindings +- A `.js` files, which contains the JavaScript bindings for our WebAssembly binary + +### 3.2.1.B Interacting with JavaScript +So what functionality does the compiled WebAssembly currently include? In `lib.rs` you can see two functions: an extern `alert()` function, and a `hello()` function. Both of these functions have been annotated with `#[wasm_bindgen]` to indicate that we want to bind them with WebAssembly. Extern functions will be bound to existing JavaScript methods, in this case the [window's `alert()` function](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) which shows a popup dialog. + +Let's add the WebAssembly to our website. Add the following JavaScript in the `` of the `index.html` to load the WebAssembly binary and call our `hello()` function when we press the submit button: +```html + +``` + +To try out the website, you can use any HTTP server that is able to serve local files. You could use `axum` to host the files like we did in exercise 5.1.1, but you can also use for example `npx http-server` if you have `npm` installed. + +### 3.2.1.C Cropping images +Let's add a `crop_image(bytes: Vec, max_size: u32) -> Vec` function to our Rust library that will crop our images. You can use the same logic as in exercise 5.1.1 (part D and E) to create a `DynamicImage` from the input bytes, crop it, and export it as WebP. Mark the function with `#[wasm_bindgen]` and rebuild the library to generate WebAssembly bindings for it. + +If you look at the generated JavaScript bindings, you will see that the `Vec`s for the `crop_image` function have been turned into `Uint8Array`s. We will need to write some JavaScript to read the user's selected image and give it to our `crop_image` as a `Uint8Array`. + +First, let's grab our other two input elements: +```js +const max_size = document.querySelector('input[name="max_size"]'); +const image = document.querySelector('input[name="image"]'); +``` +Then, in the `onclick` of the submit button, you can grab the selected file using `image.files[0]`. To get the contents of the file, we will use a [`FileReader`](https://developer.mozilla.org/en-US/docs/Web/API/FileReader): +```js +const file = image.files[0]; +const reader = new FileReader(); +reader.onload = (evt) => { + const bytes = new Uint8Array(evt.target.result); + const cropped_bytes = crop_image(bytes, max_size.value); // call our function + // TODO: do something with the cropped_bytes +}; +reader.readAsArrayBuffer(file); +``` +Finally, to display the resulting cropped image to the user, we will construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from the `Uint8Array`, and turn this `Blob` into a URL to which we will redirect the user: +```js +window.location.href = URL.createObjectURL(new Blob([cropped_bytes])); +``` +If you select an invalid file, you will get an error in the browser console. Feel free to add some better error handling by using a [try-catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch), and by validating whether `image.files[0]` exists before reading it. It would also be nice to verify that `max_size` has a sensible value. + +### 3.2.1.D Using the web-sys crate (bonus) +Instead of using JavaScript to interact with the HTML document or manually binding extern JavaScript functions using `#[wasm_bindgen]` like we saw with `alert()`, we can also use the [`web-sys`](https://crates.io/crates/web-sys) crate. This crate provides bindings for the JavaScript web APIs available in the browser. However, most of these APIs have to be manually enabled with individual features. + +Add the `web-sys` crate to your project with all the needed features enabled: +``` +cargo add web-sys --features "Window,Document,HtmlElement,HtmlImageElement,Blob,Url" +``` + +Now, instead having the `crop_image` function return an array of bytes, let's have it instead append an image to HTML document: +- First, get the HTML body element: + ```rust + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + ``` +- Then, we can create an HTML image element: + ```rust + let img = document.create_element("img").unwrap(); + let img: web_sys::HtmlImageElement = img.dyn_into().unwrap(); + ``` +- To set the source of the image, we will again need to create a `Blob` to get a temporary data URL. For this, we first create a JavaScript array: + ```rust + let bytes = web_sys::js_sys::Array::new(); + bytes.push(&web_sys::js_sys::Uint8Array::from(&buffer[..])); + ``` +- And then we can create a Blob and create a URL: + ```rust + let blob = web_sys::Blob::new_with_u8_array_sequence(&bytes).unwrap(); + let url = web_sys::Url::create_object_url_with_blob(&blob).unwrap(); + ``` +- And finally, we can set the image's source and append the image to the document's body: + ```rust + img.set_src(&url); + body.append_child(&img).unwrap(); + ``` +- Remember to also update the JavaScript code in the HTML document accordingly. diff --git a/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.lock b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.lock new file mode 100644 index 0000000..3fca472 --- /dev/null +++ b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.lock @@ -0,0 +1,1018 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "lettuce-crop-wasm" +version = "0.1.0" +dependencies = [ + "image", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] diff --git a/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.toml b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.toml new file mode 100644 index 0000000..88585a3 --- /dev/null +++ b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lettuce-crop-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2.100" +image = "0.25.5" diff --git a/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/crop.webp b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/crop.webp new file mode 100644 index 0000000000000000000000000000000000000000..e537c54c9382fe6a58cbfbccf903568778c429b7 GIT binary patch literal 8182 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 literal 0 HcmV?d00001 diff --git a/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/index.html b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/index.html new file mode 100644 index 0000000..f182d73 --- /dev/null +++ b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/index.html @@ -0,0 +1,36 @@ + + + + + + Lettuce Crop + + + + + + +
+
+
+

Lettuce Crop

+

Upload an image to make it square!

+
+
+ +
+

+ + +

+

+ + + + +

+
+ + + \ No newline at end of file diff --git a/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/styles.css b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/styles.css new file mode 100644 index 0000000..2585a4a --- /dev/null +++ b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/assets/styles.css @@ -0,0 +1,71 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@100..900&display=swap'); + +html { + background-color: #a1a691; +} + +body { + max-width: 40em; + margin: 4em auto; + padding: 1.5em 4em; + font-family: "Noto Sans", Arial, Helvetica, sans-serif; + background-color: #fff; + box-shadow: 0.5em 0.5em 0em #0006; +} + +header { + display: flex; + align-items: center; + margin: 1.5em 0; +} + +.logo { + float: left; + margin-right: 1em; + width: 5em; +} + +hgroup>* { + margin: 0; + line-height: 1.2em; +} + +h1 { + color: #3f7a16; +} + +hgroup>p { + font-size: 1.25em; +} + +form { + text-align: center; + margin: 1em 0; +} + +.box { + background-color: #b4d385; + border-radius: 1em; + padding: 1em; +} + +.box>label { + display: block; + padding-bottom: 0.5em; +} + +input:not([type="file"]) { + border-radius: 4px; + border: 1px solid #727272; + padding: 0.5em 0.5em; + min-width: 6em; +} + +input[type="submit"], +input[type="file"] { + cursor: pointer; +} + +.divider { + margin: 0 0.5em; +} \ No newline at end of file diff --git a/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/src/lib.rs b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/src/lib.rs new file mode 100644 index 0000000..8cf1866 --- /dev/null +++ b/exercises/5-rust-for-web/3-rust-in-the-browser/1-lettuce-crop-wasm/src/lib.rs @@ -0,0 +1,11 @@ +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen] +extern "C" { + fn alert(s: &str); +} + +#[wasm_bindgen] +pub fn hello(world: &str) { + alert(format!("Hello {world}!").as_str()); +}