[dry-schema] Key annotations

It would be very cool if it were possible to add annotations to keys, which can then be used to generate documentation.
Proposed syntax:

ProductSchema = Dry::Schema.JSON do
    required(:name).filled(:string)
                   .annotate('name of the product')
    required(:price).filled(:decimal)
                    .annotate('price in USD')
end

Or:

ProductSchema = Dry::Schema.JSON do
    required(:name).filled(:string)
                   .description('name of the product')
    required(:price).filled(:decimal)
                    .description('price in USD')
end

You can use these annotations with json-schema and info extensions

ProductSchema.json_schema(description: true)
# {
#   "$schema": "http://json-schema.org/draft-06/schema#",
#   "type": "object",
#   "properties": {
#     "name": {"type": "string", "minLength": 1, "description": "name of the product"},
#     "price": {"type": "number", "not": {"type": "null"}, "description": "price in USD"}
#   },
#   "required": ["name", "price"]
# }
ProductSchema.info
# {
#   keys: {
#     name: { required: true, type: 'string', description: 'name of the product' },
#     price: { required: true, type: 'float', description: 'price in USD' }
#   }
# }

What do you think? I could probably try making a PR if there is interest in merging it.

There was some discussion earlier about this, I have not seen any PRs come out of it though.

Thanks for pointing that out. That discussion is about using metadata from custom dry-types. However, I wrote about adding description to the dry-schema definition using built-in types.
Perhaps we could combine both. Then when generating a json-schema, the metadata would be taken from the dry-schema definition first, and then from the type. Being able to add example to the dry-schema definition would also be nice.
I don’t like to wrap metadata in an optional json_schema key, because that would be cumbersome in the dry-schema definition. Also, I think it would be better not to bind exactly to JSON schema, since the description and example metadata could be used by other extensions and compilers as well.
But at the same time, I’m not quite sure if the meta method is intended to define data to be used by built-in extensions, not for some custom purpose, and whether conflicts with other metadata are possible. :thinking:

If using metadata, we could extend the dry-schema DSL with the .meta method, delegating parameters to the dry-type .meta method or storing them in its own instance variables.

Thus I could imagine something like this:

module Types
  include Dry.Types()

  Email = self::String.meta(
    description: 'An email address',
    example: 'person@example.com',
  )
end

ProductSchema = Dry::Schema.JSON do
  required(:name).filled(:string).meta(
    description: 'Name of the product',
    example: 'Phone'
  )
  required(:price).filled(:decimal).meta(
    description: 'Price in USD',
    example: '999,99',
  )
  optional(:vendor_email).filled(Email)
end

ProductSchema.json_schema(meta: true)
# {
#   "$schema": "http://json-schema.org/draft-06/schema#",
#   "type": "object",
#   "properties": {
#     "name": {"type": "string", "minLength": 1, "description": "Name of the product", "example": "Phone"},
#     "price": {"type": "number", "not": {"type": "null"}, "description": "Price in USD", "example": "999,99"},
#     "vendor_email": {"type": "string", "description": "An email address", "example": "person@example.com"}
#   },
#   "required": ["name", "price"]
# }

I would suggest going in a slightly different direction with this… there is already a facility for mapping I18n files to schema errors, why not expand this for annotations?

If you have annotations in one language, you may very well want to support additional.

Yeah, I guess translations will be the most universal way. Although originally I had an idea to merge json schema generation result with some hash without any changes in dry-schema. And then I started thinking about how to avoid duplication of field list. But of course, describing annotations in the schema itself isn’t quite the SRP and universal way. And at the same time it would be nice to have a way to describe for annotations in the gem itself.
Maybe I’ll find time to make a PR, but I won’t promise yet :slightly_smiling_face: