katachi 0.0.0.1 → 0.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.
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A class to represent the result of a comparison.
4
+ # It has a value, a shape, a code, and optionally child codes.
5
+ # The code is a symbol that represents the result of the comparison.
6
+ class Katachi::ComparisonResult
7
+ # CODES is a Hash of the form { code: boolean }.
8
+ # The boolean represents whether the code is considered a match or not.
9
+ # For example, :match is considered a match, while :mismatch is not.
10
+ CODES = {
11
+ # General
12
+ exact_match: true,
13
+ match: true,
14
+ mismatch: false,
15
+ class_mismatch: false,
16
+ # AnyOf
17
+ any_of_match: true,
18
+ any_of_mismatch: false,
19
+ # Array Overall
20
+ array_is_empty: true,
21
+ array_is_match: true,
22
+ array_is_mismatch: false,
23
+ array_is_exact_match: true,
24
+ array_class_matches_any_array: true,
25
+ # Array Elements
26
+ array_element_match: true,
27
+ array_element_mismatch: false,
28
+ # Hashes
29
+ hash_class_matches_any_hash: true,
30
+ hash_is_exact_match: true,
31
+ hash_is_mismatch: false,
32
+ hash_is_match: true,
33
+ # Hash[extra key checks]
34
+ hash_has_extra_keys: false,
35
+ hash_has_no_extra_keys: true,
36
+ # Hash[extra key checks][individual keys]
37
+ hash_key_exactly_allowed: true,
38
+ hash_key_match_allowed: true,
39
+ hash_key_not_allowed: false,
40
+ # Hash[missing key checks]
41
+ hash_has_missing_keys: false,
42
+ hash_has_no_missing_keys: true,
43
+ # Hash[missing key checks][individual keys]
44
+ hash_key_exact_match: true,
45
+ hash_key_match: true,
46
+ hash_key_missing: false,
47
+ hash_key_optional: true,
48
+ # Hash[value checks]
49
+ hash_values_are_mismatch: false,
50
+ hash_values_are_match: true,
51
+ # Hash[value checks][individual kv pairs] vs Shape
52
+ kv_match: true,
53
+ kv_mismatch: false,
54
+ kv_specific_match: true,
55
+ kv_specific_mismatch: false,
56
+ # Hash[value checks][individual kv pairs] vs Shape[individual kv pairs]
57
+ kv_key_mismatch: false,
58
+ kv_value_match: true,
59
+ kv_value_mismatch: false,
60
+ }.freeze
61
+
62
+ attr_reader :value, :shape, :code, :child_results
63
+
64
+ def initialize(value:, shape:, code:, child_results: nil)
65
+ raise ArgumentError, "code `#{code.inspect}` must be one of #{CODES.keys.inspect}" unless CODES.key?(code)
66
+
67
+ @value = value
68
+ @shape = shape
69
+ @code = code
70
+ @child_results = child_results
71
+ assert_child_codes_are_valid
72
+ end
73
+
74
+ def match? = CODES[code]
75
+
76
+ def to_s(child_label = nil) = [basic_text(child_label), *child_results_text_lines].compact.join("\n")
77
+
78
+ private
79
+
80
+ def basic_text(child_label)
81
+ child_label_affix = "; child_label: #{child_label.inspect}" if child_label
82
+ "#{code.inspect} <-- compare(value: #{value.inspect}, shape: #{shape.inspect})#{child_label_affix}"
83
+ end
84
+
85
+ def child_results_text_lines
86
+ child_results&.flat_map do |k, result|
87
+ result.to_s(k).split("\n").map { |line| " #{line}" }
88
+ end
89
+ end
90
+
91
+ def assert_child_codes_are_valid
92
+ return unless child_results
93
+ raise ArgumentError, "child_results must be a Hash of ComparisonResult objects" unless child_results.is_a?(Hash)
94
+
95
+ return if child_results.values.all?(Katachi::ComparisonResult)
96
+
97
+ raise ArgumentError, "child_results must be a Hash of ComparisonResult objects"
98
+ end
99
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shapes"
4
+
5
+ Katachi::Shapes.add(:$uuid, /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec::Matchers.define :have_shape do |expected|
4
+ diffable
5
+
6
+ match do |value|
7
+ if @expected_code
8
+ Kt.compare(value:, shape: expected).code == @expected_code
9
+ else
10
+ Kt.compare(value:, shape: expected).match?
11
+ end
12
+ end
13
+
14
+ chain :with_code do |expected_code|
15
+ @expected_code = expected_code
16
+ end
17
+ end
18
+
19
+ RSpec::Matchers.define :have_compare_code do |expected|
20
+ match { |value| value.code == expected }
21
+ diffable
22
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A container for all the shapes
4
+ module Katachi::Shapes
5
+ @shapes = {}
6
+ def self.valid_key?(key) = key.is_a?(Symbol) && key.to_s.start_with?("$")
7
+ def self.all = @shapes
8
+
9
+ def self.add(key, shape)
10
+ raise ArgumentError, "Invalid shape key: #{key}" unless valid_key?(key)
11
+
12
+ @shapes[key] = shape
13
+ end
14
+
15
+ def self.[](maybe_shape)
16
+ # :$undefined is a special case because it's a valid key but not a shape
17
+ # This is because it's used to represent a value that is not present in a hash
18
+ # Afaik it's the only shape that has to look at the parent of the value being compared
19
+ # instead of the value itself.
20
+ return maybe_shape if maybe_shape == :$undefined || !valid_key?(maybe_shape)
21
+
22
+ @shapes[maybe_shape] || raise(ArgumentError, "Unknown shape: #{maybe_shape}")
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Katachi
4
- VERSION = "0.0.0.1"
4
+ VERSION = "0.0.1.0"
5
5
  end
data/lib/katachi.rb CHANGED
@@ -1,8 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "katachi/version"
4
+ require_relative "katachi/comparison_result"
5
+ require_relative "katachi/comparator"
6
+ require_relative "katachi/any_of"
7
+ require_relative "katachi/shapes"
8
+ require_relative "katachi/predefined_shapes"
4
9
 
10
+ # A tool for describing objects in a compact and readable way
5
11
  module Katachi
6
- class Error < StandardError; end
7
- # Your code goes here...
12
+ def self.compare(**) = Comparator.compare(**)
13
+ def self.any_of(*shapes) = AnyOf.new(*shapes)
14
+ def self.add_shape(key, shape) = Shapes.add(key, shape)
8
15
  end
16
+
17
+ Kt = Katachi
data/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ]
6
+ }
metadata CHANGED
@@ -1,21 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: katachi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.1
4
+ version: 0.0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Tannas
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-03-03 00:00:00.000000000 Z
10
+ date: 2025-03-18 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |
14
- Experimenting with a new way of describing objects, particularly
15
- in regards to APIs.
13
+ A tool for describing and validating objects as intuitively as possible.
16
14
 
