Git Product home page Git Product logo

workers-rs's People

Contributors

aseure avatar avsaase avatar bastidood avatar bytealex avatar cmoore-darwinium avatar dakom avatar dbw9580 avatar dependabot[bot] avatar fisherdarling avatar fkettelhoit avatar flareline avatar github-actions[bot] avatar j-white avatar jakubadamw avatar jasper-bekkers avatar jdon avatar jyn514 avatar kakapio avatar kflansburg avatar kpcyrd avatar leoorshansky avatar mendess avatar nilslice avatar ocsfrank avatar sebastiaanyn avatar slester avatar voidstar0 avatar witchof0x20 avatar xtuc avatar zebp avatar

Stargazers

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

Watchers

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

workers-rs's Issues

Returning JSON

Discussed in #70

Originally posted by vorcigernix October 16, 2021
I am trying to follow example code from the readme. For some reason it returns

KvError::Serialization: expected value at line 1 column 1
panicked at 'KvError::Serialization: expected value at line 1 column 1', src/lib.rs:20:1

where error line is where I serialize object to json like
Some(jmeniny) => Response::from_json(&jmeniny.as_json::<Jmeniny>()?),
When I format the response as a string, it works fine:
Some(jmeniny) => Response::ok(&format!("response: {:?}", &jmeniny)),
results in
response: KvValue("Hynek")
It seems that from_json use serde internally, so it should work fine. Or I am overlooking something basic in KV.
Jmeniny struct is like this:

#[derive(Deserialize, Serialize)]
struct Jmeniny {
    name: String,
}

I think that it is some stupid mistake, but I'll appreciate any help.

Auto-generated bindings

Now that @mrbbot has added support for doing this in TypeScript (cloudflare/workers-types#112), we have the infrastructure for doing this for Rust too. He actually did both but the Rust one just hasn't been integrated. Not sure what actually needs to be done here but opening this issue just to track it.

What's the release cadence?

It has been two months since the last release and master contains fixes that I need.

Is there a reason to not release patch versions after every merge?

Mutable Request/Response in place

Hi! ๐Ÿ‘‹

I'm currently trying to port my Cloudflare worker from TypeScript to Rust. I'm using a middleware pattern where I want pass a worker::Request to various functions that will transform them (e.g. transform the URL, strip certain headers, etc.) before sending to the origin. Similarly, I want to transform the worker::Response through multiple middlewares before sending it back to the end-user.

As far as I can, as the fields of both objects are private, I can only access those values through functions (e.g. req.path(), req.headers()), which means I can't mutate those values in place.

Is there any plan to support this type of pattern in Rust? Or is there anything I'm missing that would allow me to do that?

Use `log` instead of `console_log!`

This will mean all the logging from libraries that developers don't control will still show up in the JS console. We could implement it by registering a log handler which outputs to the JS console.

add GitHub Actions to make it easier for contributors and maintainers

Would suggest adding GitHub Actions (clippy and build check) which runs on every PR/merge to main that makes it easier for maintainers to review Pull Requests as well as for contributors to improve their coding style when they contribute to this repository.

Would be happy to raise a PR for the same ๐Ÿ˜„

Confusing error when trying to define both a durable worker and a fetch handler in the same file

Hi there,

I'm trying to play around with Durable Workers, so I copied in the Chatroom example from the README into my pre-existing lib.rs (which was mostly the default example after running wrangler generate).

My entire src/lib.rs file looks like this:

use worker::*;

#[durable_object]
pub struct Chatroom {
    state: State,
    env: Env, // access `Env` across requests, use inside `fetch`
}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self { state: state, env }
    }

    async fn fetch(&mut self, _req: Request) -> Result<Response> {
        todo!()
    }
}

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    Response::error("Bad Request", 400)
}

Running wrangler build, I get this error:

[INFO]: Compiling to Wasm...
   Compiling project_name v0.1.0 (/home/achin/tmp/70/project_name)
