• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

notEthan / jsi / 4271338262

pending completion
4271338262

push

github

Ethan
HashNode JSIs don't defined to_ary and readers don't conflict with it

3 of 3 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

3605 of 3695 relevant lines covered (97.56%)

186651.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.37
/lib/jsi/schema_classes.rb
1
# frozen_string_literal: true
2

3
module JSI
21✔
4
  # JSI Schema Modules are extended with JSI::SchemaModule
5
  module SchemaModule
21✔
6
    # @!method schema
7
    #   the schema of which this is the JSI Schema Module
8
    #   @return [Schema]
9
    # note: defined on JSI Schema Module by JSI::SchemaClasses.module_for_schema
10

11

12
    # a URI which refers to the schema. see {Schema#schema_uri}.
13
    # @return (see Schema#schema_uri)
14
    def schema_uri
21✔
15
      schema.schema_uri
×
16
    end
17

18
    # @return [String]
19
    def inspect
21✔
20
      if name_from_ancestor
42✔
21
        "#{name_from_ancestor} (JSI Schema Module)"
27✔
22
      else
23
        "(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
27✔
24
      end
25
    end
26

27
    alias_method :to_s, :inspect
21✔
28

29
    # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
30
    #
31
    # @param (see JSI::Schema#new_jsi)
32
    # @return [JSI::Base] a JSI whose instance is the given instance
33
    def new_jsi(instance, **kw)
21✔
34
      schema.new_jsi(instance, **kw)
84✔
35
    end
36
  end
37

38
  # a module to extend the JSI Schema Module of a schema which describes other schemas
39
  module SchemaModule::DescribesSchemaModule
21✔
40
    # @!parse include SchemaModule
41

42

43
    # instantiates the given schema content as a JSI Schema.
44
    #
45
    # see {JSI::Schema::DescribesSchema#new_schema}
46
    #
47
    # @param (see JSI::Schema::DescribesSchema#new_schema)
48
    # @return [JSI::Base, JSI::Schema] a JSI whose instance is the given schema_content and whose schemas
49
    #   consist of this module's schema.
50
    def new_schema(schema_content, **kw)
21✔
51
      schema.new_schema(schema_content, **kw)
3,474✔
52
    end
53

54
    # instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
55
    #
56
    # shortcut to chain {JSI::Schema::DescribesSchema#new_schema} + {Schema#jsi_schema_module}.
57
    #
58
    # @param (see JSI::Schema::DescribesSchema#new_schema)
59
    # @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
60
    def new_schema_module(schema_content, **kw)
21✔
61
      schema.new_schema(schema_content, **kw).jsi_schema_module
14✔
62
    end
63

64
    # @return [Set<Module>]
65
    attr_reader :schema_implementation_modules
21✔
66
  end
67

68
  # this module is a namespace for building schema classes and schema modules.
69
  module SchemaClasses
21✔
70
    extend Util::Memoize
21✔
71

72
    class << self
21✔
73
      # @api private
74
      # @return [Set<Module>]
75
      def includes_for(instance)
21✔
76
        includes = Set[]
317,393✔
77
        includes << Base::ArrayNode if instance.respond_to?(:to_ary)
317,393✔
78
        includes << Base::HashNode if instance.respond_to?(:to_hash)
317,393✔
79
        includes.freeze
317,393✔
80
      end
81

82
      # a JSI Schema Class which represents the given schemas.
83
      # an instance of the class is a JSON Schema instance described by all of the given schemas.
84
      # @api private
85
      # @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
86
      # @param includes [Enumerable<Module>] modules which will be included on the class
87
      # @return [Class subclassing JSI::Base]
88
      def class_for_schemas(schemas, includes: )
21✔
89
        schemas = SchemaSet.ensure_schema_set(schemas)
194,571✔
90
        includes = Util.ensure_module_set(includes)
194,571✔
91

92
        jsi_memoize(:class_for_schemas, schemas: schemas, includes: includes) do |schemas: , includes: |
139,463✔
93
          Class.new(Base) do
10,254✔
94
            define_singleton_method(:jsi_class_schemas) { schemas }
7,469✔
95
            define_method(:jsi_schemas) { schemas }
355,668✔
96

97
            conflicting_modules = Set[JSI::Base] + includes + schemas.map(&:jsi_schema_module)
10,254✔
98

99
            reader_modules = schemas.map do |schema|
10,254✔
100
              JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
14,181✔
101
            end
102
            reader_modules.each { |m| include m }
24,435✔
103
            readers = reader_modules.map(&:jsi_property_readers).inject(Set[], &:merge).freeze
10,254✔
104
            define_method(:jsi_property_readers) { readers }
22,085✔
105
            define_singleton_method(:jsi_property_readers) { readers }
7,350✔
106

107
            writer_modules = schemas.map do |schema|
10,254✔
108
              JSI::SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules)
14,181✔
109
            end
110
            writer_modules.each { |m| include m }
24,435✔
111

112
            includes.each { |m| include(m) }
16,781✔
113
            schemas.each { |schema| include(schema.jsi_schema_module) }
24,435✔
114
            jsi_class = self
10,254✔
115
            define_method(:jsi_class) { jsi_class }
2,754,234✔
116

117
            self
7,350✔
118
          end
119
        end
120
      end
121

122
      # a subclass of MetaschemaNode::BootstrapSchema with the given modules included
123
      # @api private
124
      # @param modules [Set<Module>] schema implementation modules
125
      # @return [Class]
126
      def bootstrap_schema_class(modules)
21✔
127
        modules = Util.ensure_module_set(modules)
18,305✔
128
        jsi_memoize(__method__, modules: modules) do |modules: |
13,075✔
129
          Class.new(MetaschemaNode::BootstrapSchema) do
98✔
130
            define_singleton_method(:schema_implementation_modules) { modules }
77✔
131
            define_method(:schema_implementation_modules) { modules }
113,714✔
132
            modules.each { |mod| include(mod) }
203✔
133

134
            self
70✔
135
          end
136
        end
137
      end
138

139
      # see {Schema#jsi_schema_module}
140
      # @api private
141
      # @return [Module]
142
      def module_for_schema(schema)
21✔
143
        Schema.ensure_schema(schema)
271,440✔
144
        jsi_memoize(:module_for_schema, schema: schema) do |schema: |
194,348✔
145
          Module.new do
13,222✔
146
            begin
147
              define_singleton_method(:schema) { schema }
13,357✔
148

149
              extend SchemaModule
13,222✔
150

151
              @possibly_schema_node = schema
13,222✔
152
              extend(SchemaModulePossibly)
13,222✔
153
              schema.jsi_schemas.each do |schema_schema|
13,222✔
154
                extend JSI::SchemaClasses.schema_property_reader_module(schema_schema,
15,964✔
155
                  conflicting_modules: Set[Module, SchemaModule, SchemaModulePossibly],
1,997✔
156
                )
157
              end
158
            end
159
          end
160
        end
161
      end
162

163
      # a module of accessors for described property names of the given schema.
164
      # getters are always defined. setters are defined by default.
165
      #
166
      # @api private
167
      # @param schema [JSI::Schema] a schema for which to define accessors for any described property names
168
      # @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
169
      #   may be used alongside the accessor module. methods defined by any conflicting_module
170
      #   will not be defined as accessors.
171
      # @param setters [Boolean] whether to define setter methods
172
      # @return [Module]
173
      def schema_property_reader_module(schema, conflicting_modules: )
21✔
174
        Schema.ensure_schema(schema)
49,362✔
175
        jsi_memoize(__method__, schema: schema, conflicting_modules: conflicting_modules) do |schema: , conflicting_modules: |
35,310✔
176
          Module.new do
16,421✔
177
            define_singleton_method(:inspect) { '(JSI Schema Property Reader Module)' }
11,755✔
178

179
            readers = schema.described_object_property_names.select do |name|
16,421✔
180
              Util.ok_ruby_method_name?(name) &&
25,026✔
181
                !conflicting_modules.any? { |m| m.method_defined?(name) || m.private_method_defined?(name) }
117,798✔
182
            end.to_set.freeze
2,348✔
183

184
            define_singleton_method(:jsi_property_readers) { readers }
46,117✔
185

186
            readers.each do |property_name|
16,421✔
187
                define_method(property_name) do |**kw|
18,615✔
188
                  self[property_name, **kw]
74,976✔
189
                end
190
            end
191
          end
192
        end
193
      end
194

195
      def schema_property_writer_module(schema, conflicting_modules: )
21✔
196
        Schema.ensure_schema(schema)
14,181✔
197
        jsi_memoize(__method__, schema: schema, conflicting_modules: conflicting_modules) do |schema: , conflicting_modules: |
10,155✔
198
          Module.new do
14,181✔
199
            define_singleton_method(:inspect) { '(JSI Schema Property Writer Module)' }
10,155✔
200

201
            writers = schema.described_object_property_names.select do |name|
14,181✔
202
              writer = "#{name}="
21,069✔
203
              Util.ok_ruby_method_name?(name) &&
14,046✔
204
                !conflicting_modules.any? { |m| m.method_defined?(writer) || m.private_method_defined?(writer) }
63,996✔
205
            end.to_set.freeze
2,028✔
206

207
            define_singleton_method(:jsi_property_writers) { writers }
10,155✔
208

209
            writers.each do |property_name|
