Skip to content
This repository has been archived by the owner on Mar 5, 2018. It is now read-only.

maxLength renders wrong message #6

Open
whitlockjc opened this issue Oct 20, 2014 · 5 comments
Open

maxLength renders wrong message #6

whitlockjc opened this issue Oct 20, 2014 · 5 comments

Comments

@whitlockjc
Copy link

I'm validating a Swagger 1.2 operation. The schema for the operation object specifies that summary has a maxLength of 120 (operationObject.json#L11) and whenever that constraint is violated, the error message has undefined for the maxLength value and it ends up with a message like: String is too long (215 chars), maximum undefined.

@silas
Copy link
Owner

silas commented Oct 22, 2014

Yeah, jjve definitely doesn't support allOf, anyOf, and any of the other more advanced operators.

I was hoping to get some movement on acornejo/jjv#25 so I didn't end up rewriting a bunch of jsonschema logic.

If you just want nice error messages it might be easier to just switch to something like z-schema.

I really need to be able to hook into jjv to do this cleanly, otherwise I'm just recreating a jsonschema validator (which I'm not really interested in doing).

@acornejo
Copy link

@whitlockjc Could you provide a working example of your problem? It would be awesome if you set it up in jsfiddle, otherwise I'll settle for links to the offending object and schema (you posted a link to the schema but not the object).

If your object doesn't pass because of a maxLength violation, I would expect jjv to tell you that the error was with 'maxLength'. It won't tell you what the expected length was, but that information is already available in the schema, so you are just a lookup away from what you want.

However @silas is correct in that the error messages produced for allOf or anyOf might not be very useful. For instance, suppose your object failed on the allOf tests, it means we tried to match your object to a bunch of schemas, and it failed in at least one of them, but possibly many of them. In the later case, which one should we report as an error? The one with the most errors (how do we count them?), the one with the least errors? All of them?

@whitlockjc
Copy link
Author

Below is the full code required to reproduce this. It was identified while working on swagger-tools and to make it as simple as possible to run, I've taken the real world schemas and inlined them in the code so you didn't have to do it yourself. Before I give you the code, let me show you the expected output and actual output.

Expected Output

[ { code: 'VALIDATION_MAX_LENGTH',
    message: 'String is too long (121 chars), maximum 120',
    data: '*************************************************************************************************************************',
    path: '$.apis[0].operations[0].summary' } ]

Actual Output

[ { code: 'VALIDATION_MAX_LENGTH',
    message: 'String is too long (121 chars), maximum undefined',
    data: '*************************************************************************************************************************',
    path: '$.apis[0].operations[0].summary' } ]

And here is the code:

'use strict';