error[E0428]: the name `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` is defined multiple times
  --> src/lib.rs:20:1
   |
3  | #[durable_object]
   | ----------------- previous definition of the value `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` here
...
20 | #[event(fetch)]
   | ^^^^^^^^^^^^^^^ `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` redefined here
   |
   = note: `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` must be defined only once in the value namespace of this module
   = note: this error originates in the attribute macro `event` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0428`.
error: could not compile `project_name` due to previous error
Error: Compiling your crate to WebAssembly failed
Caused by: failed to execute `cargo build`: exited with exit status: 101
  full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"
Error: wasm-pack exited with status exit status: 1
Error: Build failed! Status Code: 1

I think it's illegal to define a fetch handler and also a durable object in the same file. Is that true?
If so, I would be great if this could be detected better (better error message), and even greater if the README stated this a bit more clearly :)

Version info:

rustc: rustc 1.58.0-nightly (dd549dcab 2021-11-25)
wrangler: wrangler 1.19.5
workers-rs: 0.0.7

Hello World example

Current examples in https://github.com/cloudflare/workers-rs/blame/main/README.md#L9, https://blog.cloudflare.com/workers-rust-sdk/ and project generated by wrangler generate --type=rust all provide some advanced usage of using router, console_log, DO, POST handling. None of them worked out of the box for me.

For those whose just start out, like me, would it be possible to provide and example worker that just returns Hello world string so we could start from there? Thanks a lot!

re-export worker_kv::KvStore type

The type for KvStore is not available from the worker crate. I need to add the worker_kv dependency to be able to get the KvStore type. It might be a good idea to re-export the KvStore type. This will insure that the KvStore type used in one's project is the same type being used by the worker crate.

Questions/Feedback after using this crate for Cloudflare Internship Hiring Assignment

Hi,
In the last week I applied to and attempted the Cloudflare Internship Hiring Assignment https://apply.cloudflareworkers.com/ with this crate. I would say I completed a majority of it but at some point I was too unsatisfied with how my code looked so I switched to using JavaScript for my worker. I had some general questions about what the expected/idiomatic way of doing certain things were for which I couldn't find any documentation/examples for -

  • What would be the idiomatic way to implement Error Handling in my worker ? My understanding of how it is setup currently is that any error from my handler would be converted to a worker::Error which by default causes an error response of some sort with an HTML body. If I were developing a REST API with JSON responses what would be the cleanest way to convert my errors to the appropriate JSON responses with a unique error code ?
  • Maybe related to the above - there doesn't seem to be any support for adding middleware to my Router. This proved troublesome when I had to add CORS headers to all my responses or to selectively authenticate some of my handlers. Is the expectation to use one of the existing crates in the ecosystem to handle this ? Or is it just something thats on the roadmap.

I hope my message doesn't come of as too aggressive. I realize that Rust support is in very early stages and would be happy to contribute to this crates code / documentation / examples etc.

Can't run wrker-build, assumes ~/.cargo/bin is in PATH and fails otherwise

๐Ÿ› Bug report

Describe the bug

This is just developer friction / newbie trap.

Wrangler 1.19.2 installed by building via cargo.

Reproduce the bug

With the template Rust workers demo from https://blog.cloudflare.com/workers-rust-sdk/

$ wrangler dev 
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
sh: worker-build: command not found
Error: Build failed! Status Code: 127

$ PATH="$PATH:$HOME/.cargo/bin"
$ wrangler dev 
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
Installing wasm-pack...

Expected behavior

Ideally:

  1. Don't rely on user $PATH.
  2. Don't even attempt to install things on the fly automatically. (My opinion.)

Environment and versions

Fill out the following information about your environment.

  • operating system: Linux, NixOS 21.05
  • output of wrangler -V: wrangler 1.19.2
  • output of node -v: node: command not found
  • content of wrangler.toml: unedited from template

Make `worker::Error` public

Even if we don't make it constructable, it would still be nice to make the type itself public so you can see what traits are implemented, something like this:

pub struct Error(ErrorInner);
enum ErrorInner { ... }

No Implementation of `redirect()` for `Response`

Hey There! ๐Ÿ‘‹

Was just exploring the use of worker-rs where i eventually figured out that there is no redirect() method implementation for the Response struct, which means that i cannot redirect to a URL (302,308,etc) for an response recieved. It also becomes inconsistent with the documentation found at https://developers.cloudflare.com/workers/runtime-apis/response#instance-methods where it specifies an instance method for redirect().

Proposal:
Implement a function in the Impl of Response struct with the apt headers, and struct fields which becomes available for the user to use

Would be happy to raise a PR for the same ๐Ÿ˜ƒ

Responses being mangled

Iโ€™m experimenting with using the async-graphql crate inside a worker. When I use wee_alloc as the global allocator, the first few bytes of each response from the worker are being consistently โ€˜mangledโ€™ for some reason. Here is a repository with a minimal worker which replicates the issue: https://github.com/thomasfoster96/wealloc-issue and here is the worker in action: https://wealloc-issue.thomasfoster.workers.dev/.

Here I what I would expect my worker to produce:

{"data":null,"errors":[{"message":"Bad Request"}]}

Instead, this is what I get:

๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝta":null,"errors":[{"message":"Bad Request"}]}

The bytes are mangled the same way on every request. Calling serde_json::to_string on the GraphQL request and then logging it with console_log! produces the correct output โ€” this is only happening when I use Response::from_json or similar methods.

Creating a response body in other ways produces similar results. For example, using serde_json::to_string produces the correct output but using Response::from_html then mangles it, as does using serde_json::to_vec and then Response::from_bytes.

(Apologies if any of this is unclear, Iโ€™m very much a beginner with Rust and Cloudflare Workers)

Workers Sites support

Hi ๐Ÿ‘‹

I had tried to re-implement the Workers Sites using Rust, but I was stuck by KV access.

As far as I know, the kv-asset-handler package uses the STATIC_CONTENT_MANIFEST variable from global context generated by wrangler. Am I right?

If it is, can you provide some ways to accessing the manifest or Rust version of getAssetFromKV function?

gRPC support

This is a probably a very big task, since JS Workers don't seem to have an existing library or a nice way to do it. There are some existing crates like tonic which support gRPC but these likely wouldn't work without a lot of changes. gRPC also has streams, which some platforms don't support while still supporting unary requests (possibly for similar reasons that might prevent Workers from supporting them). gRPC-web might be easier, but do not currently support streams.

Another possible issue is Cloudflare's support for gRPC since HTTP/2 isn't just enabled for everything by default and Workers might not currently support HTTP/2(?).

Consider making Router Data not an Option

It seems the only reason Router and RouteContext data method return an Option<Data> is because you've decided to implement Default for a router. Nothing seems to use that default. Could I convince you to remove the Default and make data be not optional? That would remove a .unwrap() from practically every handler of every app using data.

For inspiration/justification/examples:

Confusing 403 error with `wrangler dev`

I had wrangler logined to my Cloudflare account already, but when trying to wrangler dev I received a 403 error that was not very useful in helping me determine the cause:

wrangler dev
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
    Finished release [optimized] target(s) in 0.03s
[INFO]: Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: :-) Done in 2.10s
[INFO]: :-) Your wasm pkg is ready to publish at /home/jason/src/my-project/build.
Error: HTTP status client error (403 Forbidden) for url (https://api.cloudflare.com/client/v4/accounts/ae126deafb76cf635e5e028f594434ac/workers/subdomain/edge-preview)

I think I tracked it down to not enabling the free cloudworker component in my account, but 1) I wasn't expecting a local dev cli command to require that and 2) it would have been good to spell that out in the returned 403.

Now it seems to be working past that (but getting a different error I'm trying to make sense of). Thanks for the cool project!

`#[event(fetch)]` should name the generated function after the input