14,181✔
210
                  define_method("#{property_name}=") do |value|
15,204✔
211
                    self[property_name] = value
126✔
212
                  end
213
            end
214
          end
215
        end
216
      end
217
    end
218
  end
219

220
  # a JSI Schema module and a JSI::NotASchemaModule are both a SchemaModulePossibly.
221
  # this module provides a #[] method.
222
  module SchemaModulePossibly
21✔
223
    attr_reader :possibly_schema_node
21✔
224

225
    # a name relative to a named schema module of an ancestor schema.
226
    # for example, if `Foos = JSI::JSONSchemaOrgDraft07.new_schema_module({'items' => {}})`
227
    # then the module `Foos.items` will have a name_from_ancestor of `"Foos.items"`
228
    # @api private
229
    # @return [String, nil]
230
    def name_from_ancestor
21✔
231
      named_ancestor_schema, tokens = named_ancestor_schema_tokens
12,376✔
232
      return nil unless named_ancestor_schema
12,376✔
233

234
      name = named_ancestor_schema.jsi_schema_module.name
11,781✔
235
      ancestor = named_ancestor_schema
11,781✔
236
      tokens.each do |token|
11,781✔
237
        if ancestor.jsi_property_readers.include?(token)
14,749✔
238
          name += ".#{token}"
11,223✔
239
        elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
12,047✔
240
          name += "[#{token.inspect}]"
7,740✔
241
        else
UNCOV
242
          return nil
×
243
        end
244
        ancestor = ancestor[token]
14,749✔
245
      end
246
      name
11,781✔
247
    end
248

249
    # subscripting a JSI schema module or a NotASchemaModule will subscript the node, and
250
    # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a JSI::Base,
251
    # return a NotASchemaModule; or if it is another value (a basic type), return that value.
252
    #
253
    # @param token [Object]
254
    # @return [Module, NotASchemaModule, Object]
255
    def [](token, **kw)
21✔
256
      raise(ArgumentError) unless kw.empty? # TODO remove eventually (keyword argument compatibility)
2,100✔
257
      sub = @possibly_schema_node[token]
2,100✔
258
      if sub.is_a?(JSI::Schema)
2,100✔
259
        sub.jsi_schema_module
1,057✔
260
      elsif sub.is_a?(JSI::Base)
1,041✔
261
        NotASchemaModule.new(sub)
1,036✔
262
      else
263
        sub
7✔
264
      end
265
    end
266

267
    private
21✔
268

269
    # @return [Array<JSI::Schema, Array>, nil]
270
    def named_ancestor_schema_tokens
21✔
271
      schema_ancestors = possibly_schema_node.jsi_ancestor_nodes
12,432✔
272
      named_ancestor_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
40,327✔
273
      return nil unless named_ancestor_schema
12,432✔
274
      tokens = possibly_schema_node.jsi_ptr.relative_to(named_ancestor_schema.jsi_ptr).tokens
11,795✔
275
      [named_ancestor_schema, tokens]
11,795✔
276
    end
277
  end
278

279
  # a JSI Schema Module is a module which represents a schema. a NotASchemaModule represents
280
  # a node in a schema's document which is not a schema, such as the 'properties'
281
  # object (which contains schemas but is not a schema).
282
  #
283
  # instances of this class act as a stand-in to allow users to subscript or call property accessors on
284
  # schema modules to refer to their subschemas' schema modules.
285
  #
286
  # a NotASchemaModule is extended with the module_for_schema of the node's schema.
287
  #
288
  # NotASchemaModule holds a node which is not a schema. when subscripted, it subscripts
289
  # its node. if the value is a JSI::Schema, its schema module is returned. if the value
290
  # is another node, a NotASchemaModule for that node is returned. otherwise - when the
291
  # value is a basic type - that value itself is returned.
292
  class NotASchemaModule
21✔
293
    include SchemaModulePossibly
21✔
294

295
    # @param node [JSI::Base]
296
    def initialize(node)
21✔
297
      raise(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
1,036✔
298
      raise(Bug, "node must not be JSI::Schema: #{node.pretty_inspect.chomp}") if node.is_a?(JSI::Schema)
1,036✔
299
      @possibly_schema_node = node
1,036✔
300
      node.jsi_schemas.each do |schema|
1,036✔
301
        extend(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly]))
1,036✔
302
      end
303
    end
304

305
    # @return [String]
306
    def inspect
21✔
307
      if name_from_ancestor
42✔
308
        "#{name_from_ancestor} (JSI wrapper for Schema Module)"
27✔
309
      else
310
        "(JSI wrapper for Schema Module: #{@possibly_schema_node.jsi_ptr.uri})"
27✔
311
      end
312
    end
313

314
    alias_method :to_s, :inspect
21✔
315
  end
316
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc