question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Vary presence and requirement of properties with CRUD operation

See original GitHub issue

Properties of a schema could be marked as readOnly or writeOnly if they should not be used respectively in a request or response. Additionally, required properties are present in the required field. Request vs response is the only criteria that can alter a resource schema.

This was not sufficient to describe our legacy REST APIs where presence and requirement of resource’s properties is highly dependent of CRUD operation. The was due, in part, to wrong design choices. However, we faced generic situations that cannot be expressed for example:

  • Write once / immutable property: for properties like slug or identifier. Those properties should not be part of update request.
  • Server side default value: this kind of property is required only in response.

All this can be achieved using composition. I don’t think that’s a reasonable solution because:

  • contract is not more human readable as resources are split in many schema
  • for polymorphic resources (using oneOf), there is too many different schema to create and duplication is inevitable

We used following vendor extensions to be able to produce the documentation of our API:

Field Name Type Description
x-createOnly boolean Relevant only for Schema “properties” definitions. Declares the property as “create only”. This means that it MAY be sent as part of a POST request but SHOULD NOT be sent as part of the response or any other request type. If the property is marked as x-createOnly being true and is in the required list, the required will take effect on the POST request only. A property MUST NOT be marked as both x-createOnly and readOnly, writeOnly, x-updateOnly, x-createForbidden or x-updateForbidden being true. Default value is false.
x-updateOnly boolean Relevant only for Schema “properties” definitions. Declares the property as “update only”. This means that it MAY be sent as part of a PUT request but SHOULD NOT be sent as part of the response or any other request type. If the property is marked as x-updateOnly being true and is in the required list, the required will take effect on the PUT request only. A property MUST NOT be marked as both x-updateOnly and readOnly, writeOnly, x-createOnly, x-createForbidden or x-updateForbidden being true. Default value is false.
x-createForbidden boolean Relevant only for Schema “properties” definitions. Declares the property as “create forbidden”. This means that it MAY be sent as part of a PUT request or be sent as part of the response but SHOULD NOT be sent as part of any other request type. If the property is marked as x-createForbidden being true and is in the required list, the required will take effect on the PUT request and the response only. A property MUST NOT be marked as both x-createForbidden and readOnly, writeOnly, x-createOnly, x-updateOnly or x-updateForbidden being true. Default value is false.
x-updateForbidden boolean Relevant only for Schema “properties” definitions. Declares the property as “create and read only”. This means that it MAY be sent as part of a POST request or be sent as part of the response but SHOULD NOT be sent as part of any other request type. If the property is marked as x-updateForbidden being true and is in the required list, the required will take effect on the POST request and the response only. A property MUST NOT be marked as both x-updateForbidden and readOnly, writeOnly, x-createOnly, x-updateOnly or x-createForbidden being true. Default value is false.
x-requiredCreate [string] List properties required in the POST request.
x-requiredUpdate [string] List properties requires in the PUT request.
x-requiredRead [string] List properties required in the response.

Being able to describe requirement and presence of resource’s properties based on usage context could be a great enhancement.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:7
  • Comments:19 (8 by maintainers)

github_iconTop GitHub Comments

4reactions
handrewscommented, Mar 6, 2018

@pplr I want to explore your use case a bit with existing JSON Schema features. This also comes up in hyper-schema a lot so if possible I’d like to align Hyper-Schema (the spec for which I edit) and OpenAPI on this.

All this can be achieved using composition. I don’t think that’s a reasonable solution because: contract is not more human readable as resources are split in many schema

I’ve seen this get unmaintainable when people treat the JSON Schema like an additive definition system, but it works well when JSON Schema is used for what it is: a constraint layering system.

The “base” schema should define all possible fields, and only mark as “required”, “readOnly”, etc. the ones that should meet that constraint in all situations.

Then for each usage, just layer on the “required”, “readOnly”, etc., and filter out parameters by setting their property schemas to {"not": {}} (the simplest impossible schema - in new JSON Schema you can make this more clear and encourage tools to optimize by setting a boolean schema of false, but that’s no available in OpenAPI yet).

Example:

#/components/schemas/base:

{
    "required": ["alwaysHere"],
    "properties": {
        "id": {"type": "integer", "minimum": 0, "readOnly": true},
        "alwaysHere": {"type": "string"},
        "onlyOnCreate": {"type": "boolean"},
        "onlyOnUpdate": {"type": "string"},
        "sensitiveInformation": {"type": "string", "writeOnly": true}
    }
}

For POST to create:

{
    "allOf": [{"$ref": "#/components/schemas/base"}],
    "required": ["onlyOnCreate"],
    "properties": {
        "id": {"not": {}},
        "onlyOnUpdate": {"not": {}}
    }
}

For PUT requests:

{
    "allOf": [{"$ref": "#/components/schemas/base"}],
    "required": ["id"],
    "properties": {
        "onlyOnCreate": {"not": {}}
    }
}

For GET responses:

{
    "allOf": [{"$ref": "#/components/schemas/base"}],
    "required": ["id", "onlyOnUpdate"],
    "properties": {
        "onlyOnCreate": {"not": {}},
        "sensitiveInformation": {"not": {}}
    }
}

I think this covers most of your examples. It is more verbose, but it works and clearly shows the difference between the base and each usage. It also produces the behavior out of simple constraints rather than being a complex function of the extension keywords plus which other constraints are present with which value. In general, JSON Schema keywords that involve adjacent keywords are much harder to support and work with.

for polymorphic resources (using oneOf), there is too many different schema to create and duplication is inevitable

@pplr Is the duplication due to also using "additionalProperties": false or do you need to redefine properties across the oneOfs for other reasons? The next JSON Schema draft (which admittedly won’t help OpenAPI 3.x) will have a keyword that fixes the problems of using "additionalProperties": false with the *Of keywords.

3reactions
avanbrunt-cbcommented, Oct 27, 2020

This definitely seems like a hole in the Open API specification. This https://github.com/OAI/OpenAPI-Specification/issues/425#issuecomment-220882892 laid out the options well and showed how Microsoft solved their particular case.

Write only - e.g. secrets, which can be set, but you wouldn’t want to return to the client. Read only - e.g. server-generated values, such as created/modified timestamps Read + write once - immutable properties, such as resource geographical location Read + write many - I guess this is the normal case 😃

A possible suggestion for a new specifier could be writeOnce which would indicate that the property becomes readOnly once it has been set. This could possibly include the case where a PUT with a new id creates the resource beyond limiting this to a POST for creation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

CRUD operations explained: Create, read, update, and delete
CRUD operations are used to manipulate, read, insert, delete, and edit table data. In this tutorial, you'll learn the basics of CRUD for...
Read more >
Spring Boot and Java Tutorial: Build a CRUD API - Auth0
CrudRepository is a Spring interface declaring generic CRUD operations. The first generic type, Item , represents the objects you'll store. The ...
Read more >
CRUD operations in AngularJS | Create, Read, Update & Delete
This article on CRUD operations in AngularJS will walk you through the basics of CRUD which stands for Create Read Update Delete data...
Read more >
What is a CRUD API? | Traceable App & API Security
CRUD stands for create, read, update, and delete. These functions are the four pillars of a complete CRUD API (and full-stack application, for...
Read more >
CRUD | Components | Vaadin Docs
CRUD automatically generates columns for each field in the provided dataset. You can add columns with it and configure or remove existing ones....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found