python-restx / flask-restx

Fork of Flask-RESTPlus: Fully featured framework for fast, easy and documented API development with Flask
https://flask-restx.readthedocs.io/en/latest/
Other
2.16k stars 335 forks source link

Validation not working for reqparse.RequestParser() when there are multiple items by action=append #449

Open hnakamitsu opened 2 years ago

hnakamitsu commented 2 years ago

Code

from flask import Flask
from flask_restx import Api, Resource, reqparse
from flask_restx.inputs import regex

app = Flask(__name__)
api = Api(app)

parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('ip_addr',
                    type=regex(r"^\d+\.\d+\.\d+\.\d+$"),
                    action="append")
@api.route('/')
class Test(Resource):
    @api.expect(parser, validate=True)
    def get(self):
        return {}

app.run(host="0.0.0.0", debug=True)

Repro Steps (if applicable)

This is a simple app to get multiple IP addresses from clients. As you can run/see, you can append multiple IP addresses.

  1. Put "1.1.1.1" to execute, it works fine
  2. Put "1.1.1.1" and "2.2.2.2", validation fails

Expected Behavior

I define type=regex(r"^\d+\.\d+\.\d+\.\d+$") and action="append". I think each text boxes should be treated as IP address.

Actual Behavior

Looks like regex validation happens against aggregated input such as 1.1.1.1\n2.2.2.2.

Error Messages/Stack Trace

JavaScript says Value must follow pattern ^\d+\.\d+\.\d+\.\d+$

Environment

Additional Context

I've read https://flask-restx.readthedocs.io/en/latest/parsing.html but didn't find any special information about append. Similar issues happen not only for regex but for int_range too.

hnakamitsu commented 2 years ago

This is swagger.json if it helps.

{
  "swagger": "2.0",
  "basePath": "/",
  "paths": {
    "/": {
      "get": {
        "responses": {
          "200": {
            "description": "Success"
          }
        },
        "operationId": "get_test",
        "parameters": [
          {
            "name": "ip_addr",
            "in": "query",
            "type": "array",
            "pattern": "^\\d+\\.\\d+\\.\\d+\\.\\d+$",
            "items": {
              "type": "string"
            },
            "collectionFormat": "multi"
          }
        ],
        "tags": [
          "default"
        ]
      }
    }
  },
  "info": {
    "title": "API",
    "version": "1.0"
  },
  "produces": [
    "application/json"
  ],
  "consumes": [
    "application/json"
  ],
  "tags": [
    {
      "name": "default",
      "description": "Default namespace"
    }
  ],
  "responses": {
    "ParseError": {
      "description": "When a mask can't be parsed"
    },
    "MaskError": {
      "description": "When any error occurs on mask"
    }
  }
}
peter-doggart commented 2 years ago

@hnakamitsu Not sure if you found a solution or not to this, but if not, you can define a custom function for your request parser that simply returns a ValueError if invalid. You also need to make a call to parse_args() in your endpoint for the validation to run. @api.expect(parser, validate=True) only validates the fields are correct, not the values inside them.

I think something like this is what you are trying to do?

from flask import Flask
from flask_restx import Api, Resource, reqparse
import re

app = Flask(__name__)
api = Api(app)

def safe_ip_addr(name):
    """Valide IP address is valid format"""
    if not re.compile(r"^\d+\.\d+\.\d+\.\d+$").match(name):
        raise ValueError(f"'{name}' is not a valid IP address.")
    return name

parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('ip_addr',
                    type=safe_ip_addr,
                    action="append")

@api.route('/regex')
class Test(Resource):
    @api.expect(parser, validate=True)
    def get(self):
        # Need to call parse to run the arg parser.
        x = parser.parse_args()
        return x, 200

app.run(host="0.0.0.0", debug=True)