How to prevent #param types from becoming broken links - jsdoc3

When generating docs, the type "string" (among others) will become a link to "string.html" which does not exist. How do we prevent that from happening? I want plain text to show for these types.
/**
* the comment
*
* #param {string} myString
*/
config:
"opts": {
"encoding": "utf8",
"recurse": true,
"destination": "out/clean/",
"template": "./node_modules/clean-jsdoc-theme/"
},
"tags": {
"allowUnknownTags": false,
"dictionaries": ["jsdoc","closure"]
},
"source": {
"includePattern": ".+\\.js?$",
"include": ["path/to/file"]
},
"plugins": ["plugins/markdown"],
"markdown": {
"hardwrap": false,
"idInHeadings": true
}

My issue was that I had a "#memberof string", which then jsdoc made a "string.html" file out of.

Related

Deeply nested unevaluatedProperties and their expectations

I have been working on my own validator for JSON schema and FINALLY have most of how unevaluatedProperties are supposed to work,... I think. That's one tricky piece there! However I really just want to confirm one thing. Given the following schema and JSON, what is the expected outcome... I have tried it with a https://www.jsonschemavalidator.net and gotten an answer, but I was hoping I could get a more definitive answer.
The focus is the faz property is in fact being evaluated, but the command to disallow unevaluatedProperties comes from a deeply nested schema.
Thoguhts?
Here is the schema...
{
"type": "object",
"properties": {
"foo": {
"type": "object",
"properties": {
"bar": {
"type": "string"
}
},
"unevaluatedProperties": false
}
},
"anyOf": [
{
"properties": {
"foo": {
"properties": {
"faz": {
"type": "string"
}
}
}
}
}
]
}
Here is the JSON...
{
"foo": {
"bar": "test",
"faz": "test"
}
}
That schema will successfully evaluate against the provided data. The unevaluatedProperties keyword will be aware of properties evaluated in subschemas of adjacent keywords, and is evaluated after all other applicator keywords, so it will see the annotation produced from within the anyOf subschema, also.
Evaluating this keyword is easy if you follow the specification literally -- it uses annotations to decide what to do. You just need to make sure that all keywords either produce annotations correctly or propagate annotations correctly that were produced by other keywords, and then all the information is available to generate the correct result.
The result produced by my implementation is:
{
"annotations" : [
{
"annotation" : [
"faz"
],
"instanceLocation" : "/foo",
"keywordLocation" : "/anyOf/0/properties/foo/properties"
},
{
"annotation" : [
"foo"
],
"instanceLocation" : "",
"keywordLocation" : "/anyOf/0/properties"
},
{
"annotation" : [
"bar"
],
"instanceLocation" : "/foo",
"keywordLocation" : "/properties/foo/properties"
},
{
"annotation" : [],
"instanceLocation" : "/foo",
"keywordLocation" : "/properties/foo/unevaluatedProperties"
},
{
"annotation" : [
"foo"
],
"instanceLocation" : "",
"keywordLocation" : "/properties"
}
],
"valid" : true
}
This is not an answer but a follow up example which I feel is in the same vein. I feel this guides us to the answer.
Here we have a single object being validated. But the unevaluated command resides in two different schemas each a part of a different "adjacent keyword subschemas"(from the core spec http://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.11)
How should this be resolved. If all annotations must be evaluated then in what order do I evaluate? The oneOf first or the anyOf? According the spec an unevaluated command(properties or items) generate annotation results which means that that result would affect any other unevaluated command.
http://json-schema.org/draft/2020-12/json-schema-core.html#unevaluatedProperties
"The annotation result of this keyword is the set of instance property names validated by this keyword's subschema."
This is as far as I am understanding the spec.
According to the two validators I am using this fails.
Schema
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": {
"foo": {
"type": "string"
}
},
"oneOf": [
{
"properties": {
"faz": {
"type": "string"
}
},
"unevaluatedProperties": true
}
],
"anyOf": [
{
"properties": {
"bar": {
"type": "string"
}
},
"unevaluatedProperties": false
}
]
}
Data
{
"bar": "test",
"faz": "test",
}

Validate using a specific definition with ajv

