katachi 0.0.0.1 → 0.0.1.1

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.1"
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,32 @@
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.1
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
+ == Description
16
14
 
17
- Inspired by [RSwag](https://github.com/rswag/rswag) and the challenges
18
- it faces with OpenAPI.
15
+ A tool for describing and validating objects as intuitively as possible.
16
+
17
+ ```ruby
18
+ shape = {
19
+ :$guid => {
20
+ email: :$email,
21
+ first_name: String,
22
+ last_name: String,
23
+ preferred_name: AnyOf[String, nil],
24
+ admin_only_information: AnyOf[Symbol => String, :$undefined],
25
+ Symbol => Object,
26
+ },
27
+ }
28
+ expect(api_response.body).to have_shape(shape)
29
+ ```
19
30
  email:
20
31
  - jtannas@gmail.com
21
32
  executables: []
@@ -26,22 +37,36 @@ files:
26
37
  - ".rubocop.yml"
27
38
  - ".tool-versions"
28
39
  - CHANGELOG.md
29
- - CODE_OF_CONDUCT.md
40
+ - Guardfile
30
41
  - LICENSE.txt
31
42
  - README.md
32
43
  - Rakefile
33
- - SECURITY.md
44
+ - cspell.config.yaml
45
+ - docs/CODE_OF_CONDUCT.md
46
+ - docs/CONTRIBUTING.md
47
+ - docs/HASH_COMPARISON_DESIGN.md
48
+ - docs/PHILOSOPHY.md
49
+ - docs/SECURITY.md
34
50
  - lib/katachi.rb
51
+ - lib/katachi/any_of.rb
52
+ - lib/katachi/comparator.rb
53
+ - lib/katachi/comparator/compare_array.rb
54
+ - lib/katachi/comparator/compare_hash.rb
55
+ - lib/katachi/comparator/compare_kv.rb
56
+ - lib/katachi/comparison_result.rb
57
+ - lib/katachi/predefined_shapes.rb
58
+ - lib/katachi/rspec.rb
59
+ - lib/katachi/shapes.rb
35
60
  - lib/katachi/version.rb
36
- - sig/katachi.rbs
61
+ - renovate.json
37
62
  homepage: https://github.com/jtannas/katachi
38
63
  licenses:
39
64
  - MIT
40
65
  metadata:
41
66
  homepage_uri: https://github.com/jtannas/katachi
42
67
  source_code_uri: https://github.com/jtannas/katachi
43
- changelog_uri: https://github.com/jtannas/katachi/blob/main/CHANGELOG.md
44
- post_install_message:
68
+ changelog_uri: https://github.com/jtannas/katachi/releases
69
+ rubygems_mfa_required: 'true'
45
70
  rdoc_options: []
46
71
  require_paths:
47
72
  - lib
@@ -49,15 +74,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
74
  requirements:
50
75
  - - ">="
51
76
  - !ruby/object:Gem::Version
52
- version: 3.1.0
77
+ version: 3.2.0
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '3.5'
53
81
  required_rubygems_version: !ruby/object:Gem::Requirement
54
82
  requirements:
55
83
  - - ">="
56
84
  - !ruby/object:Gem::Version
57
85
  version: '0'
58
86
  requirements: []
59
- rubygems_version: 3.3.27
60
- signing_key:
87
+ rubygems_version: 3.6.2
61
88
  specification_version: 4
62
- summary: An RSpec plugin for describing and testing APIs
89
+ summary: A tool for describing and validating objects as intuitively as possible.
63
90
  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