This error is confusing:

error[E0428]: the name `fetch` is defined multiple times
  --> rust-sandbox/src/lib.rs:38:1
   |
36 | fn fetch() {}
   | ---------- previous definition of the value `fetch` here
37 | 
38 | #[event(fetch, respond_with_errors)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fetch` redefined here
   |
   = note: `fetch` must be defined only once in the value namespace of this module
   = note: this error originates in the attribute macro `event` (in Nightly builds, run with -Z macro-backtrace for more info)

It would be better to name the generated function whatever it was originally (in this case, main).

Custom status code support

I'd like to be able to set arbitrary status codes (201, 202, 204, 302, etc.) and currently, that is impossible because Response::status_code is private and there is no method to create a custom response or modify a response like with_status(self, code: u16) (similar to with_headers.)

This will go away with #13.

scheduled event failed

lib.rs:

mod utils;
use worker::*;
// could not find `Schedule` in `worker`rustc(E0433)
#[event(scheduled)]
pub async fn main(ty: String, schedule: u64, cron: String) -> Result<()> {
    println!("test");
    Ok(())
}

Is there something I missed?

`with_headers` on `Response` does nothing when inner is `ResponseBody::Stream`

A response from Fetch can't be modified:

Fetch::Request(request).send().await?.with_headers(Headers::new())