var jjv = require('jjv');
var jjve = require('jjve');
var validator = jjv();
var dataTypeBaseJson = {
  "id": "dataTypeBase.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "Data type fields (section 4.3.3)",
  "type": "object",
  "oneOf": [
    {
      "required": [
        "type"
      ]
    },
    {
      "required": [
        "$ref"
      ]
    }
  ],
  "properties": {
    "type": {
      "type": "string"
    },
    "$ref": {
      "type": "string"
    },
    "format": {
      "type": "string"
    },
    "defaultValue": {
      "not": {
        "type": [
          "array",
          "object",
          "null"
        ]
      }
    },
    "enum": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "uniqueItems": true,
      "minItems": 1
    },
    "minimum": {
      "type": "string"
    },
    "maximum": {
      "type": "string"
    },
    "items": {
      "$ref": "#/definitions/itemsObject"
    },
    "uniqueItems": {
      "type": "boolean"
    }
  },
  "dependencies": {
    "format": {
      "oneOf": [
        {
          "properties": {
            "type": {
              "enum": [
                "integer"
              ]
            },
            "format": {
              "enum": [
                "int32",
                "int64"
              ]
            }
          }
        },
        {
          "properties": {
            "type": {
              "enum": [
                "number"
              ]
            },
            "format": {
              "enum": [
                "float",
                "double"
              ]
            }
          }
        },
        {
          "properties": {
            "type": {
              "enum": [
                "string"
              ]
            },
            "format": {
              "enum": [
                "byte",
                "date",
                "date-time"
              ]
            }
          }
        }
      ]
    }
  },
  "definitions": {
    "itemsObject": {
      "oneOf": [
        {
          "type": "object",
          "required": [
            "$ref"
          ],
          "properties": {
            "$ref": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        {
          "allOf": [
            {
              "$ref": "#"
            },
            {
              "required": [
                "type"
              ],
              "properties": {
                "type": {},
                "format": {}
              },
              "additionalProperties": false
            }
          ]
        }
      ]
    }
  }
};
var modelsObjectJson = {
  "id": "modelsObject.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "required": [
    "id",
    "properties"
  ],
  "properties": {
    "id": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "properties": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/definitions/propertyObject"
      }
    },
    "subTypes": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "uniqueItems": true
    },
    "discriminator": {
      "type": "string"
    }
  },
  "dependencies": {
    "subTypes": [
      "discriminator"
    ]
  },
  "definitions": {
    "propertyObject": {
      "allOf": [
        {
          "not": {
            "$ref": "#"
          }
        },
        {
          "$ref": "dataTypeBase.json#"
        }
      ]
    }
  }
};
var oauth2GrantTypeJson = {
  "id": "oauth2GrantType.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "minProperties": 1,
  "properties": {
    "implicit": {
      "$ref": "#/definitions/implicit"
    },
    "authorization_code": {
      "$ref": "#/definitions/authorizationCode"
    }
  },
  "definitions": {
    "implicit": {
      "type": "object",
      "required": [
        "loginEndpoint"
      ],
      "properties": {
        "loginEndpoint": {
          "$ref": "#/definitions/loginEndpoint"
        },
        "tokenName": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "authorizationCode": {
      "type": "object",
      "required": [
        "tokenEndpoint",
        "tokenRequestEndpoint"
      ],
      "properties": {
        "tokenEndpoint": {
          "$ref": "#/definitions/tokenEndpoint"
        },
        "tokenRequestEndpoint": {
          "$ref": "#/definitions/tokenRequestEndpoint"
        }
      },
      "additionalProperties": false
    },
    "loginEndpoint": {
      "type": "object",
      "required": [
        "url"
      ],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri"
        }
      },
      "additionalProperties": false
    },
    "tokenEndpoint": {
      "type": "object",
      "required": [
        "url"
      ],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri"
        },
        "tokenName": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "tokenRequestEndpoint": {
      "type": "object",
      "required": [
        "url"
      ],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri"
        },
        "clientIdName": {
          "type": "string"
        },
        "clientSecretName": {
          "type": "string"
        }
      },
      "additionalProperties": false
    }
  }
};
var authorizationObjectJson = {
  "id": "authorizationObject.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "additionalProperties": {
    "oneOf": [
      {
        "$ref": "#/definitions/basicAuth"
      },
      {
        "$ref": "#/definitions/apiKey"
      },
      {
        "$ref": "#/definitions/oauth2"
      }
    ]
  },
  "definitions": {
    "basicAuth": {
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "enum": [
            "basicAuth"
          ]
        }
      },
      "additionalProperties": false
    },
    "apiKey": {
      "required": [
        "type",
        "passAs",
        "keyname"
      ],
      "properties": {
        "type": {
          "enum": [
            "apiKey"
          ]
        },
        "passAs": {
          "enum": [
            "header",
            "query"
          ]
        },
        "keyname": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "oauth2": {
      "type": "object",
      "required": [
        "type",
        "grantTypes"
      ],
      "properties": {
        "type": {
          "enum": [
            "oauth2"
          ]
        },
        "scopes": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/oauth2Scope"
          }
        },
        "grantTypes": {
          "$ref": "oauth2GrantType.json#"
        }
      },
      "additionalProperties": false
    },
    "oauth2Scope": {
      "type": "object",
      "required": [
        "scope"
      ],
      "properties": {
        "scope": {
          "type": "string"
        },
        "description": {
          "type": "string"
        }
      },
      "additionalProperties": false
    }
  }
};
var parameterObjectJson = {
  "id": "parameterObject.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "allOf": [
    {
      "$ref": "dataTypeBase.json#"
    },
    {
      "required": [
        "paramType",
        "name"
      ],
      "properties": {
        "paramType": {
          "enum": [
            "path",
            "query",
            "body",
            "header",
            "form"
          ]
        },
        "name": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "required": {
          "type": "boolean"
        },
        "allowMultiple": {
          "type": "boolean"
        }
      }
    },
    {
      "description": "type File requires special paramType and consumes",
      "oneOf": [
        {
          "properties": {
            "type": {
              "not": {
                "enum": [
                  "File"
                ]
              }
            }
          }
        },
        {
          "properties": {
            "type": {
              "enum": [
                "File"
              ]
            },
            "paramType": {
              "enum": [
                "form"
              ]
            },
            "consumes": {
              "enum": [
                "multipart/form-data"
              ]
            }
          }
        }
      ]
    }
  ]
};
var operationObjectJson = {
  "id": "operationObject.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "allOf": [
    {
      "$ref": "dataTypeBase.json#"
    },
    {
      "required": [
        "method",
        "nickname",
        "parameters"
      ],
      "properties": {
        "method": {
          "enum": [
            "GET",
            "POST",
            "PUT",
            "PATCH",
            "DELETE",
            "OPTIONS"
          ]
        },
        "summary": {
          "type": "string",
          "maxLength": 120
        },
        "notes": {
          "type": "string"
        },
        "nickname": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_]+$"
        },
        "authorizations": {
          "type": "object",
          "additionalProperties": {
            "type": "array",
            "items": {
              "$ref": "authorizationObject.json#/definitions/oauth2Scope"
            }
          }
        },
        "parameters": {
          "type": "array",
          "items": {
            "$ref": "parameterObject.json#"
          }
        },
        "responseMessages": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/responseMessageObject"
          }
        },
        "produces": {
          "$ref": "#/definitions/mimeTypeArray"
        },
        "consumes": {
          "$ref": "#/definitions/mimeTypeArray"
        },
        "deprecated": {
          "enum": [
            "true",
            "false"
          ]
        }
      }
    }
  ],
  "definitions": {
    "responseMessageObject": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "$ref": "#/definitions/rfc2616section10"
        },
        "message": {
          "type": "string"
        },
        "responseModel": {
          "type": "string"
        }
      }
    },
    "rfc2616section10": {
      "type": "integer",
      "minimum": 100,
      "maximum": 600,
      "exclusiveMaximum": true
    },
    "mimeTypeArray": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "mime-type"
      }
    }
  }
}
var apiDeclarationJson = {
  "id": "apiDeclaration.json",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "required": [
    "swaggerVersion",
    "basePath",
    "apis"
  ],
  "properties": {
    "swaggerVersion": {
      "enum": [
        "1.2"
      ]
    },
    "apiVersion": {
      "type": "string"
    },
    "basePath": {
      "type": "string",
      "format": "uri",
      "pattern": "^https?://"
    },
    "resourcePath": {
      "type": "string",
      "format": "uri",
      "pattern": "^/"
    },
    "apis": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/apiObject"
      }
    },
    "models": {
      "type": "object",
      "additionalProperties": {
        "$ref": "modelsObject.json#"
      }
    },
    "produces": {
      "$ref": "#/definitions/mimeTypeArray"
    },
    "consumes": {
      "$ref": "#/definitions/mimeTypeArray"
    },
    "authorizations": {
      "$ref": "authorizationObject.json#"
    }
  },
  "additionalProperties": false,
  "definitions": {
    "apiObject": {
      "type": "object",
      "required": [
        "path",
        "operations"
      ],
      "properties": {
        "path": {
          "type": "string",
          "format": "uri-template",
          "pattern": "^/"
        },
        "description": {
          "type": "string"
        },
        "operations": {
          "type": "array",
          "items": {
            "$ref": "operationObject.json#"
          }
        }
      },
      "additionalProperties": false
    },
    "mimeTypeArray": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "mime-type"
      }
    }
  }
};
var data = {
  "apiVersion": "1.0.0",
  "apis": [
    {
      "operations": [
        {
          "authorizations": {},
          "method": "GET",
          "nickname": "getAllPets",
          "summary": "Find all Pets",
          "parameters": [],
          "type": "array",
          "items": {
            "$ref": "Pet"
          }
        }
      ],
      "path": "/pets"
    }
  ],
  "basePath": "http://localhost/api",
  "resourcePath": "/pets",
  "swaggerVersion": "1.2"
}
var result;

