Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded object #14

Merged
merged 3 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
/spec/reports/
/tmp/
easy_talk*.gem
Gemfile.lock
.ruby-version


# rspec failure tracking
Expand Down
120 changes: 0 additions & 120 deletions Gemfile.lock

This file was deleted.

26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ class User
title "User"
description "A user of the system"
property :name, String, description: "The user's name", title: "Full Name"
property :email, String, description: "The user's email", format: "email", title: "Email Address"
property :email, :object do
property :address, String, format: "email", description: "The user's email", title: "Email Address"
property :verified, T::Boolean, description: "Whether the email is verified"
end
property :group, String, enum: [1, 2, 3], default: 1, description: "The user's group"
property :age, Integer, minimum: 18, maximum: 100, description: "The user's age"
property :tags, T::Array[String], min_items: 1, unique_item: true, description: "The user's tags"
Expand All @@ -34,10 +37,23 @@ Calling `User.json_schema` will return the JSON Schema for the User class:
"type": "string"
},
"email": {
"title": "Email Address",
"description": "The user's email",
"type": "string",
"format": "email"
"type": "object",
"properties": {
"address": {
"title": "Email Address",
"description": "The user's email",
"type": "string",
"format": "email"
},
"verified": {
"type": "boolean",
"description": "Whether the email is verified"
}
},
"required": [
"address",
"verified"
]
},
"group": {
"type": "number",
Expand Down
62 changes: 39 additions & 23 deletions lib/easy_talk/builders/object_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ObjectBuilder < BaseBuilder
attr_reader :schema

VALID_OPTIONS = {
properties: { type: T::Hash[Symbol, T.untyped], key: :properties },
properties: { type: T::Hash[T.any(Symbol, String), T.untyped], key: :properties },
additional_properties: { type: T::Boolean, key: :additionalProperties },
subschemas: { type: T::Array[T.untyped], key: :subschemas },
required: { type: T::Array[Symbol], key: :required },
Expand All @@ -33,42 +33,58 @@ def initialize(schema_definition)

private

def properties_from_schema_definition(properties)
def properties_from_schema_definition
properties = schema.delete(:properties) || {}
properties.each_with_object({}) do |(property_name, options), context|
@required_properties << property_name unless options[:type].respond_to?(:nilable?) && options[:type].nilable?
context[property_name] = Property.new(property_name, options[:type], options[:constraints])
add_required_property(property_name, options)
context[property_name] = build_property(property_name, options)
end
end

def subschemas_from_schema_definition(subschemas)
def add_required_property(property_name, options)
return unless options.is_a?(Hash) && !(options[:type].respond_to?(:nilable?) && options[:type].nilable?)

@required_properties << property_name
end

def build_property(property_name, options)
if options.is_a?(EasyTalk::SchemaDefinition)
ObjectBuilder.new(options).build
else
Property.new(property_name, options[:type], options[:constraints])
end
end

def subschemas_from_schema_definition
subschemas = schema.delete(:subschemas) || []
subschemas.each do |subschema|
definitions = subschema.items.each_with_object({}) do |item, hash|
hash[item.name] = item.schema
end
schema[:defs] = definitions
references = subschema.items.map do |item|
{ '$ref': item.ref_template }
end
schema[subschema.name] = references
add_definitions(subschema)
add_references(subschema)
end
end

def add_definitions(subschema)
definitions = subschema.items.each_with_object({}) do |item, hash|
hash[item.name] = item.schema
end
schema[:defs] = definitions
end

def add_references(subschema)
references = subschema.items.map do |item|
{ '$ref': item.ref_template }
end
schema[subschema.name] = references
end

def options
subschemas_from_schema_definition(subschemas)
@options = schema
@options[:properties] = properties_from_schema_definition(properties)
subschemas_from_schema_definition
@options[:properties] = properties_from_schema_definition
@options[:required] = @required_properties
@options.reject! { |_key, value| [nil, [], {}].include?(value) }
@options
end

def properties
schema.delete(:properties) || {}
end

def subschemas
schema.delete(:subschemas) || []
end
end
end
end
15 changes: 12 additions & 3 deletions lib/easy_talk/schema_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,19 @@ def compose(*subschemas)
@schema[:subschemas] += subschemas
end

sig { params(name: Symbol, type: T.untyped, constraints: T.untyped).void }
def property(name, type, **constraints)
sig do
params(name: T.any(Symbol, String), type: T.untyped, constraints: T.untyped, blk: T.nilable(T.proc.void)).void
end
def property(name, type, **constraints, &blk)
@schema[:properties] ||= {}
@schema[:properties][name] = { type:, constraints: }

if block_given?
property_schema = SchemaDefinition.new(name)
property_schema.instance_eval(&blk)
@schema[:properties][name] = property_schema
else
@schema[:properties][name] = { type:, constraints: }
end
end
end
end
1 change: 1 addition & 0 deletions spec/easy_talk/examples/payment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def self.name
property :PaymentMethod, String, enum: %w[CreditCard Paypal BankTransfer]
property :Details, T::AnyOf[CreditCard, Paypal, BankTransfer]
end

expect(Payment.json_schema).to include_json(expected_json_schema)
end
end
Loading
Loading