The code compiles but doesn't actually update the headers.

My workaround:

use wasm_bindgen::{prelude::*, JsCast};
use worker::worker_sys;

#[wasm_bindgen]
extern "C" {
    pub type Response;

    #[wasm_bindgen(catch, constructor, js_class=Response)]
    pub fn new_with_opt_stream_and_init(
        body: Option<web_sys::ReadableStream>,
        init: &web_sys::ResponseInit,
    ) -> std::result::Result<Response, JsValue>;
}

impl Response {
    pub fn dup(response: worker::Response, headers: &mut worker::Headers) -> crate::Result<worker::Response> {
        let mut response_init = web_sys::ResponseInit::new();
        response_init.headers(&mut headers.0);

        let response: worker_sys::Response = response.into();
        let response = Response::new_with_opt_stream_and_init(response.body(), &response_init)?;
        let response = response.unchecked_into::<worker_sys::Response>();
        let body = worker::ResponseBody::Stream(response);

        Ok(worker::Response::from_body(body)?)
    }
}

Why not use `http`'s StatusCode to represent status code?

It seems weird for me that functions like Response::error still takes a u16 as the response status code, and impl Into<String> as the response description, since the crate http has already been a dep. So I'm expecting to have sigs like

pub fn error(code: http::StatusCode) -> Result<Self>;
pub fn status_code(&self) -> http::StatusCode;

Wrapping around http::StatusCode to have custom types like ErrorStatusCode will also allow us us to lift the following error to type-level & reduce runtime cost (well, maybe just a little). And I believe this is an error that should not be thrown in runtime.

if !(400..=599).contains(&status) {
    return Err(Error::Internal(
        "error status codes must be in the 400-599 range! see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status for more".into(),
    ));
}

KVStore corrupts binary data

I'm trying to store binary data in the KV, but the data appears to getting corrupted. Consider this code:

.get_async("/test", |req, ctx| async move {
            let kv = ctx.kv("UPLOAD")?;
            let bytes = [0xd2, 0xda, 0x1e, 0x77, 0x3c, 0x50, 0xd9, 0x35, 0x9a, 0x89, 0xd5, 0x53, 0xc3, 0x81, 0x6d, 0x20];
            kv.put_bytes("test", &bytes)?.execute().await?;

            let bytes_from_kv = kv.get("test").await?.unwrap();
            assert_eq!(&bytes, bytes_from_kv.as_bytes());

            Response::empty()
        })

Trying to run this yields this assertion failure:

