Module: Ast::Merge::ProviderContract
- Defined in:
- lib/ast/merge/provider_contract.rb,
sig/ast/merge.rbs
Overview
Validation and normalization for the portable merge-provider protocol.
rubocop:disable Metrics/ModuleLength -- protocol constants and validators form one public contract
Defined Under Namespace
Classes: DuplicateProviderError, Error, InvalidProviderError, InvalidRequestError, InvalidResultError
Constant Summary
collapse
- OPERATIONS =
%i[analyze diff2 merge2 merge3].freeze
- REQUIRED_METHODS =
%i[provider_id family capabilities analyze diff2 merge2 merge3].freeze
- REQUEST_ROLES =
{
analyze: %i[source],
diff2: %i[before_source after_source],
merge2: %i[incoming_source current_source],
merge3: %i[base_source ours_source theirs_source]
}.freeze
- REQUIRED_RESULT_FIELDS =
%i[
schema
operation
ok
provider
profile
diagnostics
changes
conflicts
fallbacks
render_report
verification
].freeze
- REQUIRED_CAPABILITY_FIELDS =
%i[
operations
dialects
backends
profiles
role
source_preservation
].freeze
- RESULT_SCHEMA =
'https://structuredmerge.org/schemas/provider-result/v1.json'
- PROVIDER_ROLES =
%i[workflow backend].freeze
Class Method Summary
collapse
-
.blank?(value) ⇒ Boolean
-
.normalize_hash(value) ⇒ Object
-
.normalize_identifier(value) ⇒ Object
-
.normalize_identifiers(values) ⇒ Object
-
.normalize_operation(operation) ⇒ Object
-
.normalized_capabilities(capabilities, operations:, role:) ⇒ Object
-
.provider_registration(provider_id, family, capabilities, operations:, role:) ⇒ Object
-
.truthy_key?(value, key) ⇒ Boolean
-
.validate_capability_fields!(capabilities) ⇒ Object
-
.validate_operations!(values) ⇒ Object
-
.validate_provider!(provider) ⇒ Object
-
.validate_provider_identity!(provider) ⇒ Object
-
.validate_provider_methods!(provider) ⇒ Object
-
.validate_request!(operation, request) ⇒ Object
-
.validate_request_roles!(operation, request) ⇒ Object
-
.validate_result!(operation, result) ⇒ Object
-
.validate_result_fields!(operation, result) ⇒ Object
-
.validate_result_operation!(expected, actual) ⇒ Object
-
.validate_result_outcome!(operation, result) ⇒ Object
-
.validate_role!(value) ⇒ Object
Class Method Details
.blank?(value) ⇒ Boolean
105
106
107
|
# File 'lib/ast/merge/provider_contract.rb', line 105
def blank?(value)
value.nil? || value.to_s.strip.empty?
end
|
.normalize_hash(value) ⇒ Object
88
89
90
91
92
93
94
95
|
# File 'lib/ast/merge/provider_contract.rb', line 88
def normalize_hash(value)
raise ArgumentError, "Expected Hash, got #{value.class}" unless value.is_a?(Hash)
value.to_h do |key, item|
normalized_key = key.respond_to?(:to_sym) ? key.to_sym : key
[normalized_key, item]
end
end
|
.normalize_identifier(value) ⇒ Object
97
98
99
|
# File 'lib/ast/merge/provider_contract.rb', line 97
def normalize_identifier(value)
value.to_s.strip
end
|
.normalize_identifiers(values) ⇒ Object
101
102
103
|
# File 'lib/ast/merge/provider_contract.rb', line 101
def normalize_identifiers(values)
Array(values).map { |value| normalize_identifier(value).to_sym }.uniq.freeze
end
|
.normalize_operation(operation) ⇒ Object
79
80
81
82
83
84
85
86
|
# File 'lib/ast/merge/provider_contract.rb', line 79
def normalize_operation(operation)
normalized = normalize_identifier(operation).to_sym
unless OPERATIONS.include?(normalized)
raise InvalidRequestError, "Unknown provider operation: #{operation.inspect}"
end
normalized
end
|
.normalized_capabilities(capabilities, operations:, role:) ⇒ Object
150
151
152
153
154
155
156
157
158
|
# File 'lib/ast/merge/provider_contract.rb', line 150
def normalized_capabilities(capabilities, operations:, role:)
capabilities.merge(
operations: operations,
dialects: normalize_identifiers(capabilities[:dialects]),
backends: normalize_identifiers(capabilities[:backends]),
profiles: normalize_identifiers(capabilities[:profiles]),
role: role
).freeze
end
|
.provider_registration(provider_id, family, capabilities, operations:, role:) ⇒ Object
160
161
162
163
164
165
166
|
# File 'lib/ast/merge/provider_contract.rb', line 160
def provider_registration(provider_id, family, capabilities, operations:, role:)
{
provider_id: provider_id,
family: family,
capabilities: normalized_capabilities(capabilities, operations: operations, role: role)
}.freeze
end
|
.truthy_key?(value, key) ⇒ Boolean
109
110
111
|
# File 'lib/ast/merge/provider_contract.rb', line 109
def truthy_key?(value, key)
value.is_a?(Hash) && (value[key] == true || value[key.to_s] == true)
end
|
.validate_capability_fields!(capabilities) ⇒ Object
127
128
129
130
|
# File 'lib/ast/merge/provider_contract.rb', line 127
def validate_capability_fields!(capabilities)
missing = REQUIRED_CAPABILITY_FIELDS.reject { |field| capabilities.key?(field) }
raise InvalidProviderError, "Provider capabilities are missing: #{missing.join(', ')}" unless missing.empty?
end
|
.validate_operations!(values) ⇒ Object
132
133
134
135
136
137
138
139
140
141
|
# File 'lib/ast/merge/provider_contract.rb', line 132
def validate_operations!(values)
operations = normalize_identifiers(values)
unknown = operations - OPERATIONS
raise InvalidProviderError, "Unknown provider operations: #{unknown.join(', ')}" unless unknown.empty?
unless operations.sort == OPERATIONS.sort
raise InvalidProviderError, "Provider must implement all operations: #{OPERATIONS.join(', ')}"
end
operations
end
|
.validate_provider!(provider) ⇒ Object
48
49
50
51
52
53
54
55
56
57
|
# File 'lib/ast/merge/provider_contract.rb', line 48
def validate_provider!(provider)
validate_provider_methods!(provider)
provider_id, family = validate_provider_identity!(provider)
capabilities = normalize_hash(provider.capabilities)
validate_capability_fields!(capabilities)
operations = validate_operations!(capabilities[:operations])
role = validate_role!(capabilities[:role])
provider_registration(provider_id, family, capabilities, operations: operations, role: role)
end
|
.validate_provider_identity!(provider) ⇒ Object
118
119
120
121
122
123
124
125
|
# File 'lib/ast/merge/provider_contract.rb', line 118
def validate_provider_identity!(provider)
provider_id = normalize_identifier(provider.provider_id)
family = normalize_identifier(provider.family)
raise InvalidProviderError, 'provider_id must not be empty' if provider_id.empty?
raise InvalidProviderError, 'family must not be empty' if family.empty?
[provider_id, family.to_sym]
end
|
.validate_provider_methods!(provider) ⇒ Object
113
114
115
116
|
# File 'lib/ast/merge/provider_contract.rb', line 113
def validate_provider_methods!(provider)
missing = REQUIRED_METHODS.reject { |method_name| provider.respond_to?(method_name) }
raise InvalidProviderError, "Provider is missing required methods: #{missing.join(', ')}" unless missing.empty?
end
|
.validate_request!(operation, request) ⇒ Object
59
60
61
62
63
64
65
66
67
68
|
# File 'lib/ast/merge/provider_contract.rb', line 59
def validate_request!(operation, request)
operation = normalize_operation(operation)
normalized = normalize_hash(request)
validate_request_roles!(operation, normalized)
if normalized[:provider_id].nil? && blank?(normalized[:family])
raise InvalidRequestError, "#{operation} request requires family or provider_id"
end
normalized.freeze
end
|
.validate_request_roles!(operation, request) ⇒ Object
168
169
170
171
|
# File 'lib/ast/merge/provider_contract.rb', line 168
def validate_request_roles!(operation, request)
missing = REQUEST_ROLES.fetch(operation).select { |role| !request.key?(role) || request[role].nil? }
raise InvalidRequestError, "#{operation} request is missing roles: #{missing.join(', ')}" unless missing.empty?
end
|
.validate_result!(operation, result) ⇒ Object
70
71
72
73
74
75
76
77
|
# File 'lib/ast/merge/provider_contract.rb', line 70
def validate_result!(operation, result)
operation = normalize_operation(operation)
normalized = normalize_hash(result)
validate_result_fields!(operation, normalized)
validate_result_operation!(operation, normalized[:operation])
validate_result_outcome!(operation, normalized)
normalized.freeze
end
|
.validate_result_fields!(operation, result) ⇒ Object
173
174
175
176
|
# File 'lib/ast/merge/provider_contract.rb', line 173
def validate_result_fields!(operation, result)
missing = REQUIRED_RESULT_FIELDS.reject { |field| result.key?(field) }
raise InvalidResultError, "#{operation} result is missing fields: #{missing.join(', ')}" unless missing.empty?
end
|
.validate_result_operation!(expected, actual) ⇒ Object
178
179
180
181
182
|
# File 'lib/ast/merge/provider_contract.rb', line 178
def validate_result_operation!(expected, actual)
return if normalize_identifier(actual).to_sym == expected
raise InvalidResultError, "Expected #{expected} result, got #{actual.inspect}"
end
|
.validate_result_outcome!(operation, result) ⇒ Object
184
185
186
187
188
189
190
191
192
|
# File 'lib/ast/merge/provider_contract.rb', line 184
def validate_result_outcome!(operation, result)
unless [true, false].include?(result[:ok])
raise InvalidResultError, "#{operation} result ok must be true or false"
end
return unless operation == :merge3 && result[:ok]
return if truthy_key?(result[:verification], :base_participated)
raise InvalidResultError, 'Successful merge3 result must verify base_participated'
end
|
.validate_role!(value) ⇒ Object
143
144
145
146
147
148
|
# File 'lib/ast/merge/provider_contract.rb', line 143
def validate_role!(value)
role = normalize_identifier(value).to_sym
raise InvalidProviderError, "Unknown provider role: #{role}" unless PROVIDER_ROLES.include?(role)
role
end
|