17
- Inspired by [RSwag](https://github.com/rswag/rswag) and the challenges
18
- it faces with OpenAPI.
15
+ ```ruby
16
+ shape = {
17
+ :$guid => {
18
+ email: :$email,
19
+ first_name: String,
20
+ last_name: String,
21
+ preferred_name: AnyOf[String, nil],
22
+ admin_only_information: AnyOf[Symbol => String, :$undefined],
23
+ Symbol => Object,
24
+ },
25
+ }
26
+ expect(api_response.body).to have_shape(shape)
27
+ ```
19
28
  email:
20
29
  - jtannas@gmail.com
21
30
  executables: []
@@ -26,22 +35,36 @@ files:
26
35
  - ".rubocop.yml"
27
36
  - ".tool-versions"
28
37
  - CHANGELOG.md
29
- - CODE_OF_CONDUCT.md
38
+ - Guardfile
30
39
  - LICENSE.txt
31
40
  - README.md
32
41
  - Rakefile
33
- - SECURITY.md
42
+ - cspell.config.yaml
43
+ - docs/CODE_OF_CONDUCT.md
44
+ - docs/CONTRIBUTING.md
45
+ - docs/HASH_COMPARISON_DESIGN.md
46
+ - docs/PHILOSOPHY.md
47
+ - docs/SECURITY.md
34
48
  - lib/katachi.rb
49
+ - lib/katachi/any_of.rb
50
+ - lib/katachi/comparator.rb
51
+ - lib/katachi/comparator/compare_array.rb
52
+ - lib/katachi/comparator/compare_hash.rb
53
+ - lib/katachi/comparator/compare_kv.rb
54
+ - lib/katachi/comparison_result.rb
55
+ - lib/katachi/predefined_shapes.rb
56
+ - lib/katachi/rspec.rb
57
+ - lib/katachi/shapes.rb
35
58
  - lib/katachi/version.rb
36
- - sig/katachi.rbs
59
+ - renovate.json
37
60
  homepage: https://github.com/jtannas/katachi
38
61
  licenses:
39
62
  - MIT
40
63
  metadata:
41
64
  homepage_uri: https://github.com/jtannas/katachi
42
65
  source_code_uri: https://github.com/jtannas/katachi
43
- changelog_uri: https://github.com/jtannas/katachi/blob/main/CHANGELOG.md
44
- post_install_message:
66
+ changelog_uri: https://github.com/jtannas/katachi/releases
67
+ rubygems_mfa_required: 'true'
45
68
  rdoc_options: []
46
69
  require_paths:
47
70
  - lib
@@ -49,15 +72,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
72
  requirements:
50
73
  - - ">="
51
74
  - !ruby/object:Gem::Version
52
- version: 3.1.0
75
+ version: 3.2.0
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: '3.5'
53
79
  required_rubygems_version: !ruby/object:Gem::Requirement
54
80
  requirements:
55
81
  - - ">="
56
82
  - !ruby/object:Gem::Version
57
83
  version: '0'
58
84
  requirements: []
59
- rubygems_version: 3.3.27
60
- signing_key:
85
+ rubygems_version: 3.6.2
61
86
  specification_version: 4
62
- summary: An RSpec plugin for describing and testing APIs
87
+ summary: A tool for describing and validating objects as intuitively as possible.
63
88
  test_files: []
data/sig/katachi.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Katachi
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end
File without changes
File without changes