panicked at 'assertion failed: `(left == right)`
  left: `[210, 218, 30, 119, 60, 80, 217, 53, 154, 137, 213, 83, 195, 129, 109, 32]`,
 right: `[239, 191, 189, 239, 191, 189, 30, 119, 60, 80, 239, 191, 189, 53, 239, 191, 189, 239, 191, 189, 239, 191, 189, 83, 195, 129, 109, 32]`', src/lib.rs:56:13

However when I download this value via the https://dash.cloudflare.com/, the data is correct (so it seems like the corruption is happening during the fetch on the rust side)

Version info

  • worker-rs version v0.0.7
  • wrangler version 1.19.6
  • rustc version 1.59.0-nightly (7abab1efb 2021-12-17)

RequestInit::new() is hanging

Hi!

It seems like something's wrong with RequestInit::new(). Here is the simple code, it creates empty RequestInit and then returns empty response. And it stucks.

use worker::*;

mod utils;

fn log_request(req: &Request) {
    console_log!(
        "{} - [{}], located at: {:?}, within: {}",
        Date::now().to_string(),
        req.path(),
        req.cf().coordinates().unwrap_or_default(),
        req.cf().region().unwrap_or("unknown region".into())
    );
}

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    log_request(&req);

    utils::set_panic_hook();

    let _init = RequestInit::new(); // the code stucks here

    Response::empty()
}

And here's the logs from wrangler dev:

๐Ÿ‘‚  Listening on http://127.0.0.1:8787
Sun Sep 12 2021 12:06:47 GMT+0000 (Coordinated Universal Time) - [/], located at: (59.8983, 30.2618), within: St.-Petersburg
Error: Worker exceeded CPU time limit. at line 0, col -2
{
  "exceptionDetails": {
    "columnNumber": -2,
    "exception": {
      "className": "Error",
      "description": "Error: Worker exceeded CPU time limit.",
      "preview": {
        "description": "Error: Worker exceeded CPU time limit.",
        "entries": null,
        "overflow": false,
        "properties": [],
        "subtype": "error",
        "type": "object"
      },
      "subtype": "error",
      "type": "object",
      "value": null
    },
    "lineNumber": 0,
    "text": "Uncaught (in response)",
    "url": "undefined"
  },
  "timestamp": 1631448407649
}
[2021-09-12 15:06:46] GET response-bot-dev.idebugger.workers.dev/ HTTP/1.1 503 Service Unavailable

could not find `worker-build` in registry `crates-io` with version `*`

I'm quite new to Rust development so it's entirely possible I'm doing something obviously wrong but I've tried pulling down this example and running according to the README and am getting this error. Anything obvious I need to adjust? Is my local env not setup appropriately?

% wrangler build                       
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
error: could not find `worker-build` in registry `crates-io` with version `*`
Error: Build failed! Status Code: 101

Feature request: No matching route closure

It would be really helpful to be able to pass in a closure to the router that is run when there is no matching route. In my use case, I'd like to be able to trigger logging and analytics. I'm using the worker as a gateway and I'd like to be sure that all the requests are being handled.

Unprivate or move `worker::response::ResponseBody`

At the moment you can't create custom responses without worker::response::ResponseBody which is in a private module.

I think the best course of action would be to move worker::response::ResponseBody to worker::ResponseBody but as I am pretty new to Rust i'll leave this up to the maintainers.

wrangler build: "Error: linker `cc` not found"

Steps to Reproduce Error:

  1. Fresh install of Ubuntu 21.10 (codename: impish, followed by update and upgrade )
  2. Installed NPM 8.1.0
  3. Installed Node v16.13.0
  4. Installed Wrangler 1.19.5
  5. Installed Rustup 1.24.3 / Cargo 1.56.0
  6. Wrangler generate --type=rust project_name

  7. cd project_name

  8. wrangler build

Following error displays:

Running cargo install -q worker-build && worker-build --release
error: linker `cc` not found
  |
  = note: No such file or directory (os error 2)

error: failed to compile `worker-build v0.0.3`, intermediate artifacts can be found at `/tmp/cargo-installarcZxR`

