Skip to content

Add description support for schema keys#501

Open
baweaver wants to merge 11 commits into
dry-rb:mainfrom
baweaver:baweaver/add-description
Open

Add description support for schema keys#501
baweaver wants to merge 11 commits into
dry-rb:mainfrom
baweaver:baweaver/add-description

Conversation

@baweaver

Copy link
Copy Markdown
Contributor

Adds a .description(text) method to schema key definitions that stores descriptions in type
metadata and includes them in JSON schema output.

Usage

schema = Dry::Schema.define do
  required(:first_name).filled(:string).description("First name of the user")
  optional(:age).filled(:integer).description("Age of the user")
  required(:address).description("The shipping address").hash do
    required(:street).filled(:string).description("Street address")
    optional(:city).filled(:string).description("City name")
  end
end

# Access via type metadata
schema.types[:first_name].meta[:description]
# => "First name of the user"

# Included in JSON schema output
Dry::Schema.load_extensions(:json_schema)
schema.json_schema[:properties][:first_name][:description]
# => "First name of the user"

Implementation

• Descriptions stored in type metadata (:description key)
• Works with all macro types (required, optional, filled, maybe, hash, array, etc.)
• Preserved when types are replaced during macro chaining
• JSON schema compiler extracts descriptions from type metadata for both top-level and nested
keys
• Improved set_type to preserve all incoming type metadata, not just schema-specific meta

Changes

lib/dry/schema/macros/dsl.rb - Added description method
lib/dry/schema/dsl.rb - Updated set_type to preserve type metadata
lib/dry/schema/extensions/json_schema.rb - Pass types to compiler
lib/dry/schema/extensions/json_schema/schema_compiler.rb - Extract and include descriptions
spec/integration/schema/description_spec.rb - Test coverage

Backwards compatible with existing schemas.

- Add description() method to Macros::DSL for setting field descriptions
- Store descriptions in type metadata
- Preserve descriptions when types are replaced during macro chaining
- Include descriptions in JSON schema output via json_schema extension
- Support nested schema descriptions
Comment thread lib/dry/schema/dsl.rb Outdated
Comment thread lib/dry/schema/dsl.rb Outdated
# @api public
def json_schema(loose: false)
compiler = SchemaCompiler.new(root: true, loose: loose)
compiler = SchemaCompiler.new(root: true, loose: loose, types: types)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to pipe types down as those are where most of the meta / description info lives.

Comment thread lib/dry/schema/extensions/json_schema/schema_compiler.rb Outdated
Comment thread lib/dry/schema/macros/dsl.rb Outdated
- Simplify description method in macros/dsl.rb
- Extract current_meta variable in json_schema compiler
- Reformat visit_set method for clarity

@timriley timriley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good to me! Thank you @baweaver.

I've left just one question for you, which I think would be good to address before merging.

@flash-gordon Do you have any thoughts about this one?

Comment thread lib/dry/schema/dsl.rb Outdated

current_meta = @types[name]&.meta

new_meta[:description] ||= current_meta[:description] if current_meta&.key?(:description)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get why this is here. It's so you can have a line like this, where the real type for the key doesn't get set until after the description is given:

required(:first_name).description("First name of the user").filled(:string)

This is a good thing! But it's also the only special-cased line in this method.

It feels like it at least warrants a comment explaining why it is so. Reckon you could add one?

(In fact, I wonder if there is a way to generalise it?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified and generalized to merge a wider range of metadata, added comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @baweaver, that’s very nice!

- Extract metadata preservation logic into dedicated method
- Preserve all user-defined metadata (not just description)
- Clearly separate system-managed keys from user-defined keys
- Add comprehensive documentation explaining fluent API enablement
- All tests passing (3226 examples, 0 failures)
@flash-gordon

Copy link
Copy Markdown
Member

This looks like a reasonable improvement of the DSL. Should we, though, add #description in the JSON schema extension rather than the general DSL? The fact that you can call .description, which does nothing when json_schema is not loaded can be confusing

@stefanoc

Copy link
Copy Markdown

Hey what's the status here? I was just looking for this but it seems it was abandoned 😢
I have a hand-written Zod schema in our Typescript codebase that I'd really like to auto-generate from a Dry::Schema since it is actually more the responsibility of the Ruby codebase, and I really don't want to have to do it the other way around since I like Ruby but Typescript... not so much :-)

@timriley

Copy link
Copy Markdown
Member

Thanks for chiming in here, @stefanoc! I'm keen to see this go in. Let's use your use case as input to the design. If you were to generate your Zod schemas, is that happening via JSON Schema-formatted JSON, or would you take some other approach?

@stefanoc

Copy link
Copy Markdown

Zod natively supports JSON Schema since 4.0 so that would be the easiest route, but it's still flagged as experimental so who knows... ideally I'd like to go with a direct Ruby-to-Typescript conversion (so I guess something like the JSON schema extension, but which would emit a string instead of a Hash, if that makes sense).

@rolftimmermans

Copy link
Copy Markdown

I'm also curious about the status. I'm currently using dry-struct for structured outputs from LLMs, but write the JSON schema definitions manually. That's duplication I prefer to avoid.

It would be ideal to define the schema with dry-schema, but not having the ability to include a description is a blocker. I'm hopeful this feature will be considered... :)

In practice the descriptions can be pretty long multi-line strings though. A fluent API might not be super ergonomical (although definitely better than not having the feature at all).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants