Class: JSI::Base

Inherits:
Object
  • Object
show all
Includes:
Enumerable, FingerprintHash, Memoize
Defined in:
lib/jsi/base.rb,
lib/jsi/base/to_rb.rb

Overview

the base class for representing and instantiating a JSON Schema.

a class inheriting from JSI::Base represents a JSON Schema. an instance of that class represents a JSON schema instance.

as such, JSI::Base itself is not intended to be instantiated - subclasses are dynamically created for schemas using class_for_schema, and these are what are used to instantiate and represent JSON schema instances.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from FingerprintHash

#==, #hash

Methods included from Memoize

#clear_memo, #memoize

Constructor Details

#initialize(instance, ancestor: nil) ⇒ Base

initializes this JSI from the given instance. the instance will be wrapped as a JSI::JSON::Node (unless what you pass is a Node already).

Parameters:

  • instance (Object)

    the JSON Schema instance being represented

  • ancestor (JSI::Base) (defaults to: nil)

    for internal use, specifies an ancestor from which this JSI originated to calculate #parents



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/jsi/base.rb', line 72

def initialize(instance, ancestor: nil)
  unless respond_to?(:schema)
    raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #schema. please use JSI.class_for_schema")
  end

  @ancestor = ancestor || self
  self.instance = instance

  if @instance.is_a?(JSI::JSON::HashNode)
    extend BaseHash
  elsif @instance.is_a?(JSI::JSON::ArrayNode)
    extend BaseArray
  end
end

Instance Attribute Details

#ancestorObject (readonly)

a JSI which is an ancestor of this



91
92
93
# File 'lib/jsi/base.rb', line 91

def ancestor
  @ancestor
end

#instanceObject

the instance of the json-schema. this is a JSI::JSON::Node.



88
89
90
# File 'lib/jsi/base.rb', line 88

def instance
  @instance
end

Class Method Details

.class_commentObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/jsi/base/to_rb.rb', line 4

def class_comment
  lines = []

  description = schema &&
    schema['description'].respond_to?(:to_str) &&
    schema['description'].to_str
  if description
    description.split("\n", -1).each do |descline|
      lines << "# " + descline
    end
    lines << "#"
  end

  schema.described_object_property_names.each_with_index do |propname, i|
    lines << "#" unless i == 0
    lines << "# @!attribute [rw] #{propname}"

    property_schema = schema['properties'].respond_to?(:to_hash) &&
      schema['properties'][propname].respond_to?(:to_hash) &&
      schema['properties'][propname]

    required = property_schema && property_schema['required']
    required ||= schema['required'].respond_to?(:to_ary) && schema['required'].include?(propname)
    lines << "#   @required" if required

    type = property_schema &&
      property_schema['type'].respond_to?(:to_str) &&
      property_schema['type'].to_str
    simple = {'string' => 'String', 'number' => 'Numeric', 'boolean' => 'Boolean', 'null' => 'nil'}
    rettypes = []
    if simple.key?(type)
      rettypes << simple[type]
    elsif type == 'object' || type == 'array'
      rettypes = []
      schema_class = JSI.class_for_schema(property_schema)
      unless schema_class.name =~ /\AJSI::SchemaClasses::/
        rettypes << schema_class.name
      end
      rettypes << {'object' => '#to_hash', 'array' => '#to_ary'}[type]
    elsif type
      # not really valid, but there's some information in there. whatever it is.
      rettypes << type
    end
    # we'll add Object to all because the accessor methods have no enforcement that their value is
    # of the specified type, and may return anything really. TODO: consider if this is of any value?
    rettypes << 'Object'
    lines << "#   @return [#{rettypes.join(', ')}]"

    description = property_schema &&
      property_schema['description'].respond_to?(:to_str) &&
      property_schema['description'].to_str
    if description
      description.split("\n", -1).each do |descline|
        lines << "#     " + descline
      end
    end
  end
  lines.join("\n")
end

.inspectString

Returns a string representing the class, with schema_id.

Returns:

  • (String)

    a string representing the class, with schema_id



25
26
27
28
29
30
31
32
33
# File 'lib/jsi/base.rb', line 25

