json_schema_view 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +58 -0
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE.md +202 -0
  9. data/README.md +117 -0
  10. data/Rakefile +12 -0
  11. data/json_schema_view.gemspec +42 -0
  12. data/lib/json_schema_view/base_component.rb +82 -0
  13. data/lib/json_schema_view/base_props.rb +32 -0
  14. data/lib/json_schema_view/configuration/schema_set_dictionary.rb +51 -0
  15. data/lib/json_schema_view/configuration.rb +44 -0
  16. data/lib/json_schema_view/json_schema_definable.rb +22 -0
  17. data/lib/json_schema_view/json_world_extensions/additional_properties.rb +58 -0
  18. data/lib/json_schema_view/json_world_extensions/any_of.rb +41 -0
  19. data/lib/json_schema_view/json_world_extensions/camelizable.rb +36 -0
  20. data/lib/json_schema_view/json_world_extensions/compact_optional_properties.rb +20 -0
  21. data/lib/json_schema_view/json_world_extensions/constant_property.rb +39 -0
  22. data/lib/json_schema_view/json_world_extensions/declarable.rb +55 -0
  23. data/lib/json_schema_view/json_world_extensions/enum_type.rb +45 -0
  24. data/lib/json_schema_view/json_world_extensions/map_type.rb +47 -0
  25. data/lib/json_schema_view/json_world_extensions/validatable.rb +17 -0
  26. data/lib/json_schema_view/json_world_extensions.rb +31 -0
  27. data/lib/json_schema_view/rails/generators/install/USAGE +5 -0
  28. data/lib/json_schema_view/rails/generators/install/install_generator.rb +50 -0
  29. data/lib/json_schema_view/rails/generators/install/templates/base_component.rb.tt +17 -0
  30. data/lib/json_schema_view/rails/generators/install/templates/base_props.rb.tt +7 -0
  31. data/lib/json_schema_view/rails/generators/install/templates/component_schema_set.rb.tt +25 -0
  32. data/lib/json_schema_view/rails/generators/install/templates/example/todo_item_resource.rb +52 -0
  33. data/lib/json_schema_view/rails/generators/install/templates/example/todo_list_component.rb +67 -0
  34. data/lib/json_schema_view/rails/generators/install/templates/initializer.rb.tt +15 -0
  35. data/lib/json_schema_view/rails/generators.rb +8 -0
  36. data/lib/json_schema_view/rails/rails_engine.rb +20 -0
  37. data/lib/json_schema_view/rails/tasks/json_schema_component.rake +9 -0
  38. data/lib/json_schema_view/rails.rb +8 -0
  39. data/lib/json_schema_view/renderers/base.rb +27 -0
  40. data/lib/json_schema_view/renderers/json.rb +28 -0
  41. data/lib/json_schema_view/renderers/react_on_rails.rb +17 -0
  42. data/lib/json_schema_view/renderers.rb +22 -0
  43. data/lib/json_schema_view/schema_set/constant_search_helper.rb +114 -0
  44. data/lib/json_schema_view/schema_set.rb +58 -0
  45. data/lib/json_schema_view/version.rb +5 -0
  46. data/lib/json_schema_view.rb +29 -0
  47. data/sig/json_schema_view.rbs +4 -0
  48. metadata +134 -0
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schema_view"
4
+
5
+ # This is an example definition of a component.
6
+ #
7
+ # @example You can use this component in a view like following code:
8
+ # todo_items = [{ title: "Buy milk", done: false }, { title: "Buy eggs", done: true, note: "Large eggs are better" }]
9
+ # render ExampleTodoListComponent.new(props: { todo_items: todo_items })
10
+ #
11
+ # @example Extract the JSON Schema of this component:
12
+ # ExampleTodoListComponent.props_class.to_json_schema
13
+ # # => {
14
+ # "properties": {
15
+ # "type": "array",
16
+ # "items": {
17
+ # "type": "object",
18
+ # "properties": {
19
+ # "name": {
20
+ # "description": "The title of TODO item.",
21
+ # "type": "string"
22
+ # },
23
+ # "done": {
24
+ # "description": "Whether the TODO item is done or not.",
25
+ # "type": ["boolean"]
26
+ # },
27
+ # "note": {
28
+ # "description": "The additional description of TODO item.",
29
+ # "type": ["string"]
30
+ # }
31
+ # },
32
+ # "required": ["name", "done"],
33
+ # "additionalProperties": false
34
+ # }
35
+ # }
36
+ # "required": ["todo_items"],
37
+ # "additionalProperties": false
38
+ # }
39
+ #
40
+ class ExampleTodoListComponent < BaseComponent
41
+ # props_class defines the properties class for the component.
42
+ # properties class is a special form of {JsonSchemaDefinable}. its instance is used by renderer to render component's content.
43
+ props_class do
44
+ # Some class methods (properties, description, additional_properties, etc.) are provided to define the JSON Schema of the class.
45
+ # Property
46
+ #
47
+ # @see https://json-schema.org/understanding-json-schema/reference/object.html#properties
48
+ property(
49
+ :todo_items,
50
+ # To type keyword, you can pass some primitive classes (String, Integer, TrueClass, Array, ...etc), a hash representing JSON Schema, an {JsonSchemaDefinable}.
51
+ type: Array,
52
+ item: {
53
+ type: TodoItemResource
54
+ },
55
+ )
56
+
57
+ # @param todo_items [Array<Hash>] An array of todo items ({ name: String, done: Boolean, note: String or nil }).
58
+ def initialize(todo_items:)
59
+ @todo_items = todo_items
60
+ end
61
+
62
+ # @return [Array<ExampleTodoItemResource>]
63
+ def todo_items
64
+ @todo_items.map { |todo_item| ExampleTodoItemResource.new(todo_item) }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schema_view"
4
+
5
+ Rails.application.config.autoload_paths += [Rails.root.join("<%= components_path %>")]
6
+
7
+ # You can export your components' schemas by running:
8
+ #
9
+ # $ rake json_schema_view:export:primary
10
+ Rails.application.config.json_schema_view.schema_sets = {
11
+ "primary": "<%= schema_set_class_name %>",
12
+ }
13
+
14
+ # Validate component with its schema on render.
15
+ Rails.application.config.json_schema_view.validate_by_default = true
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonSchemaView
4
+ # A collection of rails generators.
5
+ module Generators
6
+ require_relative "generators/install/install_generator"
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+
5
+ module JsonSchemaView
6
+ # :nodoc:
7
+ class RailsEngine < ::Rails::Engine
8
+ rake_tasks do
9
+ load "json_schema_view/rails/tasks/json_schema_view.rake"
10
+ end
11
+
12
+ generators do
13
+ require_relative "generators"
14
+ end
15
+
16
+ config.before_configuration do
17
+ Rails.application.config.json_schema_view = Configuration.instance
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :json_schema_view do
4
+ desc "Export json schemas in the specified schema set"
5
+ task :export, [:schema_set] => [:environment] do |_task, args|
6
+ schema_set = JsonSchemaView.configuration.schema_sets.fetch_instance(args[:schema_set])
7
+ schema_set.export_json_schemas(print_logs: true)
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "rails"
5
+ require_relative "rails/rails_engine"
6
+ rescue LoadError
7
+ # Skip loading modules about rails if Rails is not available.
8
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonSchemaView
4
+ module Renderers
5
+ # Render {BaseComponent} in a view.
6
+ # @abstract
7
+ class Base
8
+ # @return [BaseComponent]
9
+ attr_reader :component
10
+
11
+ # @param component [BaseComponent] The component to render.
12
+ def initialize(component)
13
+ @component = component
14
+ end
15
+
16
+ # Render the React component by using react_on_rails.
17
+ # This method is used by {ActionView::Template::Renderable}.
18
+ #
19
+ # @see https://guides.rubyonrails.org/layouts_and_rendering.html#rendering-objects
20
+ # @param view_content [Object]
21
+ # @return [String]
22
+ def render_in(view_context)
23
+ raise NotImplementedError
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module JsonSchemaView
6
+ module Renderers
7
+ # Render {BaseComponent} as json.
8
+ class Json < Base
9
+ # Render the React component by using react_on_rails.
10
+ # This method is used by {ActionView::Template::Renderable}.
11
+ #
12
+ # @param view_content [Object]
13
+ # @return [String]
14
+ def render_in(view_context)
15
+ view_context.render
16
+ view_context.render(json: component.props.to_json)
17
+ end
18
+
19
+ # Returns the content type of the response.
20
+ # This method is used by {ActionView::Template::Renderable}.
21
+ #
22
+ # @return [Symbol]
23
+ def format
24
+ :json
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module JsonSchemaView
6
+ module Renderers
7
+ # Render a React component by using {https://github.com/shakacode/react_on_rails}.
8
+ class ReactOnRails < Base
9
+ # @param view_content [Object]
10
+ # @param options (see ReactOnRailsHelper#react_component)
11
+ # @return [String]
12
+ def render_in(view_context, **options)
13
+ view_context.react_component(component.component_name, props: component.props.as_json, **options)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonSchemaView
4
+ # Renderers module contains classes that render a component.
5
+ module Renderers
6
+ require_relative "renderers/base"
7
+ require_relative "renderers/json"
8
+ require_relative "renderers/react_on_rails"
9
+
10
+ # @param name [Symbol]
11
+ def self.find_by_name(name)
12
+ case name
13
+ when :json
14
+ Json
15
+ when :react_on_rails
16
+ ReactOnRails
17
+ else
18
+ raise ArgumentError, "Unknown renderer: #{name}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "pathname"
5
+
6
+ module JsonSchemaView
7
+ class SchemaSet
8
+ # Add methods to search constants in the directory to SchemaSet.
9
+ module ConstantSearchHelper
10
+ # @return [Array<Class<BaseComponent>>]
11
+ def search_component_classes(path_pattern: nil)
12
+ constants = path_pattern ? search_constants(path_pattern) : all_constants
13
+ constants.select { |const| const.is_a?(Class) && const < JsonSchemaView::BaseComponent }
14
+ end
15
+
16
+ # @return [Array<Class, Module>]
17
+ def search_constants(path_pattern: "**/*.rb")
18
+ loader = ConstantAutoLoader.from_rails_app
19
+
20
+ root_pathname = Pathname.new(root_path)
21
+ Dir.glob(path_pattern, base: root_path).filter_map do |path|
22
+ path = root_pathname.join(path)
23
+ loader.load_constant_by_path(path)
24
+ end
25
+ end
26
+
27
+ # @return [Array<Class, Module>]
28
+ def all_constants
29
+ @all_constants ||= search_constants(path_pattern: "**/*.rb")
30
+ end
31
+
32
+ # Load corresponding constant for the path by using Rails's autoloader.
33
+ class ConstantAutoLoader
34
+ # @return [ConstantAutoLoader]
35
+ def self.from_rails_app
36
+ autoload_paths = [
37
+ *Rails.application.config.eager_load_paths,
38
+ *Rails.application.config.autoload_once_paths,
39
+ *Rails.application.config.autoload_paths
40
+ ]
41
+
42
+ new(autoload_paths: autoload_paths)
43
+ end
44
+
45
+ # @return [Array<Pathname>]
46
+ attr_reader :autoload_paths
47
+
48
+ # @param autoload_paths [Array<String>]
49
+ def initialize(autoload_paths:)
50
+ @autoload_paths = autoload_paths.map { |path| Pathname.new(path) }
51
+ end
52
+
53
+ # @param path [String, Pathname]
54
+ # @return [Object]
55
+ def load_constant_by_path(path)
56
+ autoload_base_path = nearest_autoload_path(path)
57
+ return nil unless autoload_base_path
58
+
59
+ rel_path = child_path(path, autoload_base_path)
60
+ return nil unless rel_path
61
+
62
+ ActiveSupport::Inflector.safe_constantize(constant_name_from_path(rel_path))
63
+ end
64
+
65
+ private
66
+
67
+ # @param path [Pathname]
68
+ # @return [String]
69
+ def constant_name_from_path(path)
70
+ path = remove_extname(path)
71
+ classified = ActiveSupport::Inflector.classify(path.to_s)
72
+
73
+ # Make absolute constant path
74
+ if classified.start_with?("::")
75
+ classified == "::" ? "::Object" : classified
76
+ else
77
+ "::#{classified}"
78
+ end
79
+ end
80
+
81
+ # @param path [Pathname]
82
+ # @return [Pathname]
83
+ def remove_extname(path)
84
+ # Repeat for the case of multiple extensions (e.g. .min.rb)
85
+ until path.extname.empty?
86
+ removed = path.to_s.delete_suffix(path.extname)
87
+ path = Pathname.new(removed)
88
+ end
89
+
90
+ path
91
+ end
92
+
93
+ # @param path [String]
94
+ # @return [Pathname, nil]
95
+ def nearest_autoload_path(path)
96
+ matches = autoload_paths.filter_map do |autoload_path|
97
+ rel_path = child_path(path, autoload_path)
98
+ rel_path && [autoload_path, rel_path]
99
+ end
100
+ matches.min_by { |(_autoload_path, rel_path)| rel_path.to_s.length }&.first
101
+ end
102
+
103
+ # @param path [Pathname]
104
+ # @param dir_path [Pathname]
105
+ # @return [Pathname, nil]
106
+ def child_path(path, base_dir_path)
107
+ path = Pathname.new(path).realpath
108
+ relative_path = path.relative_path_from(base_dir_path)
109
+ relative_path.to_s.start_with?("../") ? nil : relative_path
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "forwardable"
5
+ require "pathname"
6
+ require "fileutils"
7
+
8
+ module JsonSchemaView
9
+ # Builder build components
10
+ class SchemaSet
11
+ require_relative "schema_set/constant_search_helper"
12
+
13
+ include ConstantSearchHelper
14
+
15
+ # @abstract
16
+ # @return [String]
17
+ def root_path
18
+ raise NotImplementedError
19
+ end
20
+
21
+ # @abstract
22
+ # @return [String]
23
+ def export_path
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # @return [Array<Class<JsonSchemaDefinable>>]
28
+ def resource_classes_to_export
29
+ []
30
+ end
31
+
32
+ # @return [void]
33
+ def export_json_schemas(print_logs: false)
34
+ puts "Exports resource classes in #{self.class.name}..." if print_logs
35
+ export_pathname = Pathname.new(export_path)
36
+
37
+ exports = resource_classes_to_export.map do |resource_class|
38
+ destination = export_pathname.join("#{resource_class.name.demodulize}.json")
39
+ klass = resource_class < JsonSchemaView::BaseComponent ? resource_class.props_class : resource_class
40
+
41
+ [destination, resource_class, klass.to_json_schema]
42
+ end
43
+
44
+ clear_export_path
45
+
46
+ exports.each do |destination, resource_class, json_schema|
47
+ FileUtils.mkdir_p(destination.dirname)
48
+ destination.write(json_schema)
49
+ puts "#{resource_class.name} -> #{destination}" if print_logs
50
+ end
51
+ end
52
+
53
+ # @return [void]
54
+ def clear_export_path
55
+ FileUtils.rm_rf(export_path) if Dir.exist?(export_path)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonSchemaView
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+
5
+ require_relative "json_schema_view/json_schema_definable"
6
+ require_relative "json_schema_view/base_component"
7
+ require_relative "json_schema_view/base_props"
8
+ require_relative "json_schema_view/configuration"
9
+ require_relative "json_schema_view/json_world_extensions"
10
+ require_relative "json_schema_view/rails"
11
+ require_relative "json_schema_view/renderers"
12
+ require_relative "json_schema_view/schema_set"
13
+ require_relative "json_schema_view/version"
14
+
15
+ # A view library with JSON Schema.
16
+ module JsonSchemaView
17
+ class << self
18
+ # @yield (see JsonSchemaView::Configuration.configure)
19
+ # @yieldparam (see JsonSchemaView::Configuration.configure)
20
+ def configure(&block)
21
+ Configuration.configure(&block)
22
+ end
23
+
24
+ # @return [JsonSchemaView::Configuration]
25
+ def configuration
26
+ Configuration.instance
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ module JsonSchemaView
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_schema_view
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tomoya Chiba
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json-schema
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json_world
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A view library with JSON Schema
56
+ email:
57
+ - tomo.asleep@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".rspec"
63
+ - ".rubocop.yml"
64
+ - ".yardopts"
65
+ - CHANGELOG.md
66
+ - CODE_OF_CONDUCT.md
67
+ - Gemfile
68
+ - LICENSE.md
69
+ - README.md
70
+ - Rakefile
71
+ - json_schema_view.gemspec
72
+ - lib/json_schema_view.rb
73
+ - lib/json_schema_view/base_component.rb
74
+ - lib/json_schema_view/base_props.rb
75
+ - lib/json_schema_view/configuration.rb
76
+ - lib/json_schema_view/configuration/schema_set_dictionary.rb
77
+ - lib/json_schema_view/json_schema_definable.rb
78
+ - lib/json_schema_view/json_world_extensions.rb
79
+ - lib/json_schema_view/json_world_extensions/additional_properties.rb
80
+ - lib/json_schema_view/json_world_extensions/any_of.rb
81
+ - lib/json_schema_view/json_world_extensions/camelizable.rb
82
+ - lib/json_schema_view/json_world_extensions/compact_optional_properties.rb
83
+ - lib/json_schema_view/json_world_extensions/constant_property.rb
84
+ - lib/json_schema_view/json_world_extensions/declarable.rb
85
+ - lib/json_schema_view/json_world_extensions/enum_type.rb
86
+ - lib/json_schema_view/json_world_extensions/map_type.rb
87
+ - lib/json_schema_view/json_world_extensions/validatable.rb
88
+ - lib/json_schema_view/rails.rb
89
+ - lib/json_schema_view/rails/generators.rb
90
+ - lib/json_schema_view/rails/generators/install/USAGE
91
+ - lib/json_schema_view/rails/generators/install/install_generator.rb
92
+ - lib/json_schema_view/rails/generators/install/templates/base_component.rb.tt
93
+ - lib/json_schema_view/rails/generators/install/templates/base_props.rb.tt
94
+ - lib/json_schema_view/rails/generators/install/templates/component_schema_set.rb.tt
95
+ - lib/json_schema_view/rails/generators/install/templates/example/todo_item_resource.rb
96
+ - lib/json_schema_view/rails/generators/install/templates/example/todo_list_component.rb
97
+ - lib/json_schema_view/rails/generators/install/templates/initializer.rb.tt
98
+ - lib/json_schema_view/rails/rails_engine.rb
99
+ - lib/json_schema_view/rails/tasks/json_schema_component.rake
100
+ - lib/json_schema_view/renderers.rb
101
+ - lib/json_schema_view/renderers/base.rb
102
+ - lib/json_schema_view/renderers/json.rb
103
+ - lib/json_schema_view/renderers/react_on_rails.rb
104
+ - lib/json_schema_view/schema_set.rb
105
+ - lib/json_schema_view/schema_set/constant_search_helper.rb
106
+ - lib/json_schema_view/version.rb
107
+ - sig/json_schema_view.rbs
108
+ homepage: https://github.com/increments/json_schema_view
109
+ licenses:
110
+ - Apache-2.0
111
+ metadata:
112
+ homepage_uri: https://github.com/increments/json_schema_view
113
+ source_code_uri: https://github.com/increments/json_schema_view
114
+ changelog_uri: https://github.com/increments/json_schema_view/blob/main/CHANGELOG.md
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.7.0
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubygems_version: 3.3.7
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: A view library with JSON Schema
134
+ test_files: []