Caused by:
  could not compile `anyhow` due to previous error
Error: Build failed! Status Code: 101

Attempting to Google the error doesn't result in anything substantial to work on.

Reorganize before an official release

The naming of the crates in this project are a bit everywhere. It'd be nice to consolidate the names of everything and reorganize before committing to any crates.io releases.

As it stands there are currently five crates each with wildly different names:

  • edgeworker-sys
  • worker-rs-macros
  • libworker
  • worker
  • rust-worker-build

libworker could be dropped in favor of just defining everything in worker instead of using worker to re-export everything. Currently libworker is separate so it can be imported by worker-rs-macros however the macros crate never uses it so it's an unnecessary dependency. With the base name "worker", we can easily turn this into four separate easily identifiable crates.

  • worker-sys - For low-level FFI bindings
  • worker-macros - For macros
  • worker - For bringing everything together with convenience types
  • worker-build - As a build command

On top of this, it may be favorable to drop the rust-sandbox crate. Instead of testing or experimenting in a sandbox, things normally written in the sandbox would be better if written as an example in the worker crate. Then things written as examples for testing during development can be referenced later by users.

Enable files to be set as value in `FormData`

It's quite strange to have FormData::get returning FormEntry with FormData::set and FormData::append accepting only &str. It would be great if we can insert File or something like it directly into FormData.

Usage of Router inside a DurableObject: need to move env, which is required to be a reference

Hi,

Apologies in advance if I'm missing something obvious here, I'm new to Rust. I'm trying to use Router in a #[durable_object]s fetch method like so:

use worker::*;

#[durable_object]
pub struct Chatroom {
    messages: Vec<String>,
    state: State,
    env: Env,
}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self {
            messages: vec![],
            state,
            env,
        }
    }

    async fn fetch(&mut self, req: Request) -> worker::Result<Response> {
        let router = Router::with_data(&self.messages);

        router
            .get("/", |_req, context| {
                Response::ok(&format!("{} messages", context.data().len()))
            })
            .run(req, self.env)
            .await
    }
}

This fails with the error:

error[E0507]: cannot move out of `self.env` which is behind a mutable reference
  --> src/chatroom.rs:27:23
   |
27 |             .run(req, self.env)
   |                       ^^^^^^^^ move occurs because `self.env` has type `worker::Env`, which does not implement the `Copy` trait

This makes sense to me, as Router#run takes ownership of it's env argument, and since I'm taking a &mut self as a reference I can't move self.env. The #[durable_object] macro forces fetch to be &mut self so I can only give references of env. I can't work out how you would use Router inside it?

`Response::error` is a footgun

Response::error("Error", 200) is perfectly valid and is not correct at all.

Actually, looking at the implementation it would be fine if we just renamed this to Response::with_status. @nilslice what do you think? We could still have Response::internal_error or Response::request_error convenience wrappers if you think they're helpful.

