oliyh / martian

The HTTP abstraction library for Clojure/script, supporting OpenAPI, Swagger, Schema, re-frame and more
MIT License
525 stars 42 forks source link

Handle schemas with no type. #148

Closed bombaywalla closed 2 years ago

bombaywalla commented 2 years ago

This partially addresses #145.

If a schema has no "type" key, and ONLY has a "properties" key, then it is a valid schema and we can reasonably assume that the type is "object".

See https://github.com/OAI/OpenAPI-Specification/issues/1657

Excerpt: A particularly common form of this is a schema that omits type, but specifies properties. Strictly speaking, this does not mean that the value must be an object. It means that if the value is an object, and it includes any of those properties, the property values must conform to the corresponding property subschemas.

In reality, this construct almost always means that the user intends type: object, and I think it would be reasonable for a code generator to assume this, maybe with a validation: strict|lax config option to control that behavior.

oliyh commented 2 years ago

Hello,

Thanks for your continued work on this. From reading the spec, it seems like it could be expressed as:

(s/either {(s/optional k) v ...} ;; if it's an object and it includes _any_ of these properties
          s/Any ;; does not mean the value has to be an object
          )

It's not pretty, but seems to closer match the intention of the spec. What do you think?

bombaywalla commented 2 years ago

What you have is more accurate with respect to the spec. I'd guess that in the particular example I am dealing with, the person writing the spec just meant for it to be an object, and didn't understand the subtleties.

Would you like me to modify the PR to make it what you suggest?

oliyh commented 2 years ago

I'm inclined to agree with you, it seems a bit rubbish that it would allow "foo" through, and actually I don't think schema would give the right behaviour anyway. Consider:

Properties of foo and bar as ints in the schema, and the user supplies {:foo 123 :bar "not a number"}. This would pass the schema I wrote above, because it would fail the map schema but then pass the Any schema. From the wording, because the keys are present, they should use the map schema only and therefore fail.

Really, the 'correct' implementation is this:

(s/conditional 
  #(some? % [:foo :bar]) 
  {(s/optional-key :foo) s/Int 
   (s/optional-key :bar) s/Int} 

  :else s/Any)

But this seems to be getting overly complicated now. I think, given that no one has raised an issue about this before, that we go with your implementation and we can always adjust it later if it causes problems for someone else.

bombaywalla commented 2 years ago

Made the code improvement you suggested.

oliyh commented 2 years ago

Many thanks once again

bombaywalla commented 2 years ago

You're welcome Thanks for martian and for guiding the PRs to successful merge.