easy_talk 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8455d741b05a1a65ca7ea3f7e2d529271414fb04881066cc81bbb444399e200c
4
- data.tar.gz: 9814e429530dbb42da5a3cca3310175ebe98038d91d5b7deeddcd68aa90b3199
3
+ metadata.gz: d4dbef0c74efa9996294e1aa20f5beef89d7d418df8be196cd794ece88c5ec67
4
+ data.tar.gz: 79b1c90879f26967e94e56317fff93e39bc1735e41a5c5975a3a922e556c2291
5
5
  SHA512:
6
- metadata.gz: 5a85e59b3eaa7e0e9c2d57eb0283f2b4a45e6dfeb88778682738969617f751149aaf99cd93b4760c323eb84da476ac0ee8aba59eef003156d8d82aae60a940fd
7
- data.tar.gz: 0c382d7989016e5a1c376439c96cd8b1d4acb7a6758a8fb2b406239551445ede641fb3a3ed64f833ce591409a75eefb0f3023a4a60cfffc92ca49ee5827bc24d
6
+ metadata.gz: 053a079d3b233f70bd62f63708c008b2da133d2210dfdde206708a588387ee844cab5e8409a5ac7afc635ccb909dbfd82b952e6608ec79333781f2278b9c56ce
7
+ data.tar.gz: 9af64bd32362f6518c2576aa793047b4252e47b18a92e0edfae9b937c92d56c63ba0582146ddc86823c0cacd98ead9e4ecb83f01624e22ad93cf58d36b42c4d0
data/.rubocop.yml CHANGED
@@ -27,4 +27,7 @@ Layout/LineLength:
27
27
 
28
28
  RSpec/DescribeClass:
29
29
  Exclude:
30
- - 'spec/easy_talk/examples/**/*'
30
+ - 'spec/easy_talk/examples/**/*'
31
+
32
+ RSpec/MultipleExpectations:
33
+ Max: 4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.2.2] - 2024-05-17
2
+ - Fixed a bug where optional properties were not excluded from the required list.
3
+
4
+ ## [0.2.1] - 2024-05-06
5
+ - Run JSON Schema validations using ActiveModel's validations.
6
+
1
7
  ## [0.2.0] - 2024-05-01
2
8
  - Added ActiveModel::API functionality to EasyTalk::Model module. That means you get all the benefits of ActiveModel::API including attribute assignment, introspections, validations, translation (i18n) and more. See https://api.rubyonrails.org/classes/ActiveModel/API.html for more information.
3
9
 
data/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # EasyTalk
2
2
 
3
- EasyTalk is a Ruby library for defining and generating JSON Schema.
3
+ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema documents, and validates that JSON data conforms to these schemas.
4
+
5
+ Key Features
6
+ * Intuitive Schema Definition: Use Ruby classes and methods to define JSON Schema documents easily.
7
+ * JSON Schema Compliance: Implements the JSON Schema specification to ensure compatibility and standards adherence.
8
+ * LLM Function Support: Ideal for integrating with Large Language Models (LLMs) such as OpenAI's GPT-3.5-turbo and GPT-4. EasyTalk enables you to effortlessly create JSON Schema documents needed to describe the inputs and outputs of LLM function calls.
9
+ * Validation: Validates JSON inputs and outputs against defined schemas to ensure they meet expected formats and types. Write custom validations using ActiveModel's validations.
10
+ * Integration with ActiveModel: EasyTalk integrates with ActiveModel to provide additional functionality such as attribute assignment, introspections, validations, translation (i18n), and more.
11
+
12
+ Inspiration
13
+ Inspired by Python's Pydantic library, EasyTalk brings similar functionality to the Ruby ecosystem, providing a Ruby-friendly approach to JSON Schema operations.
4
14
 
5
15
  Example Use:
6
16
 