(This may become a moot point after implementing #13.)

Have Response implement Clone

Currently, it appears Responses received from fetching are immutable. It would be ideal if the Response could be Cloned, modified, and then sent to a recipient.

The use case would be instances where the worker is authorized to make a request, but the authorization information in the request should not be shared with the final recipient.

If I am wrong or there is a workaround, please let me know.

Cache

How do I access Cloudflare cache?

Question about KVStore's put vs put_bytes

When trying to use KVStore's put to store bytes, I get this runtime error:

let kv = ctx.kv("UPLOAD")?;
kv.put("test", "test".as_bytes())?.execute().await?;
TypeError: KV put() accepts only strings, ArrayBuffers, ArrayBufferViews, and ReadableStreams as values.

Two bits of confusion on this:

  • The signature for put is:
pub fn put<T: ToRawKvValue>(
    &self,
    name: &str,
    value: T
) -> Result<PutOptionsBuilder, KvError>;

And there is an impl ToRawKvValue for [u8], so this code compiles. But if this function doesn't support byte slices, then why does it accept them?

  • Why can't put accept bytes? Stated another way: why is there both a put() and put_bytes() ?

Thanks!

API Improvement ideas

Hello,

I've been working with the workers-rs api recently and noticed it has got a few rough edges in my opinion.

I've thought how that could be improved thus I am suggesting this here, willing to create a PR once the idea has been approved.

  1. Responses
  • The constructors of responses return a Result, which forces the user to call map to access response functions.

=> Should be the Self-Object not conatained in a Result, user should call Ok(Response) later.
I am aware of the error-possibility from set_headers, those can be wrapped into an Response-internal error object which is unwrapped in the final error handling. (See next point)

  1. Error handling using ? operator
  • Currently the router takes a function with a worker::Result, and everything which is not Ok() will be rendered as error with panic. (CF error page)
  • It's cool, that the worker::Error type is a thiserror:Error derive and can be constructed using the From trait from basically every possible Rest error, but it's still missing flexibility.

=>Make an error handling function built into the router which
a) supports any generic error type with a "FromWorkerError" trait
b) has the signature set_error_handler<T: FromWorkerError, E: 'static + Copy + Fn(T) -> Response>(fun: E).
This function will unwrap any possible error into a custom error message, if desired. The default error handler can just be a panic!().

For reference: My current "workaround" code
pub struct ResponseWrapper {
  body: Option<Vec<u8>>,
  headers: worker::Headers,
  status: u16,
  error: Option<worker::Error>,
}

impl ResponseWrapper {
  pub fn json<T: Serialize>(data: &T) -> Result<Self, serde_json::Error> {
      let data = serde_json::to_vec(data)?;
      Ok(Self::bytes(data)
          .append_header("content-type", "application/json"))
  }

  pub fn bytes(data: Vec<u8>) -> Self {
      Self {
          body: Some(data),
          headers: worker::Headers::new(),
          status: 200,
          error: None,
      }
  }

  pub fn text<S: Into<String>>(data: S) -> Self {
      Self {
          body: Some(data.into().into_bytes()),
          headers: worker::Headers::new(),
          status: 200,
          error: None,
      }
  }

  pub fn empty() -> Self {
      Self {
          body: None,
          headers: worker::Headers::new(),
          status: 204,
          error: None,
      }
  }

  pub fn with_status(mut self, status: u16) -> Self {
      self.status = status;
      self
  }

  pub fn append_header(mut self, key: &str, val: &str) -> Self {
      if let Some(err) = self.headers.append(key, val).err() {
          self.error = Some(err);
      }
      self
  }
}

impl Into<worker::Result<worker::Response>> for ResponseWrapper {
  fn into(self) -> worker::Result<Response> {
      if let Some(err) = self.error {
          Err(err)
      } else {
          let headers = self.headers;
          let status = self.status;
          if let Some(data) = self.body {
              Response::from_body(ResponseBody::Body(data))
                  .map(|resp| resp.with_headers(headers))
                  .map(|resp| resp.with_status(status))
          } else {
              Response::from_body(ResponseBody::Empty)
                  .map(|resp| resp.with_headers(headers))
                  .map(|resp| resp.with_status(status))
          }
      }
  }
}


pub struct RequestWrapper<T, D> {
  func: fn(worker::Request, worker::RouteContext<D>) -> T,
}

pub type ToResponseFn<'a, D> = Rc<dyn Fn(worker::Request, worker::RouteContext<D>) -> LocalBoxFuture<'a, ResponseWrapper>>;

pub trait ToResponseJson<'a, R, D, E> {
  fn to_response_json(self, err: E) -> ToResponseFn<'a, D>;
}

pub trait ToResponsePlain<'a, R, D, E> {
  fn to_response_plain(self, err: E) -> ToResponseFn<'a, D>;
}

pub trait ToResponseEmpty<'a, R, D, E> {
  fn to_response_empty(self, err: E) -> ToResponseFn<'a, D>;
}

