Class: Aspera::Schema::Reader
- Inherits:
-
Object
- Object
- Aspera::Schema::Reader
- Defined in:
- lib/aspera/schema/reader.rb
Overview
JSON schema reader
Instance Attribute Summary collapse
-
#current ⇒ Object
readonly
Returns the value of attribute current.
Class Method Summary collapse
-
.from_query_params(params) ⇒ Reader
Build a synthetic Reader from an OAS
parametersarray (entries within: query).
Instance Method Summary collapse
-
#[](key) ⇒ Hash, ...
Shortcut to access current value at path.
-
#dig(*path) ⇒ Object
Find sub path relative to current Honors $ref.
-
#each_property(prefix = '', on_variant: nil) {|property_schema, name, full_name| ... } ⇒ nil
Recursively traverse schema properties with a block.
-
#initialize(root, current = nil) ⇒ Hash?
constructor
Read schema from file or from cache.
-
#resolve_ref(ref) ⇒ Object
Resolve a $ref string to a Reader.
-
#to_rows ⇒ Array<Hash>
Convert this schema to a flat array of field descriptors.
Constructor Details
Instance Attribute Details
#current ⇒ Object (readonly)
Returns the value of attribute current.
8 9 10 |
# File 'lib/aspera/schema/reader.rb', line 8 def current @current end |
Class Method Details
.from_query_params(params) ⇒ Reader
Build a synthetic Reader from an OAS parameters array (entries with in: query).
Produces a JSON Schema object whose properties map each query param name to its schema,
with the OAS-level description and required merged in.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/aspera/schema/reader.rb', line 16 def from_query_params(params) properties = {} required_names = [] params.each do |param| next unless param['in'] == 'query' name = param['name'] prop = (param['schema'] || {}).dup prop['description'] = param['description'] if param['description'] && !prop.key?('description') properties[name] = prop required_names << name if param['required'] end synthetic = {'type' => 'object', 'properties' => properties} synthetic['required'] = required_names unless required_names.empty? new(synthetic) end |
Instance Method Details
#[](key) ⇒ Hash, ...
Shortcut to access current value at path
36 37 38 |
# File 'lib/aspera/schema/reader.rb', line 36 def [](key) @current[key] end |
#dig(*path) ⇒ Object
Find sub path relative to current Honors $ref
42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/aspera/schema/reader.rb', line 42 def dig(*path) current = @current path.each do |p| Aspera.assert(current.key?(p)) { "schema: #{p} in #{path}" } current = current[p] Aspera.assert_type(current, Hash) { 'schema' } if current.key?('$ref') ref = current['$ref'] Aspera.assert(ref.start_with?('#/')) { "schema $ref must start with '#/': #{ref}" } current = @root.dig(*ref[2..].split('/')) end end Reader.new(@root, current) end |
#each_property(prefix = '', on_variant: nil) {|property_schema, name, full_name| ... } ⇒ nil
Recursively traverse schema properties with a block.
If the current node has oneOf, each variant is traversed in turn and
on_variant is called (if given) before each variant's properties.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/aspera/schema/reader.rb', line 82 def each_property(prefix = '', on_variant: nil, &block) if @current.key?('oneOf') # Build reverse map: $ref -> discriminant value, from discriminator.mapping if present discriminant_by_ref = {} if @current.dig('discriminator', 'mapping').is_a?(Hash) @current['discriminator']['mapping'].each do |value, ref| discriminant_by_ref[ref] = value end end discriminant_property = @current.dig('discriminator', 'propertyName') @current['oneOf'].each do |variant_node| ref = variant_node['$ref'] variant_reader = ref ? resolve_ref(ref) : Reader.new(@root, variant_node) discriminant_value = ref ? discriminant_by_ref[ref] : nil on_variant&.call(variant_reader, discriminant_property, discriminant_value) variant_reader.each_property(prefix, on_variant: on_variant, &block) end return end if @current.key?('allOf') # Merge all branches: each branch contributes its properties (no variants) @current['allOf'].each do |branch_node| ref = branch_node['$ref'] branch_reader = ref ? resolve_ref(ref) : Reader.new(@root, branch_node) branch_reader.each_property(prefix, on_variant: on_variant, &block) end return end properties = dig('properties') properties.current.each_key do |name| property_full_name = "#{prefix}#{name}" property_schema = properties.dig(name) node = property_schema.current # Yield current property to block yield(property_schema, name, property_full_name) # Recursively process nested structures case node['type'] when 'object' property_schema.each_property("#{property_full_name}.", on_variant: on_variant, &block) if node['properties'] when 'array' if node['items'] array_item_schema = property_schema.dig('items') array_item_schema.each_property("#{property_full_name}[].", on_variant: on_variant, &block) if array_item_schema.current['properties'] end end # allOf without explicit type: object — recurse to merge all branches property_schema.each_property("#{property_full_name}.", on_variant: on_variant, &block) if node['allOf'] end end |
#resolve_ref(ref) ⇒ Object
Resolve a $ref string to a Reader
58 59 60 61 |
# File 'lib/aspera/schema/reader.rb', line 58 def resolve_ref(ref) Aspera.assert(ref.start_with?('#/')) { "schema $ref must start with '#/': #{ref}" } Reader.new(@root, @root.dig(*ref[2..].split('/'))) end |
#to_rows ⇒ Array<Hash>
Convert this schema to a flat array of field descriptors. Returns raw semantic fields with no ANSI or formatting, suitable for JSON/YAML output or MCP consumption. An AI or script can use the result directly to build a valid payload.
Each Hash entry contains:
name [String] dot/bracket-path of the field (e.g. "recipients[].name")
type [String] JSON type string (e.g. "string", "boolean", "Array[object]")
required [Boolean] true when the field is in its immediate parent's required list
description [String] human description (may contain Markdown **bold** / `code`)
default [Object] (optional) default value as native Ruby type
enum [Array] (optional) list of allowed string values
148 149 150 151 152 |
# File 'lib/aspera/schema/reader.rb', line 148 def to_rows rows = [] collect_rows(rows, self, '') rows end |