This issue is about the difference between modeling stuff as
object (C#), any (TypeScript), i.e. accepting anything including 42, "asd", [42], { "asd": 42 } etc.Dictionary<string, object> (C#), object (TypeScript), i.e. accepting any JSON object { ... }We (specs reviewers, validation tools, AutoRest) misinterpreted what it means for a schema to have properties, but no type: object. We thought presence of properties implies type: object, but it does not, see here.
almost 9000 schemas that mistakenly do not have type: object on them
Impact:
type: object (e.g. where we have properties), remove type: object (where wildcard/"untyped" behavior was expected)Example method to validate the above using AutoRest:
How to generate code models: --use=<modeler> (for latest: @microsoft.azure/[email protected], for fixed one: olydis/autorest.modeler#object-types) and --standalone-modeler --output-artifact=code-model-v1.norm.yaml
The goal is to force people to clearly express their intended semantics. We should do so by disallowing certain vague (but technically valid) patterns.
type (next to properties, ...) since that is what 99.9% of all APIs want; exceptions like data factory can gladly get a suppression...type: object but no properties and no additionalProperties: true/{...} (you likely meant this to be a wildcard type - if you really meant object as in JSON object, add additionalProperties: {})DONE: there is now a MultiType (AutoRest.Common version 2.4.40 and above) to represent the case when there was no type specified in Swagger
DONE: tag @objecttypes of the modeler is updated to treat Swagger spec compliantly (see table below for new treatment)
If in the Swagger there's a schema containing properties and either no type: object, we generate a strong type, even though we should not.
If in the Swagger there is type: object but no properties, the C# generator generates object in the code, which is (see above) a wildcard type, i.e. also accepts numbers and stuff! I think some other languages follow that (bad) history and service teams started to build on that behavior. In other words, we have Swaggers out there that model parameters/properties as type: object but expect it to accept numbers too. And we made it work. Except that it happens that Go is still sane and does not have this weird interpretation.
How generators treat PrimaryType with KnownPrimaryType object:
| language | behavior |
| - | - |
| C# | wildcard (also serializes known models correctly) |
| Go | object (serialization is broken for non-conformant JSON) |
| Java | TODO |
| NodeJS | TODO |
| Python | wildcard (also serializes known models correctly) |
| Ruby | TODO |
| TypeScript | TODO |
PrimaryType with KnownPrimaryType object to a JSONy object type and map MultiType to a true wildcard type@objecttypes, to be promoted to latest once everything is fixed) compared to the latest stable| Schema | Old Model (latest stable) | New Model (to be released after specs repo fixed) |
| - | - | - |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | PrimaryType with KnownPrimaryType object | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | PrimaryType with KnownPrimaryType object | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | CompositeType | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽additionalProperties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽additionalProperties:聽{},聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽additionalProperties:聽{},聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | CompositeType | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽additionalProperties:聽{聽type:聽boolean聽}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽additionalProperties:聽{聽type:聽boolean聽},聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | MultiType |
| {聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽additionalProperties:聽{聽type:聽boolean聽},聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | CompositeType | MultiType |
| {聽type:聽object聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | PrimaryType with KnownPrimaryType object | PrimaryType with KnownPrimaryType object |
| {聽type:聽object,聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | PrimaryType with KnownPrimaryType object | PrimaryType with KnownPrimaryType object (CompositeType if --generate-empty-classes) |
| {聽type:聽object,聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | CompositeType | CompositeType |
| {聽type:聽object,聽additionalProperties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | DictionaryType |
| {聽type:聽object,聽additionalProperties:聽{},聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | CompositeType |
| {聽type:聽object,聽additionalProperties:聽{},聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | CompositeType | CompositeType |
| {聽type:聽object,聽additionalProperties:聽{聽type:聽boolean聽}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | DictionaryType |
| {聽type:聽object,聽additionalProperties:聽{聽type:聽boolean聽},聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | CompositeType |
| {聽type:聽object,聽additionalProperties:聽{聽type:聽boolean聽},聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | CompositeType | CompositeType |
| {聽type:聽string聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | PrimaryType with KnownPrimaryType string | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | PrimaryType with KnownPrimaryType string | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | PrimaryType with KnownPrimaryType string | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽additionalProperties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽additionalProperties:聽{},聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽additionalProperties:聽{},聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | DictionaryType | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽additionalProperties:聽{聽type:聽boolean聽}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽additionalProperties:聽{聽type:聽boolean聽},聽properties:聽{}聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽} | DictionaryType | PrimaryType with KnownPrimaryType string |
| {聽type:聽string,聽additionalProperties:聽{聽type:聽boolean聽},聽properties:聽{聽prop:聽{聽type:聽integer聽}聽}聽} | DictionaryType | PrimaryType with KnownPrimaryType string |
Interesting. Right now Python has the wildcards semantic and accept everything without any constraints.
Side note, if type is object and the user gives to Python a Model defined in the Swagger, Python serializes it correctly. It was a requirement for Data Factory. Of course this doesn't work on deserialization, since I don't know how to deserialize into a Model.
Thanks for the feedback, C# does the same - let's actually collect that information!
@johanste FYI
Yes;
We should run thru the rest-specs-repo and find whats' there, and how it's going to change and retro fit the definitions to generate what it currently does, so that we introduce no changes.
I think we should also figure out which of the permutations need a validation rule saying "Just what the hell do you think you're doing?!"
Most helpful comment
Interesting. Right now Python has the wildcards semantic and accept everything without any constraints.
Side note, if type is
objectand the user gives to Python a Model defined in the Swagger, Python serializes it correctly. It was a requirement for Data Factory. Of course this doesn't work on deserialization, since I don't know how to deserialize into a Model.