def inspect
  if !respond_to?(:schema)
    super
  elsif !name || name =~ /\AJSI::SchemaClasses::/
    %Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
  else
    %Q(#{name} (#{schema_id}))
  end
end

.nameString

Returns a constant name of this class.

Returns:

  • (String)

    a constant name of this class



57
58
59
60
61
62
# File 'lib/jsi/base.rb', line 57

def name
  unless super || SchemaClasses.const_defined?(schema_classes_const_name)
    SchemaClasses.const_set(schema_classes_const_name, self)
  end
  super
end

.schema_classes_const_nameString

Returns a name for a constant for this class, generated from the schema_id. only used if the class is not assigned to another constant.

Returns:

  • (String)

    a name for a constant for this class, generated from the schema_id. only used if the class is not assigned to another constant.



49
50
51
52
53
54
# File 'lib/jsi/base.rb', line 49

def schema_classes_const_name
  name = schema.schema_id.gsub(/[^\w]/, '_')
  name = 'X' + name unless name[/\A[a-zA-Z_]/]
  name = name[0].upcase + name[1..-1]
  name
end

.schema_idString

Returns absolute schema_id of the schema this class represents. see Schema#schema_id.

Returns:

  • (String)

    absolute schema_id of the schema this class represents. see Schema#schema_id.



20
21
22
# File 'lib/jsi/base.rb', line 20

def schema_id
  schema.schema_id
end

.to_rbObject



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
# File 'lib/jsi/base/to_rb.rb', line 64

def to_rb
  lines = []
  description = schema &&
    schema['description'].respond_to?(:to_str) &&
    schema['description'].to_str
  if description
    description.split("\n", -1).each do |descline|
      lines << "# " + descline
    end
  end
  lines << "class #{name}"
  schema.described_object_property_names.each_with_index do |propname, i|
    lines << "" unless i == 0
    property_schema = schema['properties'].respond_to?(:to_hash) &&
      schema['properties'][propname].respond_to?(:to_hash) &&
      schema['properties'][propname]
    description = property_schema &&
      property_schema['description'].respond_to?(:to_str) &&
      property_schema['description'].to_str
    if description
      description.split("\n", -1).each do |descline|
        lines << "  # " + descline
      end
      lines << "  #" # blank comment line between description and @return
    end

    required = property_schema && property_schema['required']
    required ||= schema['required'].respond_to?(:to_ary) && schema['required'].include?(propname)
    lines << "  # @required" if required

    type = property_schema &&
      property_schema['type'].respond_to?(:to_str) &&
      property_schema['type'].to_str
    simple = {'string' => 'String', 'number' => 'Numeric', 'boolean' => 'Boolean', 'null' => 'nil'}
    rettypes = []
    if simple.key?(type)
      rettypes << simple[type]
    elsif type == 'object' || type == 'array'
      rettypes = []
      schema_class = JSI.class_for_schema(property_schema)
      unless schema_class.name =~ /\AJSI::SchemaClasses::/
        rettypes << schema_class.name
      end
      rettypes << {'object' => '#to_hash', 'array' => '#to_ary'}[type]
    elsif type
      # not really valid, but there's some information in there. whatever it is.
      rettypes << type
    end
    # we'll add Object to all because the accessor methods have no enforcement that their value is
    # of the specified type, and may return anything really. TODO: consider if this is of any value?
    rettypes << 'Object'
    lines << "  # @return [#{rettypes.join(', ')}]"

    lines << "  def #{propname}"
    lines << "    super"
    lines << "  end"
  end
  lines << "end"
  lines.join("\n")
end

.to_sString

Returns a string representing the class - a class name if one was explicitly defined, otherwise a reference to JSI::SchemaClasses.

Returns:

  • (String)

    a string representing the class - a class name if one was explicitly defined, otherwise a reference to JSI::SchemaClasses



37
38
39
40
41
42
43
44
45
# File 'lib/jsi/base.rb', line 37

def to_s
  if !respond_to?(:schema)
    super
  elsif !name || name =~ /\AJSI::SchemaClasses::/
    %Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
  else
    name
  end
end

Instance Method Details

#as_json(*opt) ⇒ Object

Returns a jsonifiable representation of the instance.

Returns:

  • (Object)

    a jsonifiable representation of the instance



195
196
197
# File 'lib/jsi/base.rb', line 195

def as_json(*opt)
  Typelike.as_json(instance, *opt)
end

#derefJSI::Base, self

if this JSI is a $ref then the $ref is followed. otherwise this JSI is returned.

Returns:



124
125
126
127
128
129
130
131
# File 'lib/jsi/base.rb', line 124

def deref
  derefed = instance.deref
  if derefed.object_id == instance.object_id
    self
  else
    self.class.new(derefed, ancestor: @ancestor)
  end
end

#eachObject

each is overridden by BaseHash or BaseArray when appropriate. the base

each is not actually implemented, along with all the methods of Enumerable.

Raises:

  • (NoMethodError)


95
96
97
# File 'lib/jsi/base.rb', line 95

def each
  raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{instance.pretty_inspect.chomp}"
end

#fingerprintObject

Returns an opaque fingerprint of this JSI for FingerprintHash.

Returns:

  • (Object)

    an opaque fingerprint of this JSI for FingerprintHash



200
201
202
# File 'lib/jsi/base.rb', line 200

def fingerprint
  {class: self.class, instance: instance}
end

#fragmentObject



145
146
147
# File 'lib/jsi/base.rb', line 145

def fragment
  instance.fragment
end

#fully_validateArray<String>

Returns array of schema validation error messages for this instance.

Returns:

  • (Array<String>)

    array of schema validation error messages for this instance



150
151
152
# File 'lib/jsi/base.rb', line 150

def fully_validate
  schema.fully_validate(instance)
end

#inspectString

Returns a string representing this JSI, indicating its class and inspecting its instance.

Returns:

  • (String)

    a string representing this JSI, indicating its class and inspecting its instance



169
170
171
# File 'lib/jsi/base.rb', line 169

def inspect
  "\#<#{self.class.to_s} #{instance.inspect}>"
end

#modified_copy {|Object| ... } ⇒ JSI::Base subclass the same as self

yields the content of the underlying instance. the block must result in a modified copy of that (not destructively modifying the yielded content) which will be used to instantiate a new instance of this JSI class with the modified content.

Yields:

  • (Object)

    the content of the instance. the block should result in a (nondestructively) modified copy of this.

Returns:

  • (JSI::Base subclass the same as self)

    the modified copy of self



140
141
142
143
# File 'lib/jsi/base.rb', line 140

def modified_copy(&block)
  modified_instance = instance.modified_copy(&block)
  self.class.new(modified_instance, ancestor: @ancestor)
end

#object_group_textString

Returns the instance's object_group_text.

Returns:

  • (String)

    the instance's object_group_text



190
191
192
# File 'lib/jsi/base.rb', line 190

def object_group_text
  instance.object_group_text
end

#parentJSI::Base?

the immediate parent of this JSI. nil if no parent(s) are known.

Returns:



116
117
118
# File 'lib/jsi/base.rb', line 116

def parent
  parents.first
end

#parentsArray<JSI::Base>

an array of JSI instances above this one in the document. empty if this JSI is at the root or was instantiated from a source that does not have a document (e.g. a plain hash or array).

Returns:



104
105
106
107
108
109
110
111
# File 'lib/jsi/base.rb', line 104

def parents
  parent = @ancestor
  (@ancestor.instance.path.size...self.instance.path.size).map do |i|
    parent.tap do
      parent = parent[self.instance.path[i]]
    end
  end.reverse
end

#pretty_print(q) ⇒ void

This method returns an undefined value.

pretty-prints a representation this JSI to the given printer



175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/jsi/base.rb', line 175

def pretty_print(q)
  q.instance_exec(self) do |obj|
    text "\#<#{obj.class.to_s}"
    group_sub {
      nest(2) {
        breakable ' '
        pp obj.instance
      }
    }
    breakable ''
    text '>'
  end
end

#validatetrue, false

Returns whether the instance validates against its schema.

Returns:

  • (true, false)

    whether the instance validates against its schema



155
156
157
# File 'lib/jsi/base.rb', line 155

def validate
  schema.validate(instance)
end

#validate!true

Returns if this method does not raise, it returns true to indicate a valid instance.

Returns:

  • (true)

    if this method does not raise, it returns true to indicate a valid instance.

Raises:

  • (::JSON::Schema::ValidationError)

    raises if the instance has validation errors



163
164
165
# File 'lib/jsi/base.rb', line 163

def validate!
  schema.validate!(instance)
end