Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Implementation bug in normaliseInput #3138

Closed
Gozala opened this issue Jul 3, 2020 · 9 comments · Fixed by #3184 or #4041
Closed

Implementation bug in normaliseInput #3138

Gozala opened this issue Jul 3, 2020 · 9 comments · Fixed by #3184 or #4041
Assignees
Labels
exp/novice Someone with a little familiarity can pick up status/ready Ready to be worked

Comments

@Gozala
Copy link
Contributor

Gozala commented Jul 3, 2020

I am pretty sure following lines would throw TypeError: iterator is not iterable because yield * expression expects iterable and not an iterator.

const iterator = input[Symbol.iterator]()
const first = iterator.next()
if (first.done) return iterator
// Iterable<Number>
// Iterable<Bytes>
if (Number.isInteger(first.value) || isBytes(first.value)) {
yield toFileObject((function * () {
yield first.value
yield * iterator

const iterator = input[Symbol.asyncIterator]()
const first = await iterator.next()
if (first.done) return iterator
// AsyncIterable<Bytes>
if (isBytes(first.value)) {
yield toFileObject((async function * () { // eslint-disable-line require-await
yield first.value
yield * iterator

const iterator = input[Symbol.iterator]()
const first = iterator.next()
if (first.done) return iterator
// Iterable<Number>
if (Number.isInteger(first.value)) {
yield toBuffer(Array.from((function * () {
yield first.value
yield * iterator

It is also worrying that no tests seem to catch that.

@Gozala Gozala added the need/triage Needs initial labeling and prioritization label Jul 3, 2020
@Gozala
Copy link
Contributor Author

Gozala commented Jul 3, 2020

Also it appears that parenthesis is in the wrong place here

throw errCode(new Error(`Unexpected input: ${input}`, 'ERR_UNEXPECTED_INPUT'))

@achingbrain
Copy link
Member

Please can you submit a PR with a failing test, and preferably, a fix.

@achingbrain achingbrain added exp/novice Someone with a little familiarity can pick up and removed need/triage Needs initial labeling and prioritization labels Jul 3, 2020
@Gozala
Copy link
Contributor Author

Gozala commented Jul 3, 2020

I'm working on the patch that adds tests and fixes.

@Gozala
Copy link
Contributor Author

Gozala commented Jul 21, 2020

Here is the example that illustrates the issue:

class IterableOf {
  constructor(...entries) {
    this.entries = entries
  }
  [Symbol.iterator]() {
    return new IteratorOf(...this.entries)
  }
}

class IteratorOf {
  constructor(...entries) {
    this.position = 0
    this.entries = entries
  }
  next() {
    if (this.position < this.entries.length) {
      return { done: false, value: this.entries[this.position++] }
    } else {
      return { done: true }
    }
  }
}

function * test () {
  const iterable = new IterableOf(1, 2, 3)
  const iterator = iterable[Symbol.iterator]()
  
  yield iterator.next().value
  yield * iterator
}

[...test()] // Uncaught TypeError: iterator is not iterable

I should note that browser built-ins (including generators) tend to return iterators that effectively have [Symbol.iterator]() { return this } and there for this doesn't appear like a problem. That said iterables are not required to return iterators are also iterables.

@achingbrain
Copy link
Member

I don't think you can use yield * like that. If I run your example I get:

  yield * iterator
  ^

TypeError: undefined is not a function

If I change it to a for..of I get:

  for (const thing of iterator) {
                      ^

TypeError: iterator is not iterable

Which is true, it's not iterable. Something is iterable if it has a [Symbol.iterator] function that returns an iterator which the value of iterator does not.

@achingbrain
Copy link
Member

achingbrain commented Jul 22, 2020

Another problem is the iterators can have state, you can't just yield * the iterable:

function * test () {
  const iterable = new IterableOf(1, 2, 3)
  const iterator = iterable[Symbol.iterator]()

  yield iterator.next().value
  yield * iterable
}

console.info([...test()])
// [ 1, 1, 2, 3 ]

If you drop down to the low-level interface you can avoid it, which I guess is the solution here:

function * test () {
  const iterable = new IterableOf(1, 2, 3)
  const iterator = iterable[Symbol.iterator]()

  yield iterator.next().value

  while (true) {
    const { value, done } = iterator.next()

    if (done) {
      break
    }

    yield value
  }
}

console.info([...test()])
// [ 1, 2, 3 ]

@Gozala
Copy link
Contributor Author

Gozala commented Jul 23, 2020

I don't think you can use yield * like that. If I run your example I get:

Yes that was what I was trying to illustrate. You see a different error because I'm guessing you ran it on V8 while I've run it on SpiderMonkey. They produce different error messages but result is the same.

@Gozala
Copy link
Contributor Author

Gozala commented Jul 23, 2020

Another problem is the iterators can have state, you can't just yield * the iterable:

That is why I introduced Peekables in #3151, so you could peek at first item, without consuming it & also use the whole thing as iterable.

/**
* @template T
*/
class Peekable {
/**
* @template T
* @template {Iterable<T>} I
* @param {I} iterable
* @returns {Peekable<T>}
*/
static from (iterable) {
return new Peekable(iterable)
}
/**
* @private
* @param {Iterable<T>} iterable
*/
constructor (iterable) {
const iterator = iterable[Symbol.iterator]()
/** @private */
this.first = iterator.next()
/** @private */
this.rest = iterator
}
peek () {
return this.first
}
next () {
const { first, rest } = this
this.first = rest.next()
return first
}
[Symbol.iterator] () {
return this
}
[Symbol.asyncIterator] () {
return this
}
}

achingbrain added a commit that referenced this issue Jul 23, 2020
To support streaming of native types with no buffering, normalise add input to blobs and upload using native FormData when the http client is run in the browser.

That is, if the user passes a blob to the http client in the browser leave it alone as enumerating blob contents cause the file data to be read.

Browser FormData objects do not allow you to specify headers for each multipart part which means we can't pass UnixFS metadata via the headers so we turn the metadata into a querystring and append it to the field name for each multipart part as a workaround.

Fixes #3138

BREAKING CHANGES:

- Removes the `mode`, `mtime` and `mtime-nsec` headers from multipart requests
- Passes `mode`, `mtime` and `mtime-nsec` as querystring parameters appended to the field name of multipart requests
@achingbrain
Copy link
Member

That is why I introduced Peekables in #3151

Yes, it was a good idea - I broke some similar code out into it-peekable

SgtPooki referenced this issue in ipfs/js-kubo-rpc-client Aug 18, 2022
To support streaming of native types with no buffering, normalise add input to blobs and upload using native FormData when the http client is run in the browser.

That is, if the user passes a blob to the http client in the browser leave it alone as enumerating blob contents cause the file data to be read.

Browser FormData objects do not allow you to specify headers for each multipart part which means we can't pass UnixFS metadata via the headers so we turn the metadata into a querystring and append it to the field name for each multipart part as a workaround.

Fixes #3138

BREAKING CHANGES:

- Removes the `mode`, `mtime` and `mtime-nsec` headers from multipart requests
- Passes `mode`, `mtime` and `mtime-nsec` as querystring parameters appended to the field name of multipart requests
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
exp/novice Someone with a little familiarity can pick up status/ready Ready to be worked
Projects
None yet
3 participants