-
-
Notifications
You must be signed in to change notification settings - Fork 933
fix(jsonschema/jsonld): make @id
and @type
properties required only in the JSON-LD schema for output
#7397
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
base: 4.2
Are you sure you want to change the base?
Conversation
6ea2a72
to
ff5e438
Compare
Interesting, but its possible when using JSON-LD to have |
I see. If so, when using JSON-LD, Would it be ok to modify this PR like that? |
834d183
to
638ad92
Compare
like this. (Just a PoC, don't merge yet.) |
Nice this looks fine! Can the |
@id
and @type
properties required only in the JSON-LD schema for output
@soyuka I fixed in 479f46b and updated the PR title and description. Example of output result![]() ![]() openapi.json{
"openapi": "3.1.0",
"info": {
"title": "Hello API Platform",
"description": "",
"version": "1.0.0"
},
"servers": [
{
"url": "/",
"description": ""
}
],
"paths": {
"/api/books": {
"get": {
"operationId": "api_books_get_collection",
"tags": [
"Book"
],
"responses": {
"200": {
"description": "Book collection",
"content": {
"application/ld+json": {
"schema": {
"type": "object",
"description": "Book.jsonld collection.",
"allOf": [
{
"$ref": "#/components/schemas/HydraCollectionBaseSchema"
},
{
"type": "object",
"properties": {
"hydra:member": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Book.jsonld"
}
}
}
}
]
}
}
}
}
},
"summary": "Retrieves the collection of Book resources.",
"description": "Retrieves the collection of Book resources.",
"parameters": [
{
"name": "page",
"in": "query",
"description": "The collection page number",
"required": false,
"deprecated": false,
"schema": {
"type": "integer",
"default": 1
},
"style": "form",
"explode": false
}
]
},
"post": {
"operationId": "api_books_post",
"tags": [
"Book"
],
"responses": {
"201": {
"description": "Book resource created",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Book.jsonld"
}
}
},
"links": {}
},
"400": {
"description": "Invalid input",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Error.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"links": {}
},
"422": {
"description": "An error occurred",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/ConstraintViolation.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ConstraintViolation"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConstraintViolation"
}
}
},
"links": {}
}
},
"summary": "Creates a Book resource.",
"description": "Creates a Book resource.",
"parameters": [],
"requestBody": {
"description": "The new Book resource",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Book.jsonld.input"
}
}
},
"required": true
}
}
},
"/api/books/{id}": {
"get": {
"operationId": "api_books_id_get",
"tags": [
"Book"
],
"responses": {
"200": {
"description": "Book resource",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Book.jsonld"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Error.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"links": {}
}
},
"summary": "Retrieves a Book resource.",
"description": "Retrieves a Book resource.",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Book identifier",
"required": true,
"deprecated": false,
"schema": {
"type": "string"
},
"style": "simple",
"explode": false
}
]
},
"delete": {
"operationId": "api_books_id_delete",
"tags": [
"Book"
],
"responses": {
"204": {
"description": "Book resource deleted"
},
"404": {
"description": "Not found",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Error.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"links": {}
}
},
"summary": "Removes the Book resource.",
"description": "Removes the Book resource.",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Book identifier",
"required": true,
"deprecated": false,
"schema": {
"type": "string"
},
"style": "simple",
"explode": false
}
]
},
"patch": {
"operationId": "api_books_id_patch",
"tags": [
"Book"
],
"responses": {
"200": {
"description": "Book resource updated",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Book.jsonld"
}
}
},
"links": {}
},
"400": {
"description": "Invalid input",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Error.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"links": {}
},
"422": {
"description": "An error occurred",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/ConstraintViolation.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ConstraintViolation"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConstraintViolation"
}
}
},
"links": {}
},
"404": {
"description": "Not found",
"content": {
"application/ld+json": {
"schema": {
"$ref": "#/components/schemas/Error.jsonld"
}
},
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"links": {}
}
},
"summary": "Updates the Book resource.",
"description": "Updates the Book resource.",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Book identifier",
"required": true,
"deprecated": false,
"schema": {
"type": "string"
},
"style": "simple",
"explode": false
}
],
"requestBody": {
"description": "The updated Book resource",
"content": {
"application/merge-patch+json": {
"schema": {
"$ref": "#/components/schemas/Book"
}
}
},
"required": true
}
}
}
},
"components": {
"schemas": {
"Book": {
"type": "object",
"properties": {
"id": {
"readOnly": true,
"type": "integer"
},
"title": {
"type": "string"
}
},
"required": [
"title"
]
},
"Book.jsonld": {
"allOf": [
{
"$ref": "#/components/schemas/HydraOutputBaseSchema"
},
{
"$ref": "#/components/schemas/Book"
}
]
},
"Book.jsonld.input": {
"allOf": [
{
"$ref": "#/components/schemas/HydraItemBaseSchema"
},
{
"$ref": "#/components/schemas/Book"
}
]
},
"ConstraintViolation": {
"type": "object",
"description": "Unprocessable entity",
"properties": {
"status": {
"default": 422,
"type": "integer"
},
"violations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"propertyPath": {
"type": "string",
"description": "The property path of the violation"
},
"message": {
"type": "string",
"description": "The message associated with the violation"
}
}
}
},
"detail": {
"readOnly": true,
"type": "string"
},
"type": {
"readOnly": true,
"type": "string"
},
"title": {
"readOnly": true,
"type": [
"string",
"null"
]
},
"instance": {
"readOnly": true,
"type": [
"string",
"null"
]
}
}
},
"ConstraintViolation.jsonld": {
"allOf": [
{
"$ref": "#/components/schemas/HydraOutputBaseSchema"
},
{
"$ref": "#/components/schemas/ConstraintViolation"
}
]
},
"Error": {
"type": "object",
"description": "A representation of common errors.",
"properties": {
"title": {
"readOnly": true,
"description": "A short, human-readable summary of the problem.",
"type": "string"
},
"detail": {
"readOnly": true,
"description": "A human-readable explanation specific to this occurrence of the problem.",
"type": "string"
},
"status": {
"type": "number",
"examples": [
404
],
"default": 400
},
"instance": {
"readOnly": true,
"description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.",
"type": [
"string",
"null"
]
},
"type": {
"readOnly": true,
"description": "A URI reference that identifies the problem type",
"type": "string"
}
}
},
"Error.jsonld": {
"allOf": [
{
"$ref": "#/components/schemas/HydraOutputBaseSchema"
},
{
"$ref": "#/components/schemas/Error"
}
]
},
"HydraCollectionBaseSchema": {
"type": "object",
"required": [
"hydra:member"
],
"properties": {
"hydra:member": {
"type": "array"
},
"hydra:totalItems": {
"type": "integer",
"minimum": 0
},
"hydra:view": {
"type": "object",
"properties": {
"@id": {
"type": "string",
"format": "iri-reference"
},
"@type": {
"type": "string"
},
"hydra:first": {
"type": "string",
"format": "iri-reference"
},
"hydra:last": {
"type": "string",
"format": "iri-reference"
},
"hydra:previous": {
"type": "string",
"format": "iri-reference"
},
"hydra:next": {
"type": "string",
"format": "iri-reference"
}
},
"example": {
"@id": "string",
"type": "string",
"hydra:first": "string",
"hydra:last": "string",
"hydra:previous": "string",
"hydra:next": "string"
}
},
"hydra:search": {
"type": "object",
"properties": {
"@type": {
"type": "string"
},
"hydra:template": {
"type": "string"
},
"hydra:variableRepresentation": {
"type": "string"
},
"hydra:mapping": {
"type": "array",
"items": {
"type": "object",
"properties": {
"@type": {
"type": "string"
},
"variable": {
"type": "string"
},
"property": {
"type": [
"string",
"null"
]
},
"required": {
"type": "boolean"
}
}
}
}
}
}
}
},
"HydraItemBaseSchema": {
"type": "object",
"properties": {
"@context": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"@vocab": {
"type": "string"
},
"hydra": {
"type": "string",
"enum": [
"http://www.w3.org/ns/hydra/core#"
]
}
},
"required": [
"@vocab",
"hydra"
],
"additionalProperties": true
}
]
},
"@id": {
"type": "string"
},
"@type": {
"type": "string"
}
}
},
"HydraOutputBaseSchema": {
"required": [
"@id",
"@type"
],
"type": "object",
"properties": {
"@context": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"@vocab": {
"type": "string"
},
"hydra": {
"type": "string",
"enum": [
"http://www.w3.org/ns/hydra/core#"
]
}
},
"required": [
"@vocab",
"hydra"
],
"additionalProperties": true
}
]
},
"@id": {
"type": "string"
},
"@type": {
"type": "string"
}
}
}
},
"responses": {},
"parameters": {},
"examples": {},
"requestBodies": {},
"headers": {},
"securitySchemes": {}
},
"security": [],
"tags": [
{
"name": "Book",
"description": "Resource 'Book' operations."
}
],
"webhooks": {}
} Reproducer |
I'm still working to fix the CI error. |
1cda6cf
to
5fdaa77
Compare
I can take a look if you want, and I may move this test to phpunit I thought that keeping |
cb98a29
to
b0cac3a
Compare
$definitionName .= '.input'; | ||
} | ||
|
||
$jsonSchema = $this->schemaFactory->buildSchema($className, 'json', $type, $operation, new Schema(version: $schema->getVersion()), $serializerContext, $forceCollection); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't work as references will be with json
instead of jsonld
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@soyuka What do you mean?🤔 I understand that the intention of your fix in #6960 was to reference a json schema in the definition of a jsonld schema. I completely agree with that intention, but unfortunately #6960 had the following bugs:
- The output schema was applied even on input, making
@id
and@type
required. - The json schema was not referenced (only
HydraItemBaseSchema
andHydraCollectionBaseSchema
were referenced).
This PR fixes these two bugs.
In order to reference or embed a json schema (e.g., Book
instead of Book.jsonld
), I believe you need to explicitly create a json schema here.
b0cac3a
to
5fdaa77
Compare
public function buildSchema(string $className, string $format = 'jsonld', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema | ||
{ | ||
if ('jsonld' !== $format || 'input' === $type) { | ||
if ('jsonld' !== $format) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm quite unsure about this as when its an input we were going through this path making @id
required only on output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@soyuka If we add 'input' === $type
here, this method will not be executed at all during input, and Book.jsonld.input
will not be generated.
The @id
@type
@context
properties will be added during both input and output, and @id
@type
should only be required during output. This control is done here and here.
53e1e96
to
fa1305f
Compare
…ferencing it It cannot always be referenced, as there may be resources that don't have a JSON schema but only a JSON-LD schema. It's not certain at the time the JSON-LD schema is being generated whether a JSON schema will ultimately be defined. Therefore, the JSON schema will be referenced only if it's already defined.
fa1305f
to
ad9be8a
Compare
rebased with current 4.2 |
e92aeff
to
d1e60d9
Compare
@soyuka I've mostly revised the existing tests to match the implementation. The only thing I'm unsure of is how to properly modify The new schema uses |
This PR makes
@id
and@type
properties required only in the JSON-LD schema for output.