validator.addSchema('dataTypeBaseJson.json', dataTypeBaseJson);
validator.addSchema('modelsObjectJson.json', modelsObjectJson);
validator.addSchema('oauth2GrantTypeJson.json', oauth2GrantTypeJson);
validator.addSchema('authorizationObjectJson.json', authorizationObjectJson);
validator.addSchema('parameterObjectJson.json', parameterObjectJson);
validator.addSchema('operationObjectJson.json', operationObjectJson);
validator.addSchema('apiDeclarationJson.json', apiDeclarationJson);

validator.addFormat('uri', function() {
  return true;
});

data.apis[0].operations[0].summary = new Array(122).join('*');

result = validator.validate(apiDeclarationJson, data);

console.log(jjve(validator)(apiDeclarationJson, data, result));

@silas
Copy link
Owner

silas commented Oct 23, 2014

Just to be clear this definitely isn't a problem with jjv, this is purely are result of how jjve is implemented.

I basically ended up re-creating a bit of jjv inside jjve in a hacky way in order to get more user friendly errors messages. In at attempt to stop re-impelementing a bunch of jjv I created that other pull request which should give me direct access to data as it's validated and get rid of weirdness like the above.

Sorry for the confusion, I didn't mean to suggest jjv was broken, I just mean't I want to fix this and other issues using the result of the originally linked pull request.

@whitlockjc
Copy link
Author

If it matters, I didn't take it that way.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants