-
Notifications
You must be signed in to change notification settings - Fork 1
Add transport-agnostic engine contracts and adapters #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Validates webhook signatures for adapters. | ||
| /// </summary> | ||
| public interface IWebhookSignatureValidator | ||
| { | ||
| /// <summary> | ||
| /// Validates the signature for the provided request. | ||
| /// </summary> | ||
| bool Validate(ModuleEngineSecurity security, IReadOnlyDictionary<string, string> headers, string rawBody, string? providedSignature); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Raw webhook request envelope understood by the transport adapters. | ||
| /// </summary> | ||
| /// <param name="Provider">Webhook provider identifier.</param> | ||
| /// <param name="EventType">Webhook event type.</param> | ||
| /// <param name="Headers">Raw headers supplied by the gateway.</param> | ||
| /// <param name="RawBody">Raw body text for signature validation.</param> | ||
| /// <param name="IdempotencyKey">Idempotency key supplied by provider.</param> | ||
| /// <param name="Attempt">Delivery attempt number.</param> | ||
| /// <param name="Signature">Optional supplied signature.</param> | ||
| /// <param name="Payload">Parsed payload DTO.</param> | ||
| public sealed record WebhookAdapterRequest<TPayload>( | ||
| string Provider, | ||
| string EventType, | ||
| IReadOnlyDictionary<string, string> Headers, | ||
| string RawBody, | ||
| string IdempotencyKey, | ||
| int Attempt, | ||
| string? Signature, | ||
| TPayload Payload); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Adapter response that can be mapped to HTTP or queue responses. | ||
| /// </summary> | ||
| /// <param name="Outcome">Outcome the transport should emit.</param> | ||
| /// <param name="Reason">Optional reason for retries.</param> | ||
| public sealed record WebhookAdapterResponse(WebhookOutcomeType Outcome, string? Reason = null); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,66 @@ | ||||||
| // Copyright (c) Bravellian | ||||||
| // | ||||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| // you may not use this file except in compliance with the License. | ||||||
| // You may obtain a copy of the License at | ||||||
| // | ||||||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||||||
| // | ||||||
| // Unless required by applicable law or agreed to in writing, software | ||||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
| // See the License for the specific language governing permissions and | ||||||
| // limitations under the License. | ||||||
|
|
||||||
| namespace Bravellian.Platform.Modularity; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Adapter that connects webhook engines to HTTP-style transports. | ||||||
| /// </summary> | ||||||
| public sealed class WebhookEngineAdapter | ||||||
| { | ||||||
| private readonly ModuleEngineDiscoveryService discovery; | ||||||
| private readonly IServiceProvider services; | ||||||
| private readonly IWebhookSignatureValidator signatureValidator; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="WebhookEngineAdapter"/> class. | ||||||
| /// </summary> | ||||||
| public WebhookEngineAdapter(ModuleEngineDiscoveryService discovery, IServiceProvider services, IWebhookSignatureValidator signatureValidator) | ||||||
| { | ||||||
| this.discovery = discovery; | ||||||
| this.services = services; | ||||||
| this.signatureValidator = signatureValidator; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Dispatches a webhook request to a registered engine. | ||||||
| /// </summary> | ||||||
| public async Task<WebhookAdapterResponse> DispatchAsync<TPayload>(WebhookAdapterRequest<TPayload> request, CancellationToken cancellationToken) | ||||||
| { | ||||||
| var descriptor = discovery.ResolveWebhookEngine(request.Provider, request.EventType) | ||||||
| ?? throw new InvalidOperationException($"No webhook engine registered for provider '{request.Provider}' and event '{request.EventType}'."); | ||||||
|
|
||||||
| if (descriptor.Manifest.Security is { } security) | ||||||
| { | ||||||
| if (!signatureValidator.Validate(security, request.Headers, request.RawBody, request.Signature)) | ||||||
| { | ||||||
| return new WebhookAdapterResponse(WebhookOutcomeType.Retry, "Signature validation failed"); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (string.IsNullOrWhiteSpace(request.IdempotencyKey) && descriptor.Manifest.Security?.IdempotencyWindow is not null) | ||||||
| { | ||||||
| return new WebhookAdapterResponse(WebhookOutcomeType.Retry, "Missing idempotency key"); | ||||||
| } | ||||||
|
Comment on lines
+44
to
+55
|
||||||
|
|
||||||
| var engine = discovery.ResolveEngine(descriptor, services) as IWebhookEngine<TPayload> | ||||||
| ?? throw new InvalidOperationException($"Engine '{descriptor.Manifest.Id}' does not implement expected webhook contract."); | ||||||
|
|
||||||
| var outcome = await engine.HandleAsync( | ||||||
| new WebhookRequest<TPayload>(request.Provider, request.EventType, request.Payload, request.IdempotencyKey, request.Attempt), | ||||||
| cancellationToken).ConfigureAwait(false); | ||||||
|
|
||||||
| return new WebhookAdapterResponse(outcome.Outcome, outcome.Reason); | ||||||
|
||||||
| return new WebhookAdapterResponse(outcome.Outcome, outcome.Reason); | |
| return new WebhookAdapterResponse(outcome.Outcome, outcome.Reason, outcome.EnqueuedEvent); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Supported engine types. Engines are framework-agnostic and must not depend on transport concerns. | ||
| /// </summary> | ||
| public enum EngineKind | ||
| { | ||
| /// <summary> | ||
| /// UI-first engines that produce view models and navigation outcomes. | ||
| /// </summary> | ||
| Ui, | ||
|
|
||
| /// <summary> | ||
| /// Webhook engines that react to external callbacks. | ||
| /// </summary> | ||
| Webhook, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Marker interface for modules that expose engines. | ||
| /// </summary> | ||
| public interface IEngineModule | ||
| { | ||
| /// <summary> | ||
| /// Provides engine descriptors for the module. | ||
| /// </summary> | ||
| IEnumerable<ModuleEngineDescriptor> DescribeEngines(); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||
| // Copyright (c) Bravellian | ||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||||||
| // you may not use this file except in compliance with the License. | ||||||||||||||||||||||||||
| // You may obtain a copy of the License at | ||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||
| // Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||
| // See the License for the specific language governing permissions and | ||||||||||||||||||||||||||
| // limitations under the License. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| namespace Bravellian.Platform.Modularity; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||
| /// Generic UI engine contract that operates on DTOs and produces view models. | ||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||
| /// <typeparam name="TInput">Input DTO.</typeparam> | ||||||||||||||||||||||||||
| /// <typeparam name="TViewModel">View model output.</typeparam> | ||||||||||||||||||||||||||
| public interface IUiEngine<TInput, TViewModel> | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||
| /// Executes the engine using the provided command DTO. | ||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||
| /// <param name="command">Command DTO.</param> | ||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||
| /// <returns>A view model and any navigation tokens emitted.</returns> | ||||||||||||||||||||||||||
| Task<UiEngineResult<TViewModel>> ExecuteAsync(TInput command, CancellationToken cancellationToken); | ||||||||||||||||||||||||||
|
Comment on lines
+25
to
+30
|
||||||||||||||||||||||||||
| /// Executes the engine using the provided command DTO. | |
| /// </summary> | |
| /// <param name="command">Command DTO.</param> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| /// <returns>A view model and any navigation tokens emitted.</returns> | |
| Task<UiEngineResult<TViewModel>> ExecuteAsync(TInput command, CancellationToken cancellationToken); | |
| /// Executes the engine using the provided input DTO. | |
| /// </summary> | |
| /// <param name="input">Input DTO.</param> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| /// <returns>A view model and any navigation tokens emitted.</returns> | |
| Task<UiEngineResult<TViewModel>> ExecuteAsync(TInput input, CancellationToken cancellationToken); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Webhook engine contract. Engines decide the outcome without transport coupling. | ||
| /// </summary> | ||
| /// <typeparam name="TPayload">Webhook payload type.</typeparam> | ||
| public interface IWebhookEngine<TPayload> | ||
| { | ||
| /// <summary> | ||
| /// Handles a webhook request. | ||
| /// </summary> | ||
| Task<WebhookOutcome> HandleAsync(WebhookRequest<TPayload> request, CancellationToken cancellationToken); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Adapter-level hints used by hosts to wire up engines to transports. | ||
| /// </summary> | ||
| /// <param name="RequiresRawRequestBody">True if the adapter must expose the raw request body to the engine.</param> | ||
| /// <param name="RequiresRawHeaders">True if the adapter must expose raw headers.</param> | ||
| /// <param name="SupportsChallengeResponses">True if the adapter should support verification/challenge responses.</param> | ||
| /// <param name="RequiresAuthenticatedUser">True if the adapter must enforce authentication.</param> | ||
| /// <param name="RequiresTenantContext">True if the adapter must enforce tenancy.</param> | ||
| public sealed record ModuleEngineAdapterHints( | ||
| bool RequiresRawRequestBody = false, | ||
| bool RequiresRawHeaders = false, | ||
| bool SupportsChallengeResponses = false, | ||
| bool RequiresAuthenticatedUser = false, | ||
| bool RequiresTenantContext = false); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Declares the actions and events an engine can process. | ||
| /// </summary> | ||
| /// <param name="Actions">Named commands/actions the engine supports.</param> | ||
| /// <param name="Events">Events emitted by the engine.</param> | ||
| /// <param name="SupportsAsync">Indicates async execution is supported.</param> | ||
| /// <param name="SupportsStreaming">Indicates streaming updates are supported.</param> | ||
| public sealed record ModuleEngineCapabilities( | ||
| IReadOnlyCollection<string> Actions, | ||
| IReadOnlyCollection<string> Events, | ||
| bool SupportsAsync = true, | ||
| bool SupportsStreaming = false); | ||
|
Comment on lines
+23
to
+28
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Copyright (c) Bravellian | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| namespace Bravellian.Platform.Modularity; | ||
|
|
||
| /// <summary> | ||
| /// Compatibility metadata for engine evolution. | ||
| /// </summary> | ||
| /// <param name="MinHostVersion">Minimum host version required.</param> | ||
| /// <param name="BreakingChanges">Human-readable notes for breaking changes.</param> | ||
| public sealed record ModuleEngineCompatibility(string? MinHostVersion, string? BreakingChanges); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 'if' statements can be combined.