I have a JSON Schema file describing my API. It consists of some definitions as well as some vestigial parts from codegen that I'd like to ignore (the properties and required fields):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"CreateBook": {
"properties": {
"title": {"type": "string"},
"author": {"type": "string"},
"numPages": {"type": "number"}
},
"required": ["title", "author"]
},
"CreateShelf": {
"properties": {
"books": {"type": "array", "items": {"type": "string"}}
},
"required": ["books"]
}
},
"properties": {
"/api/create-book": {
"properties": {"type": {"post": {"$ref": "#/definitions/CreateBook"}}},
"required": ["post"],
"type": "object"
},
"/api/create-shelf": {
"properties": {"type": {"post": {"$ref": "#/definitions/CreateShelf"}}},
"required": ["post"],
"type": "object"
}
},
"required": ["/api/create-book", "/api/create-shelf"],
"type": "object"
}
I'd like to validate requests according to the definitions. I'd like to completely ignore the properties and required fields, which describe the shape of the API itself, not the individual requests.
Given what I expect to be a CreateBook request and this JSON schema, how should I validate it?
Here's what I tried:
const ajv = new Ajv();
const validate = ajv.compile(jsonSchema);
const body = {
author: 'Roald Dahl',
numPages: 234,
// missing title
};
if (!validate(body, '#/definitions/CreateBook')) {
console.log(validate.errors);
}
This logs:
[
{
keyword: 'required',
dataPath: '#/definitions/CreateBook',
schemaPath: '#/required',
params: { missingProperty: '/api/create-book' },
message: "should have required property '/api/create-book'"
}
]
So it's ignoring the dataPath parameter ('#/definitions/CreateBook'). What's the right way to do this? Do I need to create a new schema for every request type?
If you use addSchema instead of compile to compile the schema, you can specify a fragment.
const ajv = new Ajv();
ajv.addSchema(jsonSchema);
const validate = ajv.getSchema("#/definitions/CreateBook");
const body = {
author: 'Roald Dahl',
numPages: 234,
// missing title
};
if (!validate(body)) {
console.log(validate.errors);
}

Karate: Is it possible to compare two service responses & exclude some keys in comparison

I am trying to compare responses from two service call(for Migration project) using Karate.
Is it possible to exclude or ignore some keys while comparing whole response at one go for following cases:
1)There are few elements in response where the values is different(marked in bold).
2)There are few elements which is not present in one response but present in other(marked in bold).
Service 1 response -
{
"userKey": "string",
"enabled": true,
"locked": true,
"profile": {},
"roles": [
{
"roleKey": 3,
"name": "Role",
**"links": []**
}
],
"links": [
{
"rel": "self",
**"href": "https://starhub1.com"**
},
{
"rel": "self",
**"href": "https://singtel1.com"**
}
]
}
Service 2 response -
{
"userKey": "string",
"enabled": true,
"locked": true,
"profile": {},
"roles": [
{
"roleKey": 3,
"name": "Role"
}
],
"links": [
{
"rel": "self",
**"href": "https://starhub22.com"**
},
{
"rel": "self",
**"href": "https://singtel22.com"**
}`enter code here`
]
}
Yes Karate is pretty good at this. Just do a transform on one payload before comparison.
Please see this answer for details: https://stackoverflow.com/a/53120851/143475
For your specifc example maybe all you need to do is change one "side" to #string and then do the comparison.
* def response1 = { foo: 'hello' }
* def response2 = { foo: 'world' }
* response2.foo = '#string'
* match response1 == response2

