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.

Class Attribute Summary collapse

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



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/jsi/base.rb', line 78

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

Class Attribute Details

.in_schema_classesObject

Returns the value of attribute in_schema_classes.



18
19
20
# File 'lib/jsi/base.rb', line 18

def in_schema_classes
  @in_schema_classes
end

Instance Attribute Details

#ancestorObject (readonly)

a JSI which is an ancestor of this



97
98
99
# File 'lib/jsi/base.rb', line 97

def ancestor
  @ancestor
end

#instanceObject

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



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

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



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/jsi/base.rb', line 27

def inspect
  name # see #name for side effects
  if !respond_to?(:schema)
    super
  elsif in_schema_classes
    %Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
  elsif !name
    %Q(#<Class for Schema: #{schema_id}>)
  else
    %Q(#{name} (#{schema_id}))
  end
end

.nameString

Returns a constant name of this class.

Returns:

  • (String)

    a constant name of this class



62
63
64
65
66
67
68
# File 'lib/jsi/base.rb', line 62

def name
  unless super || SchemaClasses.const_defined?(schema_classes_const_name)
    SchemaClasses.const_set(schema_classes_const_name, self)
    self.in_schema_classes = true
  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.



54
55
56
57
58
59
# File 'lib/jsi/base.rb', line 54

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.



22
23
24
# File 'lib/jsi/base.rb', line 22

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



42
43
44
45
46
47
48
49
50
# File 'lib/jsi/base.rb', line 42

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



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

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:



130
131
132
133
134
135
136
137
# File 'lib/jsi/base.rb', line 130

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)


101
102
103
# File 'lib/jsi/base.rb', line 101

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



206
207
208
# File 'lib/jsi/base.rb', line 206

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

#fragmentObject



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

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



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

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



175
176
177
# File 'lib/jsi/base.rb', line 175

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



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

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



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

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:



122
123
124
# File 'lib/jsi/base.rb', line 122

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:



110
111
112
113
114
115
116
117
# File 'lib/jsi/base.rb', line 110

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



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/jsi/base.rb', line 181

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



161
162
163
# File 'lib/jsi/base.rb', line 161

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



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

def validate!
  schema.validate!(instance)
end