Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array of items doesn't expand, and so validation check is wrong. #514

Closed
Danack opened this issue May 21, 2018 · 3 comments
Closed

Array of items doesn't expand, and so validation check is wrong. #514

Danack opened this issue May 21, 2018 · 3 comments

Comments

@Danack
Copy link

Danack commented May 21, 2018

TL:DR version

There is a missing expansion of a reference when the type is an array of items. PR incoming.


Original version

https://stackoverflow.com/questions/50390920/how-to-correctly-validate-array-of-objects-using-justinrainbow-jsonschema/50407155#50407155

I have code that correctly validates an article returned from an endpoint that returns single articles. I'm pretty sure it's working correctly as it gives a validation error when I deliberately don't include a required field in the article.

I also have this code that tries to validate an array of articles returned from an endpoint that returns an array of articles. However, I'm pretty sure that isn't working correctly, as it always says the data is valid, even when I deliberately don't include a required field in the articles.

How do I correctly validate an array of data against the schema?

The full test code is below as a standalone runnable test. Both of the tests should fail, however only one of them does.

<?php

declare(strict_types=1);

error_reporting(E_ALL);

require_once __DIR__ . '/vendor/autoload.php';


// Return the definition of the schema, either as an array
// or a PHP object
function getSchema($asArray = false)
{
    $schemaJson = <<< 'JSON'
{
  "swagger": "2.0",
  "info": {
    "termsOfService": "http://swagger.io/terms/",
    "version": "1.0.0",
    "title": "Example api"
  },
  "paths": {
    "/articles": {
      "get": {
        "tags": [
          "article"
        ],
        "summary": "Find all articles",
        "description": "Returns a list of articles",
        "operationId": "getArticleById",
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "successful operation",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/Article"
              }
            }
          }
        },
        "parameters": [
        ]
      }
    },
    "/articles/{articleId}": {
      "get": {
        "tags": [
          "article"
        ],
        "summary": "Find article by ID",
        "description": "Returns a single article",
        "operationId": "getArticleById",
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "articleId",
            "in": "path",
            "description": "ID of article to return",
            "required": true,
            "type": "integer",
            "format": "int64"
          }
        ],
        "responses": {
          "200": {
            "description": "successful operation",
            "schema": {
              "$ref": "#/definitions/Article"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "Article": {
      "type": "object",
      "required": [
        "id",
        "title"
      ],
      "properties": {
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "title": {
          "type": "string",
          "description": "The title for the link of the article"
        }
      }
    }
  },
  "schemes": [
    "http"
  ],
  "host": "example.com",
  "basePath": "/",
  "tags": [],
  "securityDefinitions": {
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ]
}
JSON;

    return json_decode($schemaJson, $asArray);
}

// Extract the schema of the 200 response of an api endpoint.
function getSchemaForPath($path)
{
    $swaggerData = getSchema(true);
    if (isset($swaggerData["paths"][$path]['get']["responses"][200]['schema']) !== true) {
        echo "response not defined";
        exit(-1);
    }

    return $swaggerData["paths"][$path]['get']["responses"][200]['schema'];
}

// JsonSchema needs to know about the ID used for the top-level
// schema apparently.
function aliasSchema($prefix, $schemaForPath)
{
    $aliasedSchema = [];

    foreach ($schemaForPath as $key => $value) {
        if ($key === '$ref') {
            $aliasedSchema[$key] = $prefix . $value;
        }
        else if (is_array($value) === true) {
            $aliasedSchema[$key] = aliasSchema($prefix, $value);
        }
        else {
            $aliasedSchema[$key] = $value;
        }
    }
    return $aliasedSchema;
}


// Test the data matches the schema.
function testDataMatches($endpointData, $schemaForPath)
{
    // Setup the top level schema and get a validator from it.
    $schemaStorage = new \JsonSchema\SchemaStorage();
    $id = 'file://example';
    $swaggerClass = getSchema(false);
    $schemaStorage->addSchema($id, $swaggerClass);
    $factory = new \JsonSchema\Constraints\Factory($schemaStorage);
    $jsonValidator = new \JsonSchema\Validator($factory);

    // Alias the schema for the endpoint, so JsonSchema can work with it.
    $schemaForPath = aliasSchema($id, $schemaForPath);

    // Validate the things
    $jsonValidator->check($endpointData, (object)$schemaForPath);

    // Process the result
    if ($jsonValidator->isValid()) {
        echo "The supplied JSON validates against the schema definition: " . \json_encode($schemaForPath) . " \n";
        return;
    }

    $messages = [];
    $messages[] = "End points does not validate. Violations:\n";
    foreach ($jsonValidator->getErrors() as $error) {
        $messages[] = sprintf("[%s] %s\n", $error['property'], $error['message']);
    }

    $messages[] = "Data: " . \json_encode($endpointData, JSON_PRETTY_PRINT);

    echo implode("\n", $messages);
    echo "\n";
}



// We have two data sets to test. A list of articles.

$articleListJson = <<< JSON
[
  {
      "id": 19874
  },
  {
      "id": 19873
  }
]
JSON;
$articleListData = json_decode($articleListJson);


// A single article
$articleJson = <<< JSON
{
  "id": 19874
}
JSON;
$articleData = json_decode($articleJson);


// This passes, when it shouldn't as none of the articles have a title
testDataMatches($articleListData, getSchemaForPath("/articles"));


// This fails correctly, as it is correct for it to fail to validate, as the article doesn't have a title
testDataMatches($articleData, getSchemaForPath("/articles/{articleId}"));
@erayd
Copy link
Contributor

erayd commented May 21, 2018

Could you please provide a minimal (a few lines, using pure JSON Schema, and none of the swagger stuff) example which demonstrates the problem? What you have above is huge, and most of the bulk relates to it being a swagger schema.

@DannyvdSluijs
Copy link
Collaborator

@Danack in an attempt to cleanup this repo we are trying to filter the issues and see which ones might be closed. Is it safe to assume this can be closed as well? Based on eyeballing PR#515 it seems there was a small misinterpretation between the chair and keyboard

@Danack
Copy link
Author

Danack commented Feb 5, 2024

Very much looks like it.

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

No branches or pull requests

3 participants