easy_talk 0.2.0 → 0.2.2

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 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