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.
- checksums.yaml +4 -4
- data/.rubocop.yml +35 -1
- data/.tool-versions +1 -1
- data/Guardfile +17 -0
- data/README.md +296 -28
- data/Rakefile +14 -0
- data/cspell.config.yaml +19 -0
- data/docs/CONTRIBUTING.md +35 -0
- data/docs/HASH_COMPARISON_DESIGN.md +244 -0
- data/docs/PHILOSOPHY.md +43 -0
- data/lib/katachi/any_of.rb +36 -0
- data/lib/katachi/comparator/compare_array.rb +65 -0
- data/lib/katachi/comparator/compare_hash.rb +174 -0
- data/lib/katachi/comparator/compare_kv.rb +56 -0
- data/lib/katachi/comparator.rb +43 -0
- data/lib/katachi/comparison_result.rb +99 -0
- data/lib/katachi/predefined_shapes.rb +5 -0
- data/lib/katachi/rspec.rb +22 -0
- data/lib/katachi/shapes.rb +24 -0
- data/lib/katachi/version.rb +1 -1
- data/lib/katachi.rb +11 -2
- data/renovate.json +6 -0
- metadata +41 -16
- data/sig/katachi.rbs +0 -4
- /data/{CODE_OF_CONDUCT.md → docs/CODE_OF_CONDUCT.md} +0 -0
- /data/{SECURITY.md → docs/SECURITY.md} +0 -0
@@ -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,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
|
data/lib/katachi/version.rb
CHANGED
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
|
-
|
7
|
-
|
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
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
|
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-
|
10
|
+
date: 2025-03-18 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: |
|
14
|
-
|
15
|
-
in regards to APIs.
|
13
|
+
A tool for describing and validating objects as intuitively as possible.
|
16
14
|
|
17
|
-
|
18
|
-
|
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
|
-
-
|
38
|
+
- Guardfile
|
30
39
|
- LICENSE.txt
|
31
40
|
- README.md
|
32
41
|
- Rakefile
|
33
|
-
-
|
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
|
-
-
|
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/
|
44
|
-
|
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.
|
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.
|
60
|
-
signing_key:
|
85
|
+
rubygems_version: 3.6.2
|
61
86
|
specification_version: 4
|
62
|
-
summary:
|
87
|
+
summary: A tool for describing and validating objects as intuitively as possible.
|
63
88
|
test_files: []
|
data/sig/katachi.rbs
DELETED
File without changes
|
File without changes
|