data/docs/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ _site
2
+ .sass-cache
3
+ .jekyll-cache
4
+ .jekyll-metadata
5
+ vendor
data/docs/404.html ADDED
@@ -0,0 +1,25 @@
1
+ ---
2
+ permalink: /404.html
3
+ layout: default
4
+ ---
5
+
6
+ <style type="text/css" media="screen">
7
+ .container {
8
+ margin: 10px auto;
9
+ max-width: 600px;
10
+ text-align: center;
11
+ }
12
+ h1 {
13
+ margin: 30px 0;
14
+ font-size: 4em;
15
+ line-height: 1;
16
+ letter-spacing: -1px;
17
+ }
18
+ </style>
19
+
20
+ <div class="container">
21
+ <h1>404</h1>
22
+
23
+ <p><strong>Page not found :(</strong></p>
24
+ <p>The requested page could not be found.</p>
25
+ </div>
data/docs/Gemfile ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ # Hello! This is where you manage which Jekyll version is used to run.
5
+ # When you want to use a different version, change it below, save the
6
+ # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
7
+ #
8
+ # bundle exec jekyll serve
9
+ #
10
+ # This will help ensure the proper Jekyll version is running.
11
+ # Happy Jekylling!
12
+ # gem "jekyll", "~> 4.3.3"
13
+ gem 'github-pages', group: :jekyll_plugins
14
+
15
+ gem 'webrick'
16
+ # This is the default theme for new Jekyll sites. You may change this to anything you like.
17
+ gem 'minima', '~> 2.5'
18
+ # If you want to use GitHub Pages, remove the "gem "jekyll"" above and
19
+ # uncomment the line below. To upgrade, run `bundle update github-pages`.
20
+ # gem "github-pages", group: :jekyll_plugins
21
+ # If you have any plugins, put them here!
22
+ group :jekyll_plugins do
23
+ gem 'jekyll-feed', '~> 0.12'
24
+ end
25
+
26
+ # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
27
+ # and associated library.
28
+ platforms :mingw, :x64_mingw, :mswin, :jruby do
29
+ gem 'tzinfo', '>= 1', '< 3'
30
+ gem 'tzinfo-data'
31
+ end
32
+
33
+ # Performance-booster for watching directories on Windows
34
+ gem 'wdm', '~> 0.1.1', platforms: %i[mingw x64_mingw mswin]
35
+
36
+ # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
37
+ # do not have a Java counterpart.
38
+ gem 'http_parser.rb', '~> 0.6.0', platforms: [:jruby]
data/docs/_config.yml ADDED
@@ -0,0 +1,53 @@
1
+ # Welcome to Jekyll!
2
+ #
3
+ # This config file is meant for settings that affect your whole blog, values
4
+ # which you are expected to set up once and rarely edit after that. If you find
5
+ # yourself editing this file very often, consider using Jekyll's data files
6
+ # feature for the data you need to update frequently.
7
+ #
8
+ # For technical reasons, this file is *NOT* reloaded automatically when you use
9
+ # 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10
+ #
11
+ # If you need help with YAML syntax, here are some quick references for you:
12
+ # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
13
+ # https://learnxinyminutes.com/docs/yaml/
14
+ #
15
+ # Site settings
16
+ # These are used to personalize your new site. If you look in the HTML files,
17
+ # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
18
+ # You can create any custom variable you would like, and they will be accessible
19
+ # in the templates via {{ site.myvariable }}.
20
+
21
+ title: EasyTalk
22
+ email: bayona.sergio@gmail.com
23
+ description: >- # this means to ignore newlines until "baseurl:"
24
+ EasyTalk: define and generate JSON Schema documents.
25
+ baseurl: "/easy_talk" # the subpath of your site, e.g. /blog
26
+ url: "https://sergiobayona.github.io" # the base hostname & protocol for your site, e.g. http://example.com
27
+ twitter_username: sergiobayona
28
+ github_username: sergiobayona
29
+
30
+ # Build settings
31
+ theme: minima
32
+ plugins:
33
+ - jekyll-feed
34
+
35
+ # Exclude from processing.
36
+ # The following items will not be processed, by default.
37
+ # Any item listed under the `exclude:` key here will be automatically added to
38
+ # the internal "default list".
39
+ #
40
+ # Excluded items can be processed by explicitly listing the directories or
41
+ # their entries' file path in the `include:` list.
42
+ #
43
+ # exclude:
44
+ # - .sass-cache/
45
+ # - .jekyll-cache/
46
+ # - gemfiles/
47
+ # - Gemfile
48
+ # - Gemfile.lock
49
+ # - node_modules/
50
+ # - vendor/bundle/
51
+ # - vendor/cache/
52
+ # - vendor/gems/
53
+ # - vendor/ruby/
@@ -0,0 +1,29 @@
1
+ ---
2
+ layout: post
3
+ title: "Welcome to Jekyll!"
4
+ date: 2024-05-07 18:39:19 -0500
5
+ categories: jekyll update
6
+ ---
7
+ You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
8
+
9
+ Jekyll requires blog post files to be named according to the following format:
10
+
11
+ `YEAR-MONTH-DAY-title.MARKUP`
12
+
13
+ Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works.
14
+
15
+ Jekyll also offers powerful support for code snippets:
16
+
17
+ {% highlight ruby %}
18
+ def print_hi(name)
19
+ puts "Hi, #{name}"
20
+ end
21
+ print_hi('Tom')
22
+ #=> prints 'Hi, Tom' to STDOUT.
23
+ {% endhighlight %}
24
+
25
+ Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk].
26
+
27
+ [jekyll-docs]: https://jekyllrb.com/docs/home
28
+ [jekyll-gh]: https://github.com/jekyll/jekyll
29
+ [jekyll-talk]: https://talk.jekyllrb.com/
@@ -0,0 +1,18 @@
1
+ ---
2
+ layout: page
3
+ title: About
4
+ permalink: /about/
5
+ ---
6
+
7
+ This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](https://jekyllrb.com/)
8
+
9
+ You can find the source code for Minima at GitHub:
10
+ [jekyll][jekyll-organization] /
11
+ [minima](https://github.com/jekyll/minima)
12
+
13
+ You can find the source code for Jekyll at GitHub:
14
+ [jekyll][jekyll-organization] /
15
+ [jekyll](https://github.com/jekyll/jekyll)
16
+
17
+
18
+ [jekyll-organization]: https://github.com/jekyll
@@ -0,0 +1,7 @@
1
+ ---
2
+ # Feel free to add content and custom Front Matter to this file.
3
+ # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
4
+
5
+ layout: home
6
+ ---
7
+ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema documents, and validates that JSON data conforms to these schemas.
@@ -34,36 +34,49 @@ module EasyTalk
34
34
  private
35
35
 
36
36
  def properties_from_schema_definition
37
- properties = schema.delete(:properties) || {}
38
- properties.each_with_object({}) do |(property_name, options), context|
39
- add_required_property(property_name, options)
40
- context[property_name] = build_property(property_name, options)
37
+ @properties_from_schema_definition ||= begin
38
+ properties = schema.delete(:properties) || {}
39
+ properties.each_with_object({}) do |(property_name, options), context|
40
+ add_required_property(property_name, options)
41
+ context[property_name] = build_property(property_name, options)
42
+ end
41
43
  end
42
44
  end
43
45
 
44
46
  # rubocop:disable Style/DoubleNegation
45
47
  def add_required_property(property_name, options)
46
48
  return if options.is_a?(Hash) && !!(options[:type].respond_to?(:nilable?) && options[:type].nilable?)
47
-
48
49
  return if options.respond_to?(:optional?) && options.optional?
50
+ return if options.is_a?(Hash) && options.dig(:constraints, :optional)
49
51
 
50
52
  @required_properties << property_name
51
53
  end
52
54
  # rubocop:enable Style/DoubleNegation
53
55
 
54
56
  def build_property(property_name, options)
55
- if options.is_a?(EasyTalk::SchemaDefinition)
56
- ObjectBuilder.new(options).build
57
- else
58
- Property.new(property_name, options[:type], options[:constraints])
57
+ @property_cache ||= {}
58
+
59
+ @property_cache[property_name] ||= if options.is_a?(EasyTalk::SchemaDefinition)
60
+ ObjectBuilder.new(options).build
61
+ else
62
+ handle_option_type(options)
63
+ Property.new(property_name, options[:type], options[:constraints])
64
+ end
65
+ end
66
+
67
+ def handle_option_type(options)
68
+ if options[:type].respond_to?(:nilable?) && options[:type].nilable? && options[:type].unwrap_nilable.class != T::Types::TypedArray
69
+ options[:type] = options[:type].unwrap_nilable.raw_type
59
70
  end
60
71
  end
61
72
 
62
73
  def subschemas_from_schema_definition
63
- subschemas = schema.delete(:subschemas) || []
64
- subschemas.each do |subschema|
65
- add_definitions(subschema)
66
- add_references(subschema)
74
+ @subschemas_from_schema_definition ||= begin
75
+ subschemas = schema.delete(:subschemas) || []
76
+ subschemas.each do |subschema|
77
+ add_definitions(subschema)
78
+ add_references(subschema)
79
+ end
67
80
  end
68
81
  end
69
82
 
@@ -7,48 +7,55 @@ require 'active_support/time'
7
7
  require 'active_support/concern'
8
8
  require 'active_support/json'
9
9
  require 'active_model'
10
- require 'json-schema'
10
+ require 'json_schemer'
11
+ require_relative 'schema_errors_mapper'
11
12
  require_relative 'builders/object_builder'
12
13
  require_relative 'schema_definition'
13
14
 
14
15
  module EasyTalk
15
- # The Model module can be included in a class to add JSON schema definition and generation support.
16
+ # The `Model` module is a mixin that provides functionality for defining and accessing the schema of a model.
17
+ #
18
+ # It includes methods for defining the schema, retrieving the schema definition,
19
+ # and generating the JSON schema for the model.
20
+ #
21
+ # Example usage:
22
+ #
23
+ # class Person
24
+ # include EasyTalk::Model
25
+ #
26
+ # define_schema do
27
+ # property :name, String, description: 'The person\'s name'
28
+ # property :age, Integer, description: 'The person\'s age'
29
+ # end
30
+ # end
31
+ #
32
+ # Person.json_schema #=> returns the JSON schema for Person
33
+ # jim = Person.new(name: 'Jim', age: 30)
34
+ # jim.valid? #=> returns true
35
+ #
36
+ # @see SchemaDefinition
16
37
  module Model
17
- # The `Model` module is a mixin that provides functionality for defining and accessing the schema of a model.
18
- #
19
- # It includes methods for defining the schema, retrieving the schema definition,
20
- # and generating the JSON schema for the model.
21
- #
22
- # Example usage:
23
- #
24
- # class Person
25
- # include EasyTalk::Model
26
- #
27
- # define_schema do
28
- # property :name, String, description: 'The person\'s name'
29
- # property :age, Integer, description: 'The person\'s age'
30
- # end
31
- # end
32
- #
33
- # Person.json_schema #=> returns the JSON schema for Person
34
- # jim = Person.new(name: 'Jim', age: 30)
35
- # jim.valid? #=> returns true
36
- #
37
- # @see SchemaDefinition
38
- #
39
38
  def self.included(base)
40
39
  base.include ActiveModel::API # Include ActiveModel::API in the class including EasyTalk::Model
40
+ base.include ActiveModel::Validations
41
+ base.extend ActiveModel::Callbacks
42
+ base.validates_with SchemaValidator
41
43
  base.extend(ClassMethods)
42
44
  end
43
45
 
44
- # Checks if the model is valid.
45
- #
46
- # This method calls the `validate_json` class method on the current class,
47
- # passing the `properties` as the argument.
48
- #
49
- # @return [Boolean] true if the model is valid, false otherwise.
50
- def valid?
51
- self.class.validate_json(properties)
46
+ class SchemaValidator < ActiveModel::Validator
47
+ def validate(record)
48
+ result = schema_validation(record)
49
+ result.errors.each do |key, error_msg|
50
+ record.errors.add key.to_sym, error_msg
51
+ end
52
+ end
53
+
54
+ def schema_validation(record)
55
+ schema = JSONSchemer.schema(record.class.json_schema)
56
+ errors = schema.validate(record.properties)
57
+ SchemaErrorsMapper.new(errors)
58
+ end
52
59
  end
53
60
 
54
61
  # Returns the properties of the model as a hash with symbolized keys.
@@ -94,14 +101,6 @@ module EasyTalk
94
101
  end
95
102
  end
96
103
 
97
- # Validates the given JSON against the model's JSON schema.
98
- #
99
- # @param json [Hash] The JSON to validate.
100
- # @return [Boolean] `true` if the JSON is valid, `false` otherwise.
101
- def validate_json(json)
102
- JSON::Validator.validate(json_schema, json)
103
- end
104
-
105
104
  # Returns the JSON schema for the model.
106
105
  #
107
106
  # @return [Hash] The JSON schema for the model.
@@ -76,10 +76,16 @@ module EasyTalk
76
76
  #
77
77
  # @return [Object] The built property.
78
78
  def build
79
- return type.respond_to?(:schema) ? type.schema : 'object' unless builder
79
+ # return type.respond_to?(:schema) ? type.schema : 'object' unless builder
80
80
 
81
- args = builder.collection_type? ? [name, type, constraints] : [name, constraints]
82
- builder.new(*args).build
81
+ # args = builder.collection_type? ? [name, type, constraints] : [name, constraints]
82
+ # builder.new(*args).build
83
+ if builder
84
+ args = builder.collection_type? ? [name, type, constraints] : [name, constraints]
85
+ builder.new(*args).build
86
+ else
87
+ type.respond_to?(:schema) ? type.schema : 'object'
88
+ end
83
89
  end
84
90
 
85
91
  # Converts the object to a JSON representation.
@@ -97,7 +103,7 @@ module EasyTalk
97
103
  #
98
104
  # @return [Builder] The builder associated with the property type.
99
105
  def builder
100
- TYPE_TO_BUILDER[type.class.name.to_s] || TYPE_TO_BUILDER[type.name.to_s]
106
+ @builder ||= TYPE_TO_BUILDER[type.class.name.to_s] || TYPE_TO_BUILDER[type.name.to_s]
101
107
  end
102
108
  end
103
109
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyTalk
4
+ class SchemaErrorsMapper
5
+ def initialize(errors)
6
+ @errors = errors.to_a
7
+ end
8
+
9
+ def errors
10
+ @errors.each_with_object({}) do |error, hash|
11
+ if error['data_pointer'].present?
12
+ key = error['data_pointer'].split('/').compact_blank.join('.')
13
+ hash[key] = error['error']
14
+ else
15
+ error['details']['missing_keys'].each do |missing_key|
16
+ message = "#{error['error'].split(':').first}: #{missing_key}"
17
+ hash[missing_key] = message
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EasyTalk
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy_talk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergio Bayona
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-01 00:00:00.000000000 Z
11
+ date: 2024-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: json-schema
42
+ name: json_schemer
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '4'
47
+ version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '4'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: sorbet-runtime
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -191,6 +191,13 @@ files:
191
191
  - LICENSE.txt
192
192
  - README.md
193
193
  - Rakefile
194
+ - docs/.gitignore
195
+ - docs/404.html
196
+ - docs/Gemfile
197
+ - docs/_config.yml
198
+ - docs/_posts/2024-05-07-welcome-to-jekyll.markdown
199
+ - docs/about.markdown
200
+ - docs/index.markdown
194
201
  - lib/easy_talk.rb
195
202
  - lib/easy_talk/builders/all_of_builder.rb
196
203
  - lib/easy_talk/builders/any_of_builder.rb
@@ -213,6 +220,7 @@ files:
213
220
  - lib/easy_talk/model.rb
214
221
  - lib/easy_talk/property.rb
215
222
  - lib/easy_talk/schema_definition.rb
223
+ - lib/easy_talk/schema_errors_mapper.rb
216
224
  - lib/easy_talk/sorbet_extension.rb
217
225
  - lib/easy_talk/tools/function_builder.rb
218
226
  - lib/easy_talk/types/all_of.rb