Have arrays within arrays in a Swagger responses - api-design

I am trying to define an API in Swagger where the result of a GET is an array of items for model MainModel each with an associated array of items from another model called AnotherModel. Something like this (which does not seem to be allowed):
responses:
200:
description: search results matching criteria
schema:
type: array
items:
$ref: '#/definitions/MainModel'
type: array
items: $ref: '#/definitions/AnotherModel'

First of all, OpenAPI Specification supports associative arrays / dictionaries with string keys only, such as:
{
"foo": 5,
"bar": 2
}
but not C#'s Dictionary<int, string> where keys are int.
An associative array is defined by using an object schema with the additionalProperties keyword specifying the type of array values. The key type is not mentioned because keys are always strings.
type: object
additionalProperties:
type: <type of array values>
# OR
# $ref: '#/definitions/ValueModel'
Similarly, an array of associative arrays can be defined as follows:
type: array
items:
type: object
additionalProperties:
type: <type of values in associative array>
# OR
# $ref: '#/definitions/ValueModel'
In your example, the values of associative array are of type AnotherModel. So an inline definition would look like:
responses:
200:
description: search results matching criteria
schema:
type: array
items:
type: object
additionalProperties:
$ref: '#/definitions/AnotherModel'
definitions:
AnotherModel:
type: <whatever> # TODO
Or if we introduce an intermediate MainModel that represents the associative array (as in your original example):
responses:
200:
description: search results matching criteria
schema:
type: array
items:
$ref: '#/definitions/MainModel'
definitions:
MainModel: # Associative array of AnotherModel
type: object
additionalProperties:
$ref: '#/definitions/AnotherModel'
AnotherModel:
type: <whatever> # TODO
A couple of notes:
Each model referenced via $ref must be defined in the definitions section. So if you use $ref: '#/definitions/MainModel', your spec must include
definitions:
MainModel:
$ref works by replacing itself and all its sibling elements with the definition it is pointing at. That's why your original example is not valid:
items:
$ref: '#/definitions/MainModel'
type: array
items: $ref: '#/definitions/AnotherModel'
Nested models can be either defined completely inline or referenced via $ref, but not both.

Related

openAPI-specification: what is the type of the variable when one of them is given in the AdditionalProperties?

I am new to OpenAPI-specifications and I was confused about the type of a variable when the attribute additionalProperties is used.
For example at https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaObject we see the example
{
"type": "object",
"additionalProperties": {
"type": "string"
}
}
What is the type of this now? Is it a string or an object? I do not see how it can be both of them.
thanks in advance, Koen
In this context, the additionalProperties keyword is used to define dictionaries/maps. type: object represents the dictionary itself, and the additionalProperties keyword defines the type of values in the dictionary. The key type is not mentioned because the keys are always strings.
A more general explanation is that additionalProperties is used to allow or deny extra properties not explicitly defined in the properties and patternProperties sections. If extra properties are allowed, additionalProperties specifies their value type.
The example is your post represents a simple string-to-string dictionary, or an object with arbitrary properties whose values are strings, such as:
{
"foo": "bar",
"hello": "world"
}
Similarly, a string-to-integer dictionary is defined as:
{
"type": "object",
"additionalProperties": {
"type": "integer"
}
}
This schema represents objects such as:
{
"apples": 3,
"oranges": 5
}
If the additionalProperties keyword does not specify a type, this means a free-form object with arbitrary properties and values.
{
"type": "object",
"additionalProperties": {}
}
// In OpenAPI 3.x, this can also be written as
{
"type": "object",
"additionalProperties": true
}
For additional info and examples, see:
Dictionaries, HashMaps and Associative Arrays on swagger.io
Additional Properties in the Understanding JSON Schema guide
Why additionalProperties is the way to represent Dictionary/Map in OpenAPI
Swagger Editor dictionary parameter definition
Swagger complex response model with dynamic key value hash maps

Object must NOT have key

