class OpenStax::Api::RepresentableSchemaPrinter
Public Class Methods
definition_name(name)
click to toggle source
Gets the definition name for the given representer name
# File lib/openstax/api/representable_schema_printer.rb, line 36 def self.definition_name(name) "#/definitions/#{name}" end
json(representer, options={})
click to toggle source
Returns some formatted Markdown with HTML containing the JSON schema for a given representer
# File lib/openstax/api/representable_schema_printer.rb, line 15 def self.json(representer, options={}) options[:include] ||= [:readable, :writeable] schema = json_schema(representer, options) json_string = JSON.pretty_generate(schema) "\n## Schema {##{SecureRandom.hex(4)} .schema}\n" + "\n<pre class='code'>\n#{json_string}\n</pre>\n" end
json_object(representer, definitions, options = {})
click to toggle source
Helper function for json_schema
# File lib/openstax/api/representable_schema_printer.rb, line 41 def self.json_object(representer, definitions, options = {}) # Initialize the schema schema = { type: :object, required: [], properties: {}, additionalProperties: false } representer.representable_attrs.each do |attr| # Handle some common attributes (as, schema_info, required) name = attr[:as].blank? ? attr[:name] : attr[:as].evaluate(representer) rescue attr[:as].call rescue attr[:name] schema_info = attr[:schema_info] || {} schema[:required].push(name.to_sym) if schema_info[:required] # Skip attr if it does not have the the specified key set to false # or it is explicitly "required" next unless [options[:include]].flatten.any? do |inc| m = inc.to_s + "?" (attr.respond_to?(m) ? attr.send(m) : attr[inc]) != false end || schema_info[:required] # Guess a default type based on the attribute name type = attr[:type].blank? ? (name.end_with?('id') ? :integer : :string) : attr[:type].to_s.downcase attr_info ||= { type: type } # Process the schema_info attribute schema_info.each do |key, value| # Handle special keys (required, definitions, type, enum) case key when :required next when :definitions definitions.merge!(value) next when :type next if attr_info[:type].nil? when :enum attr_info.delete(:type) end # Handle Procs and Uber::Callables case value when Proc value = representer.instance_exec &value when Uber::Callable value = value.call(representer) end # Store the schema_info k-v pair in attr_info attr_info[key] = value end # Overwrite type for collections attr_info[:type] = 'array' if attr[:collection] # Handle nested representers if attr[:extend] # The nested representer can be either a simple representer or # an Uber::Callable, in which case there could be multiple representers # Get the nested representers # Evaluate syntax is evaluate(context, instance or class, *args) # If we have no instance or class (since we have no fragment), we pass Object # By convention, our callables should return an array of all possible # representers when we pass the all_sub_representers: true option instance = attr[:instance].evaluate(representer, {}) rescue nil klass = attr[:class].evaluate(representer, {}) rescue Object decorators = [attr[:extend].evaluate( representer, instance || klass, all_sub_representers: true )].flatten.compact rescue [] # Count the representers include_oneof = decorators.length > 1 # If we have more than one possible representer, use oneOf to list them all sreps = include_oneof ? { oneOf: [] } : {} decorators.each do |decorator| # Attempt to get the representer's name rname = representer_name(decorator) if rname # We have a representer name, so use a schema definition with that name dname = definition_name(rname) dhash = { :$ref => dname } include_oneof ? sreps[:oneOf].push(dhash) : sreps = dhash if definitions[rname].nil? # No definition with that name found, so add it # A blank definition is added first to prevent infinite loops definitions[rname] = {} definitions[rname] = json_object(decorator, definitions, options) end else # No representer name, so add the object inline instead obj = json_object(decorator, definitions, options) include_oneof ? sreps[:oneOf].push(obj) : sreps = obj end end if attr[:collection] # Collection # Type already set above (array) # Add sub representers under :items attr_info[:items] = sreps else # Not a collection # Type included in ref # Add sub representers inline attr_info.delete(:type) attr_info.merge!(sreps) end end # Merge attr_info back into the schema schema[:properties][name.to_sym] = attr_info end # Cleanup unused fields [:required, :properties].each do |field| schema.delete(field) if schema[field].blank? end # Return the completed object schema schema end
json_schema(representer, options = {})
click to toggle source
Returns the json schema for a representer
# File lib/openstax/api/representable_schema_printer.rb, line 6 def self.json_schema(representer, options = {}) definitions = {} schema = json_object(representer, definitions, options) schema[:definitions] = definitions unless definitions.blank? schema end
representer_name(representer)
click to toggle source
Attempts to extract the given representer’s name
# File lib/openstax/api/representable_schema_printer.rb, line 29 def self.representer_name(representer) name = representer.try :name return nil if name.nil? name.chomp('Representer').demodulize.camelize(:lower) end