Referencing OpenAPI 2.0, Schema Object, or Swagger 2.0, Schema Object, and the definition of discriminator field as:
Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. The property name used MUST be defined at this schema and it MUST be in the required property list. When used, the value MUST be the name of this schema or any schema that inherits it.
My confusions/ questions:
It is ambiguous to me, what role exactly it plays in inheritance or polymorphism. Could some one please explain discriminator with a working example showing what it exactly does and what if we do not use it? Any errors, warnings or any tools that depends on it for some operations?
Is it the case that swagger-editor does not support discriminator, and this field is used in some other tools?
What I have tried so far:
I have tried to use swagger-editor and the example from the same documentation (also mentioned below), to play around with this property to see if I can see any of its special behaviors. I changed the property, removed it, and extend the Dog model to one level deeper and tried the same on the new sub-model, but I did not see any changes in the preview of swagger-editor.
I tried searching online, and specially stackoverflow questions, but did not find any relevant information.
The sample code I used to do experiments:
definitions:
Pet:
type: object
discriminator: petType
properties:
name:
type: string
petType:
type: string
required:
- name
- petType
Cat:
description: A representation of a cat
allOf:
- $ref: '#/definitions/Pet'
- type: object
properties:
huntingSkill:
type: string
description: The measured skill for hunting
default: lazy
enum:
- clueless
- lazy
- adventurous
- aggressive
required:
- huntingSkill
Dog:
description: A representation of a dog
allOf:
- $ref: '#/definitions/Pet'
- type: object
properties:
packSize:
type: integer
format: int32
description: the size of the pack the dog is from
default: 0
minimum: 0
required:
- packSize
According to this google group, discriminator is used on top of the allOf property and it is defined in the super type for polymorphism. If discriminator is not used, the allOf keyword describes that a model contains the properties of other models for composition.
Just like in your sample code, Pet is a super type with property of petType identified as the discriminator and Cat is a sub type of Pet. Following is a json example of a Cat object:
{
"petType": "Cat",
"name": "‎Kitty"
}
The use of discriminator intends to indicate the property used to identify the type of an object. Assumes that there are tools that can proper support definition objects with the use of discriminator, it is possible to determine the type by scanning the property. For example, identify the object is a Cat according to petType.
However, the discriminator field is not well defined in the current version's specification or the samples (see issue #403). As far as I know, there is no tools provided by Swagger properly support it at the time being.
discriminator may be used if the model has a property used to determine the type. In this case, it is naturally fit and it can be used as an indicator for other developers understand the polymorphism relationship. If third party tools like ReDoc which support discriminator (see petType in this gif and example) is considered, you may find this useful.
The discriminator functionality has been much improved in OpenApi 3. You now provide a discriminator object which contains the name of the discriminator property, as well as a mapping of values of that property to schema names.
(I realize you did ask about OpenApi 2, but this is so much improved in 3 that hopefully you can make use of it).
See: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#discriminatorObject for the v3.0.0 spec
Related
Trying to wrap my head around OpenAPI difference between defining a reusable object in the components/schemas section or in the components/requestBodies if it's indeed an object that is the body of a request.
Is there any difference besides the separation of simple request payloads that are no real domain objects? Why do we have different sections for schemas, requestBodies, and responses?
Especially when looking at the generated client code I see no difference between a class that was defined in schemas or one defined in requestBodies.
A schema describes a specific data structure used by an API. Such as a certain object, its properties, their data types, and so on.
Some other component types use schemas as one of their building blocks.
A request body component defines whether the request body is required or optional, the supported media types for the request body (e.g. application/json, multipart/form-data, application/octet-stream) and the schema for each media type (these schemas can be the same or different).
Similarly, a response component defines not just the response schema, but also possible response media types and response headers.
If we rewrite the indicated parts as components, we end up with:
paths:
/users:
post:
summary: Create a user
requestBody:
$ref: '#/components/requestBodies/createUser'
responses:
'201':
$ref: '#/components/responses/userCreated'
components:
schemas:
User:
type: object
properties:
id:
type: string
format: uuid
readOnly: true
name:
type: string
example: Helen
requestBodies:
createUser:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/User'
responses:
userCreated:
description: >-
The user was successfully created.
The response contains the created user.
content:
application/json:
schema:
$ref: '#/components/schemas/User'
While using components/schemas to give names to schemas is a common practice, using components/requestBodies and components/responses instead of inline definitions is more of a convenience thing. You would typically put the latter into components only if the same request body definition or response definition is duplicated in multiple places. For example, 401 and 403 responses usually have the same schema and description in all operations so you may want to $ref them to reduce the repetition, whereas 200/201 responses that need unique descriptions can be kept inline.
Especially when looking at the generated client code I see no difference between a class that was defined in schemas or one defined in requestBodies.
This might vary from one codegen to another. But it's probably because schemas are converted to classes, whereas request body and response attributes (such as media types) are expressed with annotations. So in the code generation context, it doesn't really matter if request bodies and responses are defined inline or in components.
I would like to add a description field to my dto (also to satisfy no_schema_description in OpenAPI linting), but find no way to do so. Which decorator to use? At the point of defining the dto or in the response?
Update (clarify): I am looking to define a description for the whole schema, not for single properties.
You can do that simply by using ApiProperty():
#ApiProperty({
description: 'The age of a cat',
})
age: number;
I recommend you to check the official doc openapi/types-and-parameters
I have a GraphQL enum like:
class MediaFilterType < Types::BaseEnum
value "TWITTER", value: :twitter
value "FACEBOOK", value: :facebook
value "YOUTUBE", value: :youtube
end
And a field that receives an array of this type or nil. Actually when we receive nil, we should pass along that we are going to use all available values in the enum:
def dashboard(media_types: nil)
if media_type.nil?
# Here is the problem below
media_types = MediaFilterType.values.values.map(&:value)
end
...
DashboardQuery.new(media_types).call
end
So I have to do this conditional sanitization for every field just like dashboard field. Not a big deal, except for the duplication. But I think this is a responsibility that should be inside the Query (there is a collaborator that can do that sanitizing inside the Query object). But I think it would be worse to extract the values from the GQL enum type inside a business object. Actually don't even know if I should be extracting these enum values anyway.
Is it good practice to extract values from the GQL type like that?
If it is ok, there is another way to use the enum values inside a business object like a query object or a sanitizer without having them depend on the GQL enum type?
Should I create an Enum with the same values in my business layer?
Appreciate any help.
There is no definitive 'right' answer here. The issue is, graphql-ruby requires a fair bit of duplication in many other cases - think of your models and types. I would not be too scared of a tiny bit of duplication. However...
But I think it would be worse to extract the values from the GQL enum type inside a business object
Depends. If the values are only used within the API and literally nowhere else your implementation is already as good as it gets.
Should I create an Enum with the same values in my business layer?
That is an option. If you forgo documentation for the GraphQL enum types you can get rid of any duplication fairly elegantly. Assuming you add your media filters as enums on some model:
class Dashboard
enum media_types: [:twitter :facebook :youtube]
end
You can then do:
class MediaFilterType < Types::BaseEnum
Dashboard.media_types.each { |media_type| value(media_type.upcase, value: media_type.to_sym)}
end
And for your field:
types = media_types || Dashboard.media_types
DashboardQuery.new(media_types).call
Your model enum now serves as a single source of truth.
However, it is not uncommon that your API diverges a bit from your models with time, so in that case there really isn't a nice way around duplicating at least some things. Just to keep that in mind.
Let's say I have a model like this:
Film:
properties:
title:
type: string
duration:
type: string
actors:
type: array
items:
$ref: '#/components/schemas/Person'
And I have a lot of models that inherit from this and add some extra properties.
But I also have a model in which the actors are not Persons but Aliens.
Would I need to remove actors from the common model and add it specifically in each inheriting model? (I hope not.)
Can I simply inherit from Film and redefine the actors attribute?
FilmWithAliens:
allOf:
- $ref: 'components/schemas/Film'
- properties:
actors:
type: array
items:
$ref: '#/components/schemas/Alien'
This seems to work correctly when generating the documentation using Swagger UI. But if I read the Swagger docs here: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
It seems they say that this is not recommended:
Note: When validating the data, servers and clients will validate the
combined model against each model it consists of. It is recommended to
avoid using conflicting properties (like properties that have the same
names, but different data types).
So my question is: how should it be done correctly?
I am using Protege 5.0.0 for developing an ontology. Part of the Ontology is shown in figure at the link:
http://imgur.com/Yq6EF3i
For the given Ontology I have created classes OperatingRange,OperatingPowerRange,and ValueRange. Also, properties hasOperatingProperty, hasValue and other properties are created. I am facing problem in the following:
For ssn:hasOperatingProperty, I am using OpertingRange class as domain and ssn:OperatingPowerRange class as range.Now, according to figure should I set ssn:hasOperatingProperty as domain for ssn:hasValue? I tried to do the same but Protege is not showing any option for this.
What is the meaning of "can be" and "all of" written below some properties in the above figure?
For the unitOfMeasure property should I set ssn:hasvalue as domain and unit:Volt as range?
I believe the pattern:
'propertyName'
can be
one of
'className'
is a way to represent restrictions, which are class expressions, and should be read as: at least one object property assertion with property 'propertyName' exists, and the individual in that assertion is an instance of 'className'.
'can be' and 'one of' are to be read as 'can be one of' - meaning an existential quantification. 'can be' 'all of' is universal quantification.
Regarding unitOfMeasure, you cannot have a property as a domain for another property, unless the second property (hasValue in your case) is an annotation property. Using unit:Volt as range sounds correct.
Edit: to answer the comment, you need to add a class expression do the domain. In Protege you do this by clicking on the domain tab, and choosing the type of restriction, the property along which it acts and the type you expect to be a filler (can be Thing). See the picture for reference.