Repository: honojs/hono
File: src/validator/validator.ts (TypeScript)

Code before:
    if (this instanceof VArray) {
      this.keys.push(key, '[*]')
    } else if (this instanceof VObject) {
      this.keys.push(key)
    }
  }

  isOptional() {
    this._isOptional = true
    return this
  }

  getValidators = (): VBase[] => {
    const validators: VBase[] = []

    const walk = (container: T, keys: string[], isOptional: boolean) => {
      for (const v of Object.values(container)) {
        if (v instanceof VArray || v instanceof VObject) {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          isOptional ||= v._isOptional
          keys.push(...v.keys)
          walk(v.container as T, keys, isOptional)
          const tmp: string[] = []
          const lastObjectInArray = v instanceof VArray && keys[keys.length - 2] === '[*]'
          Object.assign(tmp, keys.slice(0, keys.length - (lastObjectInArray ? 3 : 1)))
          keys = tmp
        } else if (v instanceof VBase) {
          if (isOptional) v.isOptional()
          v.baseKeys.push(...keys)
          validators.push(v)
        }
      }
    }

    walk(this.container, this.keys, this._isOptional)

    return validators
  }
}

export class VObject<T extends Schema> extends VObjectBase<T> {
  constructor(container: T, key: string) {
    super(container, key)
  }
}

export class VArray<T extends Schema> extends VObjectBase<T> {
  type: 'array' = 'array'
  constructor(container: T, key: string) {
    super(container, key)

Review comment (security):
@yusukebe the issue here was that we were forgetting the keys after finishing the check of an object, for example in:
```ts
    const vObject = v.object('blog', v => ({
      posts: v.array('posts', v => ({
        author: v.object('author', v => ({
          name: v.json('name'),
        })),
        publisher: v.object('publisher', v => ({
          name: v.json('name'),
        }))
      })),
    }))
```
after having walked through the author, the keys would be reset to `['blog']` (instead of `['blog', 'posts']`) so I figured that we shouldn't always use the same `thisKeys` but always use the keys and just push and pop values as needed.

the removal is for 1 element if the last element was not the last in an array (for example in `blog.posts.[*].publisher.name` we want to remove `name`) otherwise it is 3 because we also need to remove the `[*]` and the name of the array (for example in `blog.posts.[*].publisher` we want to remove `publisher`, `[*]` and `posts`) 

What do you think? does this solution make sense to you? :thinking: 

Code after:
    if (this instanceof VArray) {
      this.keys.push(key, '[*]')
    } else if (this instanceof VObject) {
      this.keys.push(key)
    }
  }

  isOptional() {
    this._isOptional = true
    return this
  }

  getValidators = (): VBase[] => {
    const validators: VBase[] = []

    const walk = (container: T, keys: string[], isOptional: boolean) => {
      for (const v of Object.values(container)) {
        if (v instanceof VArray || v instanceof VObject) {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          isOptional ||= v._isOptional
          walk(v.container as T, [...keys, ...v.keys], isOptional)
        } else if (v instanceof VBase) {
          if (isOptional) v.isOptional()
          v.baseKeys.push(...keys)
          validators.push(v)
        }
      }
    }

    walk(this.container, this.keys, this._isOptional)

    return validators
  }
}

export class VObject<T extends Schema> extends VObjectBase<T> {
  constructor(container: T, key: string) {
    super(container, key)
  }
}

export class VArray<T extends Schema> extends VObjectBase<T> {
  type: 'array' = 'array'
  constructor(container: T, key: string) {
    super(container, key)
  }
}

export class Validator {
  constructor(private inArray: boolean = false) {}