liminal 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 +7 -0
- data/CHANGELOG.md +12 -0
- data/CONTRIBUTING.md +26 -0
- data/LICENSE.txt +22 -0
- data/README.md +227 -0
- data/lib/liminal/adapters/dry_schema_v1.rb +115 -0
- data/lib/liminal/compiler.rb +373 -0
- data/lib/liminal/components.rb +90 -0
- data/lib/liminal/dialects/openapi_30/metadata_validator.rb +123 -0
- data/lib/liminal/dialects/openapi_30.rb +363 -0
- data/lib/liminal/errors.rb +59 -0
- data/lib/liminal/internal/normalizer.rb +247 -0
- data/lib/liminal/internal/schema_merge.rb +95 -0
- data/lib/liminal/path.rb +35 -0
- data/lib/liminal/predicate_registry.rb +235 -0
- data/lib/liminal/schema.rb +95 -0
- data/lib/liminal/version.rb +5 -0
- data/lib/liminal.rb +39 -0
- metadata +175 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7c8b9358afd48ae00ba175c322616177fea6b11b28b68039866960250ecac6ad
|
|
4
|
+
data.tar.gz: 7336f7cf8458cb1da841cc08b3647df2345cbea98849ed87dbca9f118bdb9de5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e2fb7bae0338e67a6971d6aa072e310be23c3dae0f948fbc91ff0b067135e99f51a667c700a3a08b5de4d769ea02e7b448adde5fb61d3d5d25ca886c4d66d5bd
|
|
7
|
+
data.tar.gz: 35b0ba788a0b564d7b0fb65e9e674653515f4117122c90321240ca5767d773cddad697488dd121fc12a5d53f89ed81c2aaaf04e4e681946da763608b4044bafd
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. The project follows Semantic Versioning.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 - Unreleased
|
|
6
|
+
|
|
7
|
+
- Add strict dry-schema 1.x AST compilation to OpenAPI 3.0 Schema Objects.
|
|
8
|
+
- Add exclusions, enrichment and authoritative overrides, and immutable predicate extensions.
|
|
9
|
+
- Add idempotent OpenAPI component registration with collision detection.
|
|
10
|
+
- Compile the standard dry-types boolean union emitted as `true?`/`false?` value predicates.
|
|
11
|
+
- Add strict Schema Object normalization and non-mutating `required`, `exactly_one`, and `all_of` composition.
|
|
12
|
+
- Reject ambiguous paths and keys, non-JSON numeric values, unsafe regular-expression anchors, and malformed metadata objects.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Liminal intentionally supports only declarative behavior visible in `Dry::Schema#to_ast`. Changes that infer or execute
|
|
4
|
+
application rules are outside the project boundary.
|
|
5
|
+
|
|
6
|
+
Install dependencies and run the default checks:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
bundle install
|
|
10
|
+
bundle exec rake
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run a dry-schema compatibility appraisal by selecting its Gemfile:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
BUNDLE_GEMFILE=gemfiles/dry_schema_1_13.gemfile bundle install
|
|
17
|
+
BUNDLE_GEMFILE=gemfiles/dry_schema_1_13.gemfile bundle exec rspec
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Repeat for `dry_schema_1_14`, `dry_schema_1_15`, and `dry_schema_1_16`. Tests use randomized order. Enable line and branch
|
|
21
|
+
coverage locally with `COVERAGE=true bundle exec rspec`.
|
|
22
|
+
|
|
23
|
+
Before proposing a new predicate, show that its semantics can be represented safely in OpenAPI 3.0 and add real
|
|
24
|
+
dry-schema AST coverage. Do not publish, release, or widen the supported dependency range without the maintainers'
|
|
25
|
+
explicit approval and a green compatibility matrix.
|
|
26
|
+
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Liminal contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Liminal
|
|
2
|
+
|
|
3
|
+
Liminal strictly compiles declarative `Dry::Schema#to_ast` structure and predicates into deterministic OpenAPI 3.0
|
|
4
|
+
Schema Objects. It is useful when a dry-schema definition is the application source of truth and silently dropping a
|
|
5
|
+
validation constraint would make generated API documentation misleading.
|
|
6
|
+
|
|
7
|
+
The Ruby API lives under `Liminal`; it does not use `Dry::` and is not an official dry-rb project.
|
|
8
|
+
|
|
9
|
+
## Compatibility
|
|
10
|
+
|
|
11
|
+
| Concern | Supported |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Ruby | 3.2 and newer |
|
|
14
|
+
| dry-schema | 1.13.4 through 1.16.x (`>= 1.13.4`, `< 1.17`) |
|
|
15
|
+
| OpenAPI | 3.0.x only |
|
|
16
|
+
| OpenAPI integration | Provided separately by the `liminal-openapi` companion gem |
|
|
17
|
+
|
|
18
|
+
The dependency matrix runs the same real dry-schema definitions against 1.13, 1.14, 1.15, and 1.16. OpenAPI 3.1 syntax,
|
|
19
|
+
including type arrays and numeric `exclusiveMinimum` values, is rejected.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Add the gem to your bundle:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem "liminal", "~> 0.1.0"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then run `bundle install` and require `liminal`.
|
|
30
|
+
|
|
31
|
+
## Compilation
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require "dry/schema"
|
|
35
|
+
require "liminal"
|
|
36
|
+
|
|
37
|
+
schema = Dry::Schema.Params do
|
|
38
|
+
required(:name).filled(:string)
|
|
39
|
+
optional(:score).filled(:float, gt?: 0)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
openapi_schema = Liminal.compile(
|
|
43
|
+
schema,
|
|
44
|
+
dialect: :openapi_3_0,
|
|
45
|
+
source_name: "CreateEventSchema"
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The result is a plain recursively string-keyed `Hash`. Required fields become `required`; optional fields do not.
|
|
50
|
+
Arrays always include `items`, empty `required` arrays are omitted, and stable AST order produces stable output.
|
|
51
|
+
|
|
52
|
+
Liminal depends on `dry-schema`, not `dry-validation`. Applications using contracts can expose a convenience method:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class Contracts::Base < Dry::Validation::Contract
|
|
56
|
+
def self.openapi_schema(**options)
|
|
57
|
+
Liminal.compile(schema, source_name: name, **options)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`Dry::Schema::Params` may coerce input strings before validation, while OpenAPI describes their post-coercion JSON
|
|
63
|
+
representation. Use `Dry::Schema::JSON` when differential tests should compare non-coercing behavior directly.
|
|
64
|
+
|
|
65
|
+
## Exclusions
|
|
66
|
+
|
|
67
|
+
Array paths are canonical and preserve property names containing dots:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
Liminal.compile(
|
|
71
|
+
schema,
|
|
72
|
+
exclude: [[:message_id], %i[data internal_metadata]]
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Exclusions happen before the field rule is compiled, remove the field from its parent's `required` list, and can conceal
|
|
77
|
+
an intentionally undocumented unsupported predicate. Unknown paths raise `Liminal::UnknownExclusionPathError`. Dotted
|
|
78
|
+
strings are accepted only as a convenience.
|
|
79
|
+
|
|
80
|
+
## Overrides
|
|
81
|
+
|
|
82
|
+
An enrichment override deep-merges documentation or representable structural details into a successfully generated field:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
overrides = {
|
|
86
|
+
%i[data attributes event_at] => {
|
|
87
|
+
"format" => "date",
|
|
88
|
+
"description" => "Calendar date"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`required` arrays are unioned during enrichment. An authoritative override may replace a field whose node or predicate
|
|
94
|
+
cannot be compiled, but it must contain one of `type`, `$ref`, `oneOf`, `anyOf`, or `allOf`:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
overrides = {
|
|
98
|
+
[:opaque_id] => {"type" => "string", "format" => "uuid"}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A description alone never hides an unsupported construct. Unknown paths raise `Liminal::UnknownOverridePathError`.
|
|
103
|
+
An override cannot target an excluded path or one of its descendants; contradictory path configuration raises
|
|
104
|
+
`Liminal::InvalidOverrideError`. A parent override may enrich a schema after one of its descendants is excluded.
|
|
105
|
+
OpenAPI 3.1-only keywords are rejected. OpenAPI 3.0 forbids `$ref` siblings, so use `allOf` when a reference also needs a
|
|
106
|
+
description:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
{ "allOf" => [{ "$ref" => "#/components/schemas/User" }], "description" => "User" }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Schema composition
|
|
113
|
+
|
|
114
|
+
`Liminal::Schema.normalize` is the public boundary for precompiled Schema
|
|
115
|
+
Objects. It copies the input, normalizes object keys, validates OpenAPI 3.0
|
|
116
|
+
keywords and nested values, and produces deterministic keyword order:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
normalized = Liminal::Schema.normalize({type: "string"})
|
|
120
|
+
required = Liminal::Schema.required(schema, :mobile_phone)
|
|
121
|
+
exclusive = Liminal::Schema.exactly_one(schema, :mobile_phone, :email)
|
|
122
|
+
combined = Liminal::Schema.all_of(base_schema, detail_schema)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Precompiled values must already be JSON-native: strings, booleans, `nil`,
|
|
126
|
+
integers, finite floats, arrays, and string- or symbol-keyed hashes. Ambiguous
|
|
127
|
+
string/symbol keys and Ruby-only scalar values fail validation instead of being
|
|
128
|
+
silently coerced.
|
|
129
|
+
|
|
130
|
+
`required` unions the named properties with an existing `required` array.
|
|
131
|
+
`exactly_one` emits one `oneOf` required-property branch per name and preserves
|
|
132
|
+
an existing `oneOf` as a separate `allOf` constraint. `all_of` keeps each input
|
|
133
|
+
as an independent branch. Applying a combinator to a `$ref` uses `allOf`, since
|
|
134
|
+
OpenAPI 3.0 forbids `$ref` siblings.
|
|
135
|
+
|
|
136
|
+
## Predicate extensions
|
|
137
|
+
|
|
138
|
+
The default registry is immutable. `with` returns a copy:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
predicates = Liminal::PredicateRegistry.default.with(:uuid_v4?) do |_arguments, context|
|
|
142
|
+
raise "unexpected dialect" unless context.dialect.name == :openapi_3_0
|
|
143
|
+
|
|
144
|
+
{"type" => "string", "format" => "uuid"}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
Liminal.compile(schema, predicates: predicates)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Handlers receive semantic arguments with dry input placeholders removed and a context containing the canonical path and
|
|
151
|
+
dialect. They must return a Hash. Duplicate registration requires `replace: true`; incompatible keywords and types are
|
|
152
|
+
rejected.
|
|
153
|
+
|
|
154
|
+
Built-in predicates cover arrays, booleans, dates and times, numbers, hashes, integers, strings, `type?` for the documented
|
|
155
|
+
Ruby primitive classes, equality and inclusion enums, safe regular expressions, filled and size constraints, numeric
|
|
156
|
+
boundaries, and integer parity. `and`, `or`, nullable implications, and `each` are compiled without exposing raw AST nodes.
|
|
157
|
+
Ruby `\A` and `\z` anchors are translated to their ECMA-262 equivalents. Raw `^`
|
|
158
|
+
and `$` anchors are rejected because their Ruby line semantics cannot be
|
|
159
|
+
preserved safely.
|
|
160
|
+
|
|
161
|
+
## Strict failures and custom rules
|
|
162
|
+
|
|
163
|
+
Unsupported AST nodes and predicates raise contextual `Liminal::UnsupportedNodeError` or
|
|
164
|
+
`Liminal::UnsupportedPredicateError`, including the source, canonical field path, and failing operation. Compilation never
|
|
165
|
+
has a permissive mode.
|
|
166
|
+
|
|
167
|
+
A dry-validation rule such as this is arbitrary Ruby and is not part of the declarative schema AST:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
rule("data.attributes.event_at") do
|
|
171
|
+
ValidateIso8601Date.call(date: value)
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Liminal does not execute, introspect, or pretend to translate such rules. Represent structural documentation explicitly
|
|
176
|
+
with an override. Database checks and cross-field business rules belong in endpoint prose rather than a Schema Object.
|
|
177
|
+
|
|
178
|
+
## Components
|
|
179
|
+
|
|
180
|
+
Register a generated component in any mutable OpenAPI document:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
reference = Liminal::Components.register(
|
|
184
|
+
document: openapi_document,
|
|
185
|
+
name: :calendar_event,
|
|
186
|
+
schema: openapi_schema
|
|
187
|
+
)
|
|
188
|
+
# => {"$ref"=>"#/components/schemas/calendar_event"}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Missing maps are created. A document may use either string or symbol container
|
|
192
|
+
keys, but declaring both logical forms is rejected as ambiguous. Component
|
|
193
|
+
names may contain only letters, digits, periods, hyphens, and underscores.
|
|
194
|
+
Re-registering the same normalized schema is idempotent; a different schema or
|
|
195
|
+
duplicate string/symbol component key raises `Liminal::ComponentCollisionError`.
|
|
196
|
+
Returned references are immutable.
|
|
197
|
+
|
|
198
|
+
## Rswag boundary
|
|
199
|
+
|
|
200
|
+
The core gem contains no Rails, Active Support, RSpec, or Rswag runtime dependency and performs no global monkey-patching.
|
|
201
|
+
Until a separately versioned Rswag companion is requested, an application-side opt-in helper can keep the integration at
|
|
202
|
+
the metadata boundary:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
def request_body_from_schema(schema:, component:, parameter_name:, source_name: nil, **options)
|
|
206
|
+
generated = Liminal.compile(schema, source_name: source_name, **options)
|
|
207
|
+
reference = Liminal::Components.register(
|
|
208
|
+
document: selected_openapi_document,
|
|
209
|
+
name: component,
|
|
210
|
+
schema: generated
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
parameter name: parameter_name, in: :body, required: true, schema: reference
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Application action-to-contract metadata remains outside Liminal. Request examples remain Rswag metadata and are not
|
|
218
|
+
embedded into structural schemas.
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
Run `bundle exec rake` for randomized RSpec and RuboCop checks, `COVERAGE=true bundle exec rspec` for line and branch
|
|
223
|
+
coverage, and `bundle exec rake build` to build the gem locally. The development suite validates a generated minimal
|
|
224
|
+
document with `openapi3_parser` and compares representative non-coercing payload behavior with `json_schemer`.
|
|
225
|
+
|
|
226
|
+
The project is versioned from `0.1.0` while its extension API is being proven. No release or publication is performed by
|
|
227
|
+
the implementation workflow.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liminal
|
|
4
|
+
module Adapters
|
|
5
|
+
# Converts the documented dry-schema 1.x AST into semantic nodes. No raw
|
|
6
|
+
# AST layout escapes this adapter.
|
|
7
|
+
class DrySchemaV1
|
|
8
|
+
SetNode = Struct.new(:children, keyword_init: true)
|
|
9
|
+
FieldNode = Struct.new(:name, :required, :rule, keyword_init: true)
|
|
10
|
+
PredicateNode = Struct.new(:name, :arguments, keyword_init: true)
|
|
11
|
+
AndNode = Struct.new(:children, keyword_init: true)
|
|
12
|
+
OrNode = Struct.new(:children, keyword_init: true)
|
|
13
|
+
NullableNode = Struct.new(:rule, keyword_init: true)
|
|
14
|
+
ImplicationNode = Struct.new(:left, :right, keyword_init: true)
|
|
15
|
+
NotNode = Struct.new(:rule, keyword_init: true)
|
|
16
|
+
EachNode = Struct.new(:rule, keyword_init: true)
|
|
17
|
+
KeyNode = Struct.new(:name, :rule, keyword_init: true)
|
|
18
|
+
UnknownNode = Struct.new(:operation, keyword_init: true)
|
|
19
|
+
|
|
20
|
+
def call(ast)
|
|
21
|
+
visit(ast)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def visit(ast)
|
|
27
|
+
return UnknownNode.new(operation: :invalid_ast) unless ast.is_a?(Array) && ast.length == 2
|
|
28
|
+
|
|
29
|
+
operation, payload = ast
|
|
30
|
+
case operation
|
|
31
|
+
when :set then SetNode.new(children: array_payload(payload).map { |child| visit(child) })
|
|
32
|
+
when :and then visit_and(payload)
|
|
33
|
+
when :or then OrNode.new(children: array_payload(payload).map { |child| visit(child) })
|
|
34
|
+
when :implication then visit_implication(payload)
|
|
35
|
+
when :not then NotNode.new(rule: visit(payload))
|
|
36
|
+
when :each then EachNode.new(rule: visit(payload))
|
|
37
|
+
when :key then visit_key(payload)
|
|
38
|
+
when :predicate then visit_predicate(payload)
|
|
39
|
+
else UnknownNode.new(operation: operation || :invalid_ast)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def visit_and(payload)
|
|
44
|
+
children = array_payload(payload)
|
|
45
|
+
field = field_from_wrapper(children, required: true)
|
|
46
|
+
field || AndNode.new(children: children.map { |child| visit(child) })
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def visit_implication(payload)
|
|
50
|
+
children = array_payload(payload)
|
|
51
|
+
field = field_from_wrapper(children, required: false)
|
|
52
|
+
return field if field
|
|
53
|
+
return NullableNode.new(rule: visit(children[1])) if nullable_guard?(children[0]) && children.length == 2
|
|
54
|
+
|
|
55
|
+
ImplicationNode.new(left: visit(children[0]), right: visit(children[1]))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def field_from_wrapper(children, required:)
|
|
59
|
+
return unless children.length == 2
|
|
60
|
+
|
|
61
|
+
name = key_presence_name(children[0])
|
|
62
|
+
key_ast = children[1]
|
|
63
|
+
return unless name && raw_operation(key_ast) == :key
|
|
64
|
+
|
|
65
|
+
key_name, rule_ast = key_ast[1]
|
|
66
|
+
return unless key_name.to_s == name.to_s
|
|
67
|
+
|
|
68
|
+
FieldNode.new(name: key_name.to_s, required: required, rule: visit(rule_ast))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def nullable_guard?(ast)
|
|
72
|
+
return false unless raw_operation(ast) == :not
|
|
73
|
+
|
|
74
|
+
predicate = ast[1]
|
|
75
|
+
raw_operation(predicate) == :predicate && predicate.dig(1, 0) == :nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def key_presence_name(ast)
|
|
79
|
+
return unless raw_operation(ast) == :predicate
|
|
80
|
+
|
|
81
|
+
name, tagged_arguments = ast[1]
|
|
82
|
+
return unless name == :key?
|
|
83
|
+
|
|
84
|
+
tagged_arguments.find { |tag, _value| tag == :name }&.last
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def visit_key(payload)
|
|
88
|
+
name, rule = payload
|
|
89
|
+
KeyNode.new(name: name.to_s, rule: visit(rule))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def visit_predicate(payload)
|
|
93
|
+
name, tagged_arguments = payload
|
|
94
|
+
arguments = Array(tagged_arguments).filter_map do |tag, value|
|
|
95
|
+
value unless input_placeholder?(name, tag)
|
|
96
|
+
end
|
|
97
|
+
PredicateNode.new(name: name.to_sym, arguments: arguments.freeze)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def input_placeholder?(predicate, tag)
|
|
101
|
+
tag == :input ||
|
|
102
|
+
(predicate == :eql? && tag == :right) ||
|
|
103
|
+
(%i[true? false?].include?(predicate) && tag == :value)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def raw_operation(ast)
|
|
107
|
+
ast[0] if ast.is_a?(Array)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def array_payload(payload)
|
|
111
|
+
payload.is_a?(Array) ? payload : []
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|