How to correctly validate array of objects using JustinRainbow/JsonSchema

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}"));
The minimal composer.json is:
{
"require": {
"justinrainbow/json-schema": "^5.2"
}
}
Edit-2: 22nd May
I have been digging further turns out that the issue is because of your top level conversion to object
$jsonValidator->check($endpointData, (object)$schemaForPath);
You shouldn't have just done that and it would have all worked
$jsonValidator->check($endpointData, $schemaForPath);
So it doesn't seem to be a bug it was just a wrong usage. If you just remove (object) and run the code
$ php test.php
End points does not validate. Violations:
[[0].title] The property title is required
[[1].title] The property title is required
Data: [
{
"id": 19874
},
{
"id": 19873
}
]
End points does not validate. Violations:
[title] The property title is required
Data: {
"id": 19874
}
Edit-1
To fix the original code you would need to update the CollectionConstraints.php
/**
* Validates the items
*
* #param array $value
* #param \stdClass $schema
* #param JsonPointer|null $path
* #param string $i
*/
protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
if (is_array($schema->items) && array_key_exists('$ref', $schema->items)) {
$schema->items = $this->factory->getSchemaStorage()->resolveRefSchema((object)$schema->items);
var_dump($schema->items);
};
if (is_object($schema->items)) {
This will handle your use case for sure but if you don't prefer changing code from the dependency then use my original answer
Original Answer
The library has a bug/limitation that in src/JsonSchema/Constraints/CollectionConstraint.php they don't resolve a $ref variable as such. If I updated your code like below
// Alias the schema for the endpoint, so JsonSchema can work with it.
$schemaForPath = aliasSchema($id, $schemaForPath);
if (array_key_exists('items', $schemaForPath))
{
$schemaForPath['items'] = $factory->getSchemaStorage()->resolveRefSchema((object)$schemaForPath['items']);
}
// Validate the things
$jsonValidator->check($endpointData, (object)$schemaForPath);
and run it again, I get the exceptions needed
$ php test2.php
End points does not validate. Violations:
[[0].title] The property title is required
[[1].title] The property title is required
Data: [
{
"id": 19874
},
{
"id": 19873
}
]
End points does not validate. Violations:
[title] The property title is required
Data: {
"id": 19874
}
You either need to fix the CollectionConstraint.php or open an issue with developer of the repo. Or else manually replace your $ref in the whole schema, like had shown above. My code will resolve the issue specific to your schema, but fixing any other schema should not be a big issue
EDIT: Important thing here is that provided schema document is instance of Swagger Schema, which employs extended subset of JSON Schema to define some cases of request and response. Swagger 2.0 Schema itself can be validated by its JSON Schema, but it can not act as a JSON Schema for API Response structure directly.
In case entity schema is compatible with standard JSON Schema you can perform validation with general purpose validator, but you have to provide all relevant definitions, it can be easy when you have absolute references, but more complicated for local (relative) references that start with #/. IIRC they must be defined in the local schema.
The problem here is that you are trying to use schema references detached from resolution scope. I've added id to make references absolute, therefore not requiring being in scope.
"$ref": "http://example.com/my-schema#/definitions/Article"
The code below works well.
<?php
require_once __DIR__ . '/vendor/autoload.php';
$swaggerSchemaData = json_decode(<<<'JSON'
{
"id": "http://example.com/my-schema",
"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": "http://example.com/my-schema#/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": "http://example.com/my-schema#/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
);
$schemaStorage = new \JsonSchema\SchemaStorage();
$schemaStorage->addSchema('http://example.com/my-schema', $swaggerSchemaData);
$factory = new \JsonSchema\Constraints\Factory($schemaStorage);
$validator = new \JsonSchema\Validator($factory);
$schemaData = $swaggerSchemaData->paths->{"/articles"}->get->responses->{"200"}->schema;
$data = json_decode('[{"id":1},{"id":2,"title":"Title2"}]');
$validator->validate($data, $schemaData);
var_dump($validator->isValid()); // bool(false)
$data = json_decode('[{"id":1,"title":"Title1"},{"id":2,"title":"Title2"}]');
$validator->validate($data, $schemaData);
var_dump($validator->isValid()); // bool(true)
I'm not sure I fully understand your code here, but I have an idea based on some assumptions.
Assuming $typeForEndPointis the schema you're using for validation, your item key word needs to be an object rather than an array.
The items key word can be an array or an object. If it's an object, that schema is applicable to every item in the array. If it is an array, each item in that array is applicable to the item in the same position as the array being validated.
This means you're only validating the first item in the array.
If "items" is a schema, validation succeeds if all elements in the
array successfully validate against that schema.
If "items" is an array of schemas, validation succeeds if each element
of the instance validates against the schema at the same position, if
any.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.4.1
jsonValidator don't like mixed of object and array association,
You can use either:
$jsonValidator->check($endpointData, $schemaForPath);
or
$jsonValidator->check($endpointData, json_decode(json_encode($schemaForPath)));

How to get jsonschema to use a boolean of false OR attributes

I'm creating an Avatar in json schema, and in my app, if we want to hide the avatar, we pass it in as false, otherwise, an avatar has an image and a name.
In json schema, I specify avatar as
const avatarSchema = {
"type": ["object", "boolean"],
"required": [],
"additionalProperties": false,
"properties":{
"name": {
"type": "string"
},
"image": {
"type": "string",
"format": "url"
}
}
};
This doesn't work because if Avatar = true, 'image' does not exist on type 'Avatar'.
Property 'image' does not exist on type 'true'.
I don't want Avatar to every be true, it's either false or {image, name}, how can I tell json schema to operate this way?
Your explanation of why your schema doesn't work is not correct. The required, additionalProperties, and properties keywords only apply if the data being validated is an object. So, the schema should work as you want except that it can have value "true".
If the validator you are using is giving an error message like the one in your question, it is not validating correctly. You should file a bug report for that validator.
Anyway, the solution to your problem requires the anyOf keyword.
{
"anyOf": [
{ "enum": [false] },
{
"type": "object",
"required": ["name", "image"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"image": {
"type": "string",
"format": "url"
}
}
}
]
}