I am trying to define that an object must NOT have a certain key.
Here is my case:
alert({
items: [{ label:'Apple' }, { label:'Orange' }]
})
alert({
items: [{ foo:'Apple' }, { foo:'Orange' }]
labelKey: 'foo'
})
If items is an array of ojects that does not contain "label" key, then labelKey is required in Options
I tried this:
type Options = {|
items: Array<{ label:string }>
|} | {|
items: Array<$Diff<{}, { label:string }>>,
labelKey: string // must be key in items
|}
function alert(options: Options) {
}
Bonus question:
Also is it possible to define that labelKey is any key from objects passed in items?
Ensuring a property does not exist on an object
tl;dr: use { myProp?: empty }
I'm assuming you want to use the objects as maps when you pass something into the alert function. The trick to creating a map without a label is to give a property that, if assigned to something, will fail to typecheck.
We can leverage the empty type, a type which doesn't match against anything, to get the desired effect. It's a little tricky to use an empty type in conjunction with an object map, because by defining a property, we tell flow that we want that type to be in the object. So this fails to typecheck:
(Try)
type MapWithLabel = {
[string]: string,
label: string,
}
type MapWithoutLabel = {[string]: mixed, label: empty}
type Options = {|
items: Array<MapWithLabel>
|} | {|
labelKey: string,
items: Array<MapWithoutLabel>,
|}
declare function alert(options: Options): void;
alert({
items: [{ foo:'Apple' }], // Error, expected a "label" property with empty type
labelKey: 'foo'
})
Next, we can define the property as optional, which means to only typecheck against empty if the property exists. With this we can give the object a "label" property that either:
Does not exist OR
Has a type that matches nothing (empty)
So code can either not have a value for that property (what we want), or it can pass something that is empty (this isn't possible).
(Try)
type MapWithLabel = {
[string]: string,
label: string,
}
type MapWithoutLabel = {[string]: mixed, label?: empty}
type Options = {|
items: Array<MapWithLabel>
|} | {|
labelKey: string,
items: Array<MapWithoutLabel>,
|}
declare function alert(options: Options): void;
alert({
items: [{ label:'Apple' }],
})
alert({
items: [{ label:'Apple' }], // Error - Should not have label
labelKey: 'ohno',
})
alert({
items: [{ foo:'Apple' }],
labelKey: 'foo'
})
alert({
items: [{ foo:'Apple' }], // Error - Needs a labelKey
})
So to get the desired effect, we needed to leverage two tools: optional properties and the empty type. With it, we can specify an object that will fail to typecheck if that empty property exists.
Setting a dynamic property key at the type level
tl;dr: not possible
Regarding the bonus question: I'm not sure Flow could understand that since I don't know of a way to set a variable property on objects. I would not expect this feature since it could make things complicated/impossible to type check.
Edit: After a little more research, you can use indexer properties to assert an object has a key at the type level:
(Try)
type ObjWithKey<T: string = 'label'> = {
// An indexer property with only one valid value: T with "label"
// as default, but we can't ensure that the property exists anymore
// and multiple indexers are not supported.
[T]: string,
aNumber: 3,
aFunction: () => void,
}
declare var usesLabel: ObjWithKey<>
(usesLabel.label: string);
(usesLabel.aNumber: number);
(usesLabel.missing: number); //Error - Doesn't exist on object
(usesLabel.aFunction: () => void);
(usesLabel.aFunction: string); //Error - Wrong type
However, you can't do that and use the object as a general map, since multiple indexer properties are not supported (Try). For reference, someone else tried to do something else similar, but couldn't get it to work.
If that's a major problem for you, see if you can architect your data structure in a different way to make it easier for static analysis with Flow.

Custom type is incompatible with mixed

The problem can be demo'd here
I want to define a function that can take an array of mixed types:
function foo(x: Array<mixed>): string {
// ... do something
}
Then I try to call it with an array off some custom object type:
type Thing = {
id: string
}
let array : Array<Thing> = [{id: 'hello'}];
foo(array);
... and I get the following error
Cannot call `foo` with `array` bound to `x` because `Thing` [1] is incompatible with mixed [2] in array element.`
Is there something that I'm not understanding about the mixed type. Why can't a use an array of objects as an argument?
It has to do with a mutability of the array argument. You can use $ReadOnlyArray to satisfy the requirement.
function foo(x: $ReadOnlyArray<mixed>): string {
// ... do something
return "hello";
}
type Thing = {
id: string
}
let array : Array<Thing> = [{id: 'hello'}];
foo(array);
Array's are passed by reference, not by value, so the value contained in the variable array can be modified within foo. e.g.
x.push(1)
array would no longer be an array of Thing only. So using $ReadOnlyArray means that x is immutable and therefore array is safe.

Construct deep struct is not working

i’ve struct which responsible to parse data from yaml file
While this struct is working sometimes I got some newly fields which I need to parse
This is working
- name: test1
type: type
path: path
This is not
- name: test1
type: type
path: path
build-parameters:
maven-opts:
defines:
skipTests: true
This is the struct
type Modules struct {
Name string
Type string
Path string
Parameters Parameters `yaml:"build-parameters,omitempty"`
}
And the parameters is type of:
type Parameters map[string]string
How I should construct my struct to accept this build-parameters entries also?
This is the library I use
https://github.com/go-yaml/yaml
Your struct does not match the data structure. If Parameters is a map[string]string, it accepts key-value pairs where the key and value are both string. In your data, build-parameters contains an object, which contains an object, which contains a key-value pair.
You could either redefine Parameters to be map[string]interface{} and use type assertions, or you can define the entire structure, e.g.:
type Parameters struct {
MavenOpts struct {
Defines map[string]string
} `yaml:"maven-opts"`
}
If you use the empty interface, you'll have to use type assertions, which can get pretty cumbersome. For example:
if opts, ok := module.Parameters["maven-opts"].(map[string]interface{}); ok {
if defines,ok := opts["defines"].(map[string]interface{}); ok {
if skipTests,ok := defines["skipTests"].(bool); ok {
// skipTests is the bool value from the yaml
}
}
}

Nullable embedded value object with not nullable fields

I created an entity "Person" in Doctrine2, and I added to it an Adress entity, which is a value object (embeddable).
I want to allow a Person creation, without an Address, so I tag my embedded as "nullable = true". But on the other hand, my Address entity, if it exists, SHOULD contains at least some information (like city, zip code, etc...). So it has "nullable = false" attributes.
Address:
type: embeddable
fields:
[...]
city:
type: string
length: 255
nullable: false
Person:
type: entity
table: null
embedded:
address:
class: Address
nullable: true
It seems that the "nullable = true" of the embedded object was not working.
Do you know if it's a normal Doctrine behaviour ?
Do I have to put all my embeddable attributes to nullable = true ?

Resources