ferristseng / rust-ipfs-api

IPFS HTTP client in Rust
Apache License 2.0
247 stars 68 forks source link

Feature Request: Allow add_with_options to support virtual/non-existent directory structures. #84

Closed iohzrd closed 2 years ago

iohzrd commented 3 years ago

This is something I can do using js-ipfs-http-client:

      const dir_obj = [
        {
          path: "some.json",
          content: JSON.stringify(object),
        },
      ];
      const add_options = {
        wrapWithDirectory: true,
      };
      const add_result = await ipfs.add(obj, add_options);

I looked into writing the equivalent with build_base_request and request_stream_json like add_path does but couldn't because they are private.

Is there another way to do this already? If not, I suppose I could take a swing at implementing it. Would adding an Optional form argument be a good approach? Something like:

    async fn add_with_options<R>(
        &self,
        data: R,
        add: request::Add<'_>,
        mut form_option: Option<multipart::Form<'static>>,
    ) -> Result<response::AddResponse, Self::Error>
    where
        R: 'static + Read + Send + Sync,
    {
        if form_option.is_none() {
            let mut form_unwrapped = multipart::Form::default();
            form_unwrapped.add_reader("path", data);
            form_option = Some(form_unwrapped);
        }

        self.request(add, form_option).await
    }
SionoiS commented 3 years ago

Do you know of https://github.com/ferristseng/rust-ipfs-api/blob/799f702b22a9b567c0b6b7674210583b02b10d55/ipfs-api-prelude/src/request/add.rs#L26

iohzrd commented 3 years ago

yeah, I tried it using:

  let json = serde_json::to_string(&p).unwrap();
  let data = Cursor::new(json);
  let add = ipfs_api::request::Add::builder()
    .wrap_with_directory(true)
    .build();
  let result = match state.ipfs_client.add_with_options(data, add).await ...

but there seems to be two issues...

  1. when I use it, I get:
    Parse(
    Error("trailing characters", line: 2, column: 1),
    )
  2. I couldn't find a way to define the directory structure, in this case, the "path" for data.

Am I missing something?

SionoiS commented 3 years ago

About thew error I'm not sure.

But if your using json data why not use IPLD via ipfs dag put?

iohzrd commented 3 years ago

I've just never tried it, my current usecase is based on uploading json. Not sure IPLD would fit the usecase, maybe it would. But regardless if it fits the usecase better or not, I would think we would want to support the functionality.

SionoiS commented 3 years ago

I understand.

ipfs add in this case is made for file not really json constructed programatically.

If your can fix it that would be good.

iohzrd commented 3 years ago

To be fair, this functionality is useful for other binary data as well, not necessarily just json. We should be able to upload multiple files without manipulating the actual filesystem. Using something like this:

  let json = serde_json::to_string(&p).unwrap();
  let data = Cursor::new(json);
  let add = ipfs_api::request::Add::builder()
    .wrap_with_directory(true)
    .build();
  let mut form = Form::default();
  form.add_reader_file("path", data, "p.json");
  let cid = match state
    .ipfs_client
   .add_with_options(data, add, Some(form))
   .await ...

It should be possible using something like the code I posted originally, since that's basically how add_path already works. I'm new to rust though, so I've yet to make it work. currently with.

    #[inline]
    async fn add_with_options<R>(
        &self,
        data: R,
        add: request::Add<'_>,
        form: Option<multipart::Form<'static>>,
    ) -> Result<response::AddResponse, Error>
    where
        R: 'static + Read + Send + Sync,
    {
        let mut form_option = form;
        if form_option.is_none() {
            let mut form_unwrapped = multipart::Form::default();
            form_unwrapped.add_reader("path", data);
            form_option = Some(form_unwrapped);
        }

        self.request(add, form_option).await
    }

results in :

hidden type for `impl Trait` captures lifetime that does not appear in bounds rustc E0700
internal.rs(466, 10): hidden type `impl futures::Future` captures lifetime smaller than the function body

Looked it up, but not sure how to proceed.

iohzrd commented 3 years ago

Alright, I've got a working implementation here:

https://github.com/iohzrd/rust-ipfs-api/blob/e964814abc84ebc4a87bd2a2c4ffe53db71fe752/ipfs-api/src/client/internal.rs#L494

https://github.com/iohzrd/rust-ipfs-api/blob/e964814abc84ebc4a87bd2a2c4ffe53db71fe752/ipfs-api-prelude/src/api.rs#L124

thoughts?

SionoiS commented 3 years ago

Maybe @ferristseng could chime in. API design is not really my forte.

IMO it looks good.

ferristseng commented 3 years ago

Thanks! I think this looks fine. I'll take a closer look this weekend, but could you make a PR for it?

iohzrd commented 3 years ago

Here it is. https://github.com/ferristseng/rust-ipfs-api/pull/86 Thanks.