Reading this post (see: 3 How to use a single definition when...) about describing a REST API using OpenAPI (Swagger) specification you can note how to keep a single resource representation for adding/updating and getting resource using readOnly property instead of having one representation for getting (GET a collection item) and other one for adding (POST to a collection). For example, in the following User single representation, the id is a read-only property which mean that it won't be sent in the representation when a user is created, it will be there when a user is retrieved.
"User":
{
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64",
"readOnly": true
},
"company_data": {
"type": "object",
"properties": {
.
.
.
},
"readOnly": false
}
}
}
It is really clean and nice to keep the list of resource representation as short as possible so I want to keep the single resource representation approach but the problem I am facing to do that is: how to manage required when a property is mandatory for input only? Suppose I need to set company_data as required when the user is created (POST /users/) but non-required when an user is retrieved (GET /users/{user_id}). There are any way in OpenAPI specification to satisfy this need without lost the single resource representation?
From the Swagger-OpenAPI 2.0 spec, readonly is defined as follows:
Declares the property as "read only". This means that it MAY be sent
as part of a response but MUST NOT be sent as part of the request.
Properties marked as readOnly being true SHOULD NOT be in the required
list of the defined schema. Default value is false.
Since the specification says that a read-only property should not be required, there are no defined semantics for what readonly + required should mean.
(It might have been reasonable to say that readonly + required means it's required in the response, but still excluded from the request. In fact there is an open issue to make this change, and it looks like it's under consideration for OpenAPI 3.0.)
Unfortunately there is no way for a single schema to make properties required in the request, but optional (or disallowed) in the response.
(Again, there's an open issue proposing a "write-only" modifier, possibly under consideration for the next release.)
For now, you would need to create different schemas for these different cases. As described here, you might be able to make these schemas a bit more DRY using allOf composition.
Related
Is the following OpenAPI definintion valid?
"settings": {
"type": "object",
"description": "Json formatted public settings for the extension."
},
It's defined as object but has no sub-properties.
Yes, this definition is valid and means that settings is a free-form object, that is, it can have arbitrary properties or be an empty object.
This is an OWASP security risk
https://apisecurity.io/encyclopedia/content/oasv2/datavalidation/schema/schema-object-without-properties
I'm using go rego package, and the rego.ResultSet when marshalled gives this:
[
{
"expressions": [
{
"value": {...},
"text": "data",
"location": { "row": 1, "col": 1 }
}
]
}
]
I intend to output the location(s) in Input JSON where the keys were responsible for failures so that I can use this in building context for the errors
We used JSON schema earlier for validating JSONs and it used to return the keys from input that we can map with errors. https://www.jsonschemavalidator.net/
I suppose as rego could support far more complex decision making where more than one key would be responsible for making the final outcome, that could be the reason it wouldn’t point to a location in the input for failure context. unless am I missing anything?
To answer the first question:
Every value parsed by OPA retains "location" information identifying where it came from in the source string/file. The location in the ResultSet is the location of the expression in the query that was passed when creating the rego.Rego object.
In your case, the query was "data", i.e., you referred to ALL of the documents in OPA (both base documents which could have be loaded from outside as well as virtual documents generated by any rules you loaded into OPA.) The location of the expression in this case is not very interesting: row 1, column 1.
To answer your second question:
OPA does not currently have a reliable way of returning the location of JSON values in the input however this is something that would be valuable and could be added in the future.
I am following the OpenAPI (swagger) specification to define my API endpoints. This is an excerpt of an endpoint definition to get user's data:
"paths": {
"/users/{id}": {
"parameters": [
{
"name": "id",
"in": "path",
"description": "The user ID on which the operation will be executed.",
"required": true,
"type": "integer",
"format": "int32",
}
]
}
}
But I want to use /users/me to get the data of logged in user (because at some point the user ID is unknown for logged user). As you can see me is a string, not an integer so I could not find a way to mix types in parameter definition according to OpenAPI specification.
Is there any way or workaround to do that? Should I define another endpoint /users/me and duplicate the /users/{id} definition to satisfy this need?
I think there are two possible ways to do this:
As you suggested, you can define a second endpoint at /users/me, without any path parameters. Swagger should allow this, because the the URI strings are distinct. The users/me segment doesn't match an endpoint expecting an integer value; nor does an integer match the fixed string me.
You could also define a single path, with the {id} parameter typed as a string, with a pattern constraint that allows 'me' or an integer.
Here's what the second option would look like:
{
"paths": {
"/users/{id}": {
"parameters": [
{
"name": "id",
"in": "path",
"description": "The user ID on which the operation will be executed.",
"required": true,
"type": "string",
"pattern": "^[-+]?[1-9]\\d*$|^[Mm][Ee]$"
}
]
}
}
}
Notes:
The regular expression allows an optional leading + or - sign, followed by an integer value. If you don't need signed integers, you can remove the [-+]? expression.
This allows an arbitrary integer, without any range restriction, and with leading zeroes disallowed. Restricting this to the int32 range is considerably messier, but possible if you need to do it.
Note the escaped backslash for \d (decimal numeral). JSON strings require this.
The regex also allows me as an alternative to the integer, using any combination of upper and lower case.
As for whether to use the first or second option, it really depends on your API and your code. If the API contract is exactly the same, syntactically, the second option has the advantage of only requiring one path object, therefore only one handler in your code.
If the rules are different, e.g. the /users/me variant requires an API key or user token to be passed in the header, then the first option might be cleaner.
Simplest Solution will be to breakup this into two api's if you're not interested in multiplexing the same api.
eg.
/users/{id}
/current_user
The /current_user path won't even need any parameters as the authentication scheme you choose to implement would suffice.
I have a REST API where legal JSON submitted to an endpoint will contain a sub tree depending on a "type" declaration in the top level. Something like:
{
...
"mtype": "http:...",
"content": {
.. what goes here is what would be defined by the above "mtype"
Essentially, the 'mtype' is the schema url for what goes into 'content'. But what I'm looking for is the equivalent of <xsd:any>.
It would already be useful if I can put something in there which would instruct the Swagger UI to simply put a large text box there for users to paste a JSON body.
you can return an object of type object with no properties as a response. It's not best practice but it can serve your purposes
I don't know Swagger that well, but I know it's type definition is based on JSON Schema. In JSON Schema, the equivalent of <xsd:any> is {}. The empty schema validates as true for any valid JSON. I expect this should be no different in Swagger.
I'm attempting to change the type of one custom orgunit to another to correct an error that was made previously.
Doing: GET /d2l/api/lp/1.4/orgstructure/6770
Results in:
{
"Identifier": "6770",
"Name": "Art",
"Code": "ART",
"Type": {
"Id": 101,
"Code": "Department",
"Name": "Department"
}
}
I then take that data and run it through PUT /d2l/api/lp/1.4/orgstructure/6770 as per the documentation however I change the data to be:
{
"Identifier": "6770",
"Path": "/content/",
"Name": "Art",
"Code": "ART",
"Type": {
"Id": 103,
"Code": "Discipline",
"Name": "Discipline"
}
}
Essentially only adding a "Path" property because it issues a 404 without it. As well as changing the type to a Discipline rather than Department. However the object returned is identical to the original without updating any of the type information.
Any suggestions to how to fix this? Deletion and recreation at this point is not a feasible option at all. Because both of these are "custom" org unit types I would imagine an update like this shouldn't be difficult.
This is an oversight in the documentation, combined with a somewhat awkward evolution of the API. The documentation has now been updated to be more clear on this situation:
The update orgunit properties call can only update the Name, Code, or Path properties of an orgunit, not it's Identifier (sensibly) or it's Type. (I do not believe there is a way to update the type of an org unit, once created, even in the Web UI for the LMS -- you likely have to create a new org unit, re-assign parent and children relationships as appropriate, and then drop the old unit.)
Unfortunately, you must provide a valid, good Path for the org unit, and the simple call to fetch a single org unit's properties won't tell you what the current one is.
If you don't already know what the path is, and should be, then you'll need to call the route to fetch back a list of org unit records, find the exact one that matches yours (by Identifier, or by matching on several properties like Code and Name), and then send back that Path dispensed in the record sent back there. (Note that you're strongly advised to scope the call to fetch back a list of org unit records by filtering on type, code, and/or name, and the call is paged, so you may have to proceed with it several times if you don't scope down the call enough, to find the particular org unit record in question.)