ruby_llm-schema 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f2af8bd6f95696f6f8ab70e9d83637772bc1cdd3bf6fea283d2d7389a31a156e
4
+ data.tar.gz: ae39967ea90ec3a189eb0399bcc58da9d3da33d94093d98934bdda71485d70b0
5
+ SHA512:
6
+ metadata.gz: 5d5903e14886b65431d92dfd511a61db51a824b03da19caf6fe062d1384ae12f4ec1a5004f2b1d2974ed1b32da8b91ca45243c5b299014b4a845828d4722506f
7
+ data.tar.gz: bb501057362585d42a11abfdc4de38a7743b8944699fcfd24e234655f478d6999c49c3d2659a7538e89d279a56f7dfea51e1577694f1941b8b97fbfa65758a44
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --format documentation
data/.rspec_status ADDED
@@ -0,0 +1,55 @@
1
+ example_id | status | run_time |
2
+ --------------------------------------------------------- | ------ | --------------- |
3
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:1:1] | passed | 0.0002 seconds |
4
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:1:2] | passed | 0.00004 seconds |
5
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:2:1] | passed | 0.00003 seconds |
6
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:2:2] | passed | 0.00027 seconds |
7
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:3:1] | passed | 0.00004 seconds |
8
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:3:2] | passed | 0.00003 seconds |
9
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:4:1] | passed | 0.00003 seconds |
10
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:1:4:2] | passed | 0.00003 seconds |
11
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:2:1] | passed | 0.00044 seconds |
12
+ ./spec/ruby_llm/schema_class_inheritance_spec.rb[1:3:1] | passed | 0.00058 seconds |
13
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:1:1] | passed | 0.00004 seconds |
14
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:1:2] | passed | 0.00003 seconds |
15
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:2:1] | passed | 0.00004 seconds |
16
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:2:2] | passed | 0.00003 seconds |
17
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:3:1] | passed | 0.00003 seconds |
18
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:3:2] | passed | 0.00003 seconds |
19
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:4:1] | passed | 0.00003 seconds |
20
+ ./spec/ruby_llm/schema_factory_spec.rb[1:1:4:2] | passed | 0.00003 seconds |
21
+ ./spec/ruby_llm/schema_factory_spec.rb[1:2:1] | passed | 0.00005 seconds |
22
+ ./spec/ruby_llm/schema_factory_spec.rb[1:3:1] | passed | 0.00004 seconds |
23
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:1:1] | passed | 0.00004 seconds |
24
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:1:2] | passed | 0.00003 seconds |
25
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:2:1] | passed | 0.00003 seconds |
26
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:2:2] | passed | 0.00003 seconds |
27
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:3:1] | passed | 0.00003 seconds |
28
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:3:2] | passed | 0.00003 seconds |
29
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:4:1] | passed | 0.00003 seconds |
30
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:1:4:2] | passed | 0.00003 seconds |
31
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:2:1] | passed | 0.00005 seconds |
32
+ ./spec/ruby_llm/schema_helpers_spec.rb[1:3:1] | passed | 0.00003 seconds |
33
+ ./spec/ruby_llm/schema_spec.rb[1:1:1] | passed | 0.00018 seconds |
34
+ ./spec/ruby_llm/schema_spec.rb[1:1:2] | passed | 0.00004 seconds |
35
+ ./spec/ruby_llm/schema_spec.rb[1:1:3] | passed | 0.00003 seconds |
36
+ ./spec/ruby_llm/schema_spec.rb[1:1:4] | passed | 0.00002 seconds |
37
+ ./spec/ruby_llm/schema_spec.rb[1:1:5] | passed | 0.00002 seconds |
38
+ ./spec/ruby_llm/schema_spec.rb[1:1:6] | passed | 0.0006 seconds |
39
+ ./spec/ruby_llm/schema_spec.rb[1:2:1] | passed | 0.00004 seconds |
40
+ ./spec/ruby_llm/schema_spec.rb[1:2:2] | passed | 0.00007 seconds |
41
+ ./spec/ruby_llm/schema_spec.rb[1:2:3] | passed | 0.00004 seconds |
42
+ ./spec/ruby_llm/schema_spec.rb[1:3:1] | passed | 0.00005 seconds |
43
+ ./spec/ruby_llm/schema_spec.rb[1:3:2] | passed | 0.00004 seconds |
44
+ ./spec/ruby_llm/schema_spec.rb[1:3:3] | passed | 0.00004 seconds |
45
+ ./spec/ruby_llm/schema_spec.rb[1:4:1] | passed | 0.00006 seconds |
46
+ ./spec/ruby_llm/schema_spec.rb[1:5:1] | passed | 0.00003 seconds |
47
+ ./spec/ruby_llm/schema_spec.rb[1:5:2] | passed | 0.00057 seconds |
48
+ ./spec/ruby_llm/schema_spec.rb[1:5:3] | passed | 0.00023 seconds |
49
+ ./spec/ruby_llm/schema_spec.rb[1:6:1] | passed | 0.00038 seconds |
50
+ ./spec/ruby_llm/schema_spec.rb[1:6:2] | passed | 0.00003 seconds |
51
+ ./spec/ruby_llm/schema_spec.rb[1:7:1:1] | passed | 0.00021 seconds |
52
+ ./spec/ruby_llm/schema_spec.rb[1:7:1:2] | passed | 0.00006 seconds |
53
+ ./spec/ruby_llm/schema_spec.rb[1:7:2:1] | passed | 0.00005 seconds |
54
+ ./spec/ruby_llm/schema_spec.rb[1:8:1] | passed | 0.00044 seconds |
55
+ ./spec/ruby_llm/schema_spec.rb[1:8:2] | passed | 0.00008 seconds |
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Daniel Friis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # RubyLLM::Schema
2
+
3
+ A Ruby DSL for creating JSON schemas with a clean, Rails-inspired API. Perfect for defining structured data schemas for LLM function calling or structured outputs.
4
+
5
+ ### Simple Example
6
+
7
+ ```ruby
8
+ class PersonSchema < RubyLLM::Schema
9
+ string :name, description: "Person's full name"
10
+ number :age, description: "Age in years"
11
+ boolean :active, required: false
12
+
13
+ object :address do
14
+ string :street
15
+ string :city
16
+ string :country, required: false
17
+ end
18
+
19
+ array :tags, of: :string, description: "User tags"
20
+
21
+ array :contacts do
22
+ object do
23
+ string :email
24
+ string :phone, required: false
25
+ end
26
+ end
27
+
28
+ any_of :status do
29
+ string enum: ["active", "pending", "inactive"]
30
+ null
31
+ end
32
+ end
33
+
34
+ # Usage
35
+ schema = PersonSchema.new
36
+ puts schema.to_json
37
+ ```
38
+
39
+ ## Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ ```ruby
44
+ gem 'ruby_llm-schema'
45
+ ```
46
+
47
+ And then execute:
48
+
49
+ ```bash
50
+ bundle install
51
+ ```
52
+
53
+ Or install it yourself as:
54
+
55
+ ```bash
56
+ gem install ruby_llm-schema
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ Three approaches for creating schemas:
62
+
63
+ ### Class Inheritance
64
+
65
+ ```ruby
66
+ class PersonSchema < RubyLLM::Schema
67
+ string :name, description: "Person's full name"
68
+ number :age
69
+ boolean :active, required: false
70
+
71
+ object :address do
72
+ string :street
73
+ string :city
74
+ end
75
+
76
+ array :tags, of: :string
77
+ end
78
+
79
+ schema = PersonSchema.new
80
+ puts schema.to_json
81
+ ```
82
+
83
+ ### Factory Method
84
+
85
+ ```ruby
86
+ PersonSchema = RubyLLM::Schema.create do
87
+ string :name, description: "Person's full name"
88
+ number :age
89
+ boolean :active, required: false
90
+
91
+ object :address do
92
+ string :street
93
+ string :city
94
+ end
95
+
96
+ array :tags, of: :string
97
+ end
98
+
99
+ schema = PersonSchema.new
100
+ puts schema.to_json
101
+ ```
102
+
103
+ ### Global Helper
104
+
105
+ ```ruby
106
+ require 'ruby_llm/schema'
107
+ include RubyLLM::Helpers
108
+
109
+ person_schema = schema "PersonData", description: "A person object" do
110
+ string :name, description: "Person's full name"
111
+ number :age
112
+ boolean :active, required: false
113
+
114
+ object :address do
115
+ string :street
116
+ string :city
117
+ end
118
+
119
+ array :tags, of: :string
120
+ end
121
+
122
+ puts person_schema.to_json
123
+ ```
124
+
125
+ ## Field Types
126
+
127
+ ### Primitive Types
128
+
129
+ ```ruby
130
+ string :name # Required string
131
+ string :title, required: false # Optional string
132
+ string :status, enum: ["on", "off"] # String with enum values
133
+ number :count # Required number
134
+ integer :id # Required integer
135
+ boolean :active # Required boolean
136
+ null :placeholder # Null type
137
+ ```
138
+
139
+ ### Arrays
140
+
141
+ ```ruby
142
+ array :tags, of: :string # Array of strings
143
+ array :scores, of: :number # Array of numbers
144
+
145
+ array :items do # Array of objects
146
+ object do
147
+ string :name
148
+ number :price
149
+ end
150
+ end
151
+ ```
152
+
153
+ ### Objects
154
+
155
+ ```ruby
156
+ object :user do
157
+ string :name
158
+ number :age
159
+ end
160
+
161
+ object :settings, description: "User preferences" do
162
+ boolean :notifications
163
+ string :theme, enum: ["light", "dark"]
164
+ end
165
+ ```
166
+
167
+ ### Union Types (anyOf)
168
+
169
+ ```ruby
170
+ any_of :value do
171
+ string
172
+ number
173
+ null
174
+ end
175
+
176
+ any_of :identifier do
177
+ string description: "Username"
178
+ number description: "User ID"
179
+ end
180
+ ```
181
+
182
+ ### Schema Definitions and References
183
+
184
+ ```ruby
185
+ class MySchema < RubyLLM::Schema
186
+ define :location do
187
+ string :latitude
188
+ string :longitude
189
+ end
190
+
191
+ array :coordinates, of: :location
192
+
193
+ object :home_location do
194
+ reference :location
195
+ end
196
+ end
197
+ ```
198
+
199
+ ## JSON Output
200
+
201
+ ```ruby
202
+ schema = PersonSchema.new
203
+ schema.to_json_schema
204
+ # => {
205
+ # name: "PersonSchema",
206
+ # description: nil,
207
+ # schema: {
208
+ # type: "object",
209
+ # properties: { ... },
210
+ # required: [...],
211
+ # additionalProperties: false,
212
+ # strict: true
213
+ # }
214
+ # }
215
+
216
+ puts schema.to_json # Pretty JSON string
217
+ ```
218
+
219
+ ## License
220
+
221
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "standard/rake"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ class Schema
5
+ # Base error class for all schema-related errors
6
+ class Error < StandardError; end
7
+
8
+ # Raised when an invalid schema type is specified
9
+ class InvalidSchemaTypeError < Error
10
+ def initialize(type)
11
+ super("Unknown schema type: #{type}")
12
+ end
13
+ end
14
+
15
+ # Raised when an invalid array type is specified
16
+ class InvalidArrayTypeError < Error
17
+ def initialize(type)
18
+ super("Invalid array type: #{type}")
19
+ end
20
+ end
21
+
22
+ # Raised when schema definition is invalid
23
+ class InvalidSchemaError < Error; end
24
+
25
+ # Raised when schema validation fails
26
+ class ValidationError < Error; end
27
+
28
+ # Raised when maximum limits are exceeded
29
+ class LimitExceededError < Error; end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Helpers
5
+ def schema(name = nil, description: nil, &block)
6
+ schema_class = Schema.create(&block)
7
+ schema_class.new(name, description: description)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ class Schema
5
+ class PropertySchemaCollector
6
+ attr_reader :schemas
7
+
8
+ def initialize
9
+ @schemas = []
10
+ end
11
+
12
+ def collect(&)
13
+ instance_eval(&)
14
+ end
15
+
16
+ def string(**options)
17
+ @schemas << Schema.build_property_schema(:string, **options)
18
+ end
19
+
20
+ def number(**options)
21
+ @schemas << Schema.build_property_schema(:number, **options)
22
+ end
23
+
24
+ def integer(**options)
25
+ @schemas << Schema.build_property_schema(:integer, **options)
26
+ end
27
+
28
+ def boolean(**options)
29
+ @schemas << Schema.build_property_schema(:boolean, **options)
30
+ end
31
+
32
+ def null(**options)
33
+ @schemas << Schema.build_property_schema(:null, **options)
34
+ end
35
+
36
+ def object(...)
37
+ @schemas << Schema.build_property_schema(:object, ...)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ class Schema
5
+ class Validator
6
+ # Node states for DFS-based topological sort
7
+ WHITE = :white # No mark (unvisited)
8
+ GRAY = :gray # Temporary mark (currently being processed)
9
+ BLACK = :black # Permanent mark (completely processed)
10
+
11
+ def initialize(schema_class)
12
+ @schema_class = schema_class
13
+ end
14
+
15
+ def validate!
16
+ validate_circular_references!
17
+ # Future validations can be added here
18
+ end
19
+
20
+ def valid?
21
+ validate!
22
+ true
23
+ rescue ValidationError
24
+ false
25
+ end
26
+
27
+ private
28
+
29
+ def validate_circular_references!
30
+ definitions = @schema_class.definitions
31
+ return if definitions.empty?
32
+
33
+ # Initialize all nodes as WHITE (no mark)
34
+ marks = Hash.new { WHITE }
35
+
36
+ # Visit each unmarked node
37
+ definitions.each_key do |node|
38
+ visit(node, definitions, marks) if marks[node] == WHITE
39
+ end
40
+ end
41
+
42
+ # DFS visit function
43
+ def visit(node, definitions, marks)
44
+ # If node has a permanent mark, return
45
+ return if marks[node] == BLACK
46
+
47
+ # If node has a temporary mark, we found a cycle
48
+ if marks[node] == GRAY
49
+ raise ValidationError, "Circular reference detected involving '#{node}'"
50
+ end
51
+
52
+ # Mark node with temporary mark
53
+ marks[node] = GRAY
54
+
55
+ # Visit all adjacent nodes (dependencies)
56
+ definition = definitions[node]
57
+ if definition && definition[:properties]
58
+ definition[:properties].each_value do |property|
59
+ references = extract_references(property)
60
+ references.each do |adjacent_node|
61
+ visit(adjacent_node, definitions, marks)
62
+ end
63
+ end
64
+ end
65
+
66
+ # Mark node with permanent mark
67
+ marks[node] = BLACK
68
+ end
69
+
70
+ def extract_references(property)
71
+ references = []
72
+
73
+ case property
74
+ when Hash
75
+ if property["$ref"]
76
+ # Extract definition name from reference like "#/$defs/user"
77
+ ref_name = property["$ref"].split("/").last&.to_sym
78
+ references << ref_name if ref_name
79
+ else
80
+ # Recursively check nested properties
81
+ property.each_value do |value|
82
+ references.concat(extract_references(value))
83
+ end
84
+ end
85
+ when Array
86
+ property.each do |item|
87
+ references.concat(extract_references(item))
88
+ end
89
+ end
90
+
91
+ references
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLlm
4
+ class Schema
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "schema/version"
4
+ require_relative "schema/property_schema_collector"
5
+ require_relative "schema/errors"
6
+ require_relative "schema/helpers"
7
+ require_relative "schema/validator"
8
+ require "json"
9
+ require "set"
10
+
11
+ module RubyLLM
12
+ class Schema
13
+ PRIMITIVE_TYPES = %i[string number integer boolean null].freeze
14
+
15
+ class << self
16
+ def create(&block)
17
+ schema_class = Class.new(Schema)
18
+ schema_class.class_eval(&block)
19
+ schema_class
20
+ end
21
+
22
+ def description(description = nil)
23
+ @description = description if description
24
+ @description
25
+ end
26
+
27
+ def additional_properties(value = nil)
28
+ return @additional_properties ||= false if value.nil?
29
+ @additional_properties = value
30
+ end
31
+
32
+ def strict(value = nil)
33
+ return @strict ||= true if value.nil?
34
+ @strict = value
35
+ end
36
+
37
+ def string(name = nil, enum: nil, description: nil, required: true)
38
+ add_property(name, build_property_schema(:string, enum: enum, description: description), required: required)
39
+ end
40
+
41
+ def number(name = nil, description: nil, required: true)
42
+ add_property(name, build_property_schema(:number, description: description), required: required)
43
+ end
44
+
45
+ def integer(name = nil, description: nil, required: true)
46
+ add_property(name, build_property_schema(:integer, description: description), required: required)
47
+ end
48
+
49
+ def boolean(name = nil, description: nil, required: true)
50
+ add_property(name, build_property_schema(:boolean, description: description), required: required)
51
+ end
52
+
53
+ def null(name = nil, description: nil, required: true)
54
+ add_property(name, build_property_schema(:null, description: description), required: required)
55
+ end
56
+
57
+ def object(name = nil, description: nil, required: true, &block)
58
+ add_property(name, build_property_schema(:object, description: description, &block), required: required)
59
+ end
60
+
61
+ def array(name, of: nil, description: nil, required: true, &block)
62
+ items = determine_array_items(of, &block)
63
+
64
+ add_property(name, {
65
+ type: "array",
66
+ description: description,
67
+ items: items
68
+ }.compact, required: required)
69
+ end
70
+
71
+ def any_of(name, required: true, description: nil, &block)
72
+ schemas = collect_property_schemas_from_block(&block)
73
+
74
+ add_property(name, {
75
+ description: description,
76
+ anyOf: schemas
77
+ }.compact, required: required)
78
+ end
79
+
80
+ def define(name, &)
81
+ sub_schema = Class.new(Schema)
82
+ sub_schema.class_eval(&)
83
+
84
+ definitions[name] = {
85
+ type: "object",
86
+ properties: sub_schema.properties,
87
+ required: sub_schema.required_properties
88
+ }
89
+ end
90
+
91
+ def reference(schema_name)
92
+ {"$ref" => "#/$defs/#{schema_name}"}
93
+ end
94
+
95
+ def properties
96
+ @properties ||= {}
97
+ end
98
+
99
+ def required_properties
100
+ @required_properties ||= []
101
+ end
102
+
103
+ def definitions
104
+ @definitions ||= {}
105
+ end
106
+
107
+ def build_property_schema(type, **options, &)
108
+ case type
109
+ when :string
110
+ {type: "string", enum: options[:enum], description: options[:description]}.compact
111
+ when :number
112
+ {type: "number", description: options[:description]}.compact
113
+ when :integer
114
+ {type: "integer", description: options[:description]}.compact
115
+ when :boolean
116
+ {type: "boolean", description: options[:description]}.compact
117
+ when :null
118
+ {type: "null", description: options[:description]}.compact
119
+ when :object
120
+ sub_schema = Class.new(Schema)
121
+ sub_schema.class_eval(&)
122
+
123
+ {
124
+ type: "object",
125
+ properties: sub_schema.properties,
126
+ required: sub_schema.required_properties,
127
+ additionalProperties: additional_properties,
128
+ description: options[:description]
129
+ }.compact
130
+ else
131
+ raise InvalidSchemaTypeError, type
132
+ end
133
+ end
134
+
135
+ def validate!
136
+ validator = Validator.new(self)
137
+ validator.validate!
138
+ end
139
+
140
+ def valid?
141
+ validator = Validator.new(self)
142
+ validator.valid?
143
+ end
144
+
145
+ private
146
+
147
+ def add_property(name, definition, required:)
148
+ properties[name.to_sym] = definition
149
+ required_properties << name.to_sym if required
150
+ end
151
+
152
+ def determine_array_items(of, &)
153
+ return collect_property_schemas_from_block(&).first if block_given?
154
+ return build_property_schema(of) if primitive_type?(of)
155
+ return reference(of) if of.is_a?(Symbol)
156
+
157
+ raise InvalidArrayTypeError, of
158
+ end
159
+
160
+ def collect_property_schemas_from_block(&)
161
+ collector = PropertySchemaCollector.new
162
+ collector.collect(&)
163
+ collector.schemas
164
+ end
165
+
166
+ def primitive_type?(type)
167
+ type.is_a?(Symbol) && PRIMITIVE_TYPES.include?(type)
168
+ end
169
+ end
170
+
171
+ def initialize(name = nil, description: nil)
172
+ @name = name || self.class.name || "Schema"
173
+ @description = description
174
+ end
175
+
176
+ def to_json_schema
177
+ validate! # Validate schema before generating JSON
178
+
179
+ {
180
+ name: @name,
181
+ description: @description,
182
+ schema: {
183
+ :type => "object",
184
+ :properties => self.class.properties,
185
+ :required => self.class.required_properties,
186
+ :additionalProperties => self.class.additional_properties,
187
+ :strict => self.class.strict,
188
+ "$defs" => self.class.definitions
189
+ }
190
+ }
191
+ end
192
+
193
+ def to_json(*_args)
194
+ validate! # Validate schema before generating JSON string
195
+ JSON.pretty_generate(to_json_schema)
196
+ end
197
+
198
+ def validate!
199
+ self.class.validate!
200
+ end
201
+
202
+ def valid?
203
+ self.class.valid?
204
+ end
205
+
206
+ def method_missing(method_name, ...)
207
+ if respond_to_missing?(method_name)
208
+ send(method_name, ...)
209
+ else
210
+ super
211
+ end
212
+ end
213
+
214
+ def respond_to_missing?(method_name, include_private = false)
215
+ %i[string number integer boolean array object any_of null].include?(method_name) || super
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,13 @@
1
+ namespace :release do
2
+ desc "Release a new version of the gem"
3
+ task :version do
4
+ puts "Enter the new version: "
5
+ version = gets.chomp
6
+ system "git tag v#{version}"
7
+ system "git push origin v#{version}"
8
+
9
+ system "gem build ruby_llm-schema.gemspec"
10
+ system "gem push ruby_llm-schema-#{version}.gem"
11
+ system "rm ruby_llm-schema-#{version}.gem"
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_llm-schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Friis
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-06-16 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: standard
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ email:
41
+ - d@friis.me
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - ".rspec"
47
+ - ".rspec_status"
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - lib/ruby_llm/schema.rb
52
+ - lib/ruby_llm/schema/errors.rb
53
+ - lib/ruby_llm/schema/helpers.rb
54
+ - lib/ruby_llm/schema/property_schema_collector.rb
55
+ - lib/ruby_llm/schema/validator.rb
56
+ - lib/ruby_llm/schema/version.rb
57
+ - lib/tasks/release.rake
58
+ homepage: https://github.com/danielfriis/ruby_llm-schema
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://github.com/danielfriis/ruby_llm-schema
63
+ source_code_uri: https://github.com/danielfriis/ruby_llm-schema
64
+ changelog_uri: https://github.com/danielfriis/ruby_llm-schema/blob/main/CHANGELOG.md
65
+ rubygems_mfa_required: 'true'
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 3.1.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.6.2
81
+ specification_version: 4
82
+ summary: A simple and clean Ruby DSL for creating JSON schemas.
83
+ test_files: []