impl<R: 'static, T: 'static + Future<Output=Result<R, crate::model::Error>>, D: 'static> RequestWrapper<T, D> {
  pub fn from(func: fn(worker::Request, worker::RouteContext<D>) -> T) -> Self {
      Self {
          func,
      }
  }
}

impl<
  'a,
  R: 'static + Serialize,
  T: 'static + Future<Output=Result<R, crate::model::Error>>,
  D: 'static,
  E: 'static + Copy + Fn(crate::model::Error) -> ResponseWrapper
> ToResponseJson<'a, R, D, E> for RequestWrapper<T, D> {
  fn to_response_json(self, err: E) -> ToResponseFn<'a, D> {
      Rc::new(move |req, ctx| Box::pin(to_response_json(req, ctx, err, self.func)))
  }
}

impl<
  'a,
  R: 'static + Into<String>,
  T: 'static + Future<Output=Result<R, crate::model::Error>>,
  D: 'static,
  E: 'static + Copy + Fn(crate::model::Error) -> ResponseWrapper
> ToResponsePlain<'a, R, D, E> for RequestWrapper<T, D> {
  fn to_response_plain(self, err: E) -> ToResponseFn<'a, D> {
      Rc::new(move |req, ctx| Box::pin(to_response_plain(req, ctx, err, self.func)))
  }
}

impl<
  'a,
  R: 'static + None,
  T: 'static + Future<Output=Result<R, crate::model::Error>>,
  D: 'static,
  E: 'static + Copy + Fn(crate::model::Error) -> ResponseWrapper
> ToResponseEmpty<'a, R, D, E> for RequestWrapper<T, D> {
  fn to_response_empty(self, err: E) -> ToResponseFn<'a, D> {
      Rc::new(move |req, ctx| Box::pin(to_response_empty(req, ctx, err, self.func)))
  }
}

pub fn default_error_wrapper(err: crate::model::Error) -> ResponseWrapper {
  ResponseWrapper::text(format!("Error: {}", err))
      .with_status(500)
}

async fn to_response_json<
  R: 'static + Serialize,
  T: Future<Output=Result<R, crate::model::Error>>,
  D,
  E: Fn(crate::model::Error) -> ResponseWrapper
>(req: worker::Request, ctx: worker::RouteContext<D>, error_wrapper: E, func: fn(worker::Request, worker::RouteContext<D>) -> T) -> ResponseWrapper {
  let result = func(req, ctx).await;
  match result {
      Ok(data) => {
          match ResponseWrapper::json(&data) {
              Ok(resp) => resp,
              Err(err) => {
                  // todo: error_wrapper?
                  ResponseWrapper::text(format!("{}", err))
                      .with_status(500)
              }
          }
      }
      Err(err) => {
          error_wrapper(err)
      }
  }
}

async fn to_response_plain<
  R: 'static + Into<String>,
  T: Future<Output=Result<R, crate::model::Error>>,
  D,
  E: Fn(crate::model::Error) -> ResponseWrapper
>(req: worker::Request, ctx: worker::RouteContext<D>, error_wrapper: E, func: fn(worker::Request, worker::RouteContext<D>) -> T) -> ResponseWrapper {
  let result = func(req, ctx).await;
  match result {
      Ok(data) => ResponseWrapper::text(data),
      Err(err) => {
          error_wrapper(err)
      }
  }
}

async fn to_response_empty<
  R: 'static + None,
  T: Future<Output=Result<R, crate::model::Error>>,
  D,
  E: Fn(crate::model::Error) -> ResponseWrapper
>(req: worker::Request, ctx: worker::RouteContext<D>, error_wrapper: E, func: fn(worker::Request, worker::RouteContext<D>) -> T) -> ResponseWrapper {
  let result = func(req, ctx).await;
  match result {
      Ok(_) => ResponseWrapper::empty(),
      Err(err) => {
          error_wrapper(err)
      }
  }
}

trait None {}

impl None for () {}

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.