tasker-engine 1.0.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/LICENSE +22 -0
- data/README.md +443 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/diagram/edge.rb +106 -0
- data/app/models/tasker/diagram/flowchart.rb +137 -0
- data/app/models/tasker/diagram/node.rb +99 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +424 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_diagram.rb +332 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +94 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/app/views/tasker/task/_diagram.html.erb +32 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2277 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1780 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2665 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +552 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_DIAGRAM.md +252 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1518 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2575 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +304 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +452 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2375 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +541 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +541 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +9182 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +74 -0
- data/lib/tasker/authorization/resource_registry.rb +143 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +322 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +324 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +368 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +7 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +302 -0
- metadata +958 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tasker
|
4
|
+
module Health
|
5
|
+
# StatusChecker provides comprehensive system status information
|
6
|
+
#
|
7
|
+
# Gathers detailed metrics about tasks, workflow steps, and system health.
|
8
|
+
# Uses Rails caching to avoid expensive database queries on frequent requests.
|
9
|
+
#
|
10
|
+
# @example Basic usage
|
11
|
+
# checker = Tasker::Health::StatusChecker.new
|
12
|
+
# status = checker.check
|
13
|
+
# puts status[:healthy]
|
14
|
+
#
|
15
|
+
# @example Force refresh (bypass cache)
|
16
|
+
# status = checker.check(force_refresh: true)
|
17
|
+
class StatusChecker
|
18
|
+
# @return [Integer] Cache duration in seconds
|
19
|
+
attr_reader :cache_duration
|
20
|
+
|
21
|
+
# Cache key for status data
|
22
|
+
CACHE_KEY = 'tasker:health:status'
|
23
|
+
|
24
|
+
# Initialize status checker
|
25
|
+
#
|
26
|
+
# @param cache_duration [Integer] Cache duration in seconds (default from config)
|
27
|
+
def initialize(cache_duration: nil)
|
28
|
+
@cache_duration = cache_duration || Tasker.configuration.health.cache_duration_seconds
|
29
|
+
end
|
30
|
+
|
31
|
+
# Class method for status check
|
32
|
+
#
|
33
|
+
# @return [Hash] Comprehensive status information
|
34
|
+
def self.status
|
35
|
+
new.check
|
36
|
+
end
|
37
|
+
|
38
|
+
# Perform comprehensive status check
|
39
|
+
#
|
40
|
+
# @param force_refresh [Boolean] Whether to bypass cache and force fresh data
|
41
|
+
# @return [Hash] Comprehensive status information
|
42
|
+
# - :healthy [Boolean] Overall system health
|
43
|
+
# - :status [String] Status description
|
44
|
+
# - :data [Hash] Detailed metrics
|
45
|
+
# - :cached [Boolean] Whether data came from cache
|
46
|
+
# - :timestamp [Time] When data was generated
|
47
|
+
def check(force_refresh: false)
|
48
|
+
if force_refresh
|
49
|
+
result = generate_status_data
|
50
|
+
result[:cached] = false
|
51
|
+
result
|
52
|
+
else
|
53
|
+
begin
|
54
|
+
# Check if we have cached data first
|
55
|
+
cached_data = Rails.cache.read(CACHE_KEY)
|
56
|
+
if cached_data.nil?
|
57
|
+
# No cached data, generate fresh and cache it
|
58
|
+
result = generate_status_data
|
59
|
+
# Create a copy for caching (without the cached flag)
|
60
|
+
cache_data = result.dup
|
61
|
+
cache_data.delete(:cached)
|
62
|
+
Rails.cache.write(CACHE_KEY, cache_data, expires_in: @cache_duration.seconds)
|
63
|
+
result[:cached] = false
|
64
|
+
else
|
65
|
+
# Return cached data with cached flag set
|
66
|
+
result = cached_data.dup
|
67
|
+
result[:cached] = true
|
68
|
+
end
|
69
|
+
result
|
70
|
+
rescue StandardError => e
|
71
|
+
# If cache fails, generate fresh data
|
72
|
+
result = generate_status_data
|
73
|
+
result[:cached] = false
|
74
|
+
result[:cache_error] = "Cache error: #{e.message}"
|
75
|
+
result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Generate fresh status data
|
83
|
+
#
|
84
|
+
# @return [Hash] Fresh status information
|
85
|
+
def generate_status_data
|
86
|
+
start_time = Time.current
|
87
|
+
|
88
|
+
begin
|
89
|
+
# Get health metrics from SQL function
|
90
|
+
health_data = Tasker::Functions::FunctionBasedSystemHealthCounts.call
|
91
|
+
|
92
|
+
# Determine overall health status
|
93
|
+
healthy = assess_system_health(health_data)
|
94
|
+
|
95
|
+
formatted_data = format_health_data(health_data)
|
96
|
+
|
97
|
+
{
|
98
|
+
healthy: healthy,
|
99
|
+
status: healthy ? 'healthy' : 'unhealthy',
|
100
|
+
metrics: formatted_data[:metrics],
|
101
|
+
database: formatted_data[:database],
|
102
|
+
cached: false,
|
103
|
+
timestamp: start_time,
|
104
|
+
generation_duration: Time.current - start_time
|
105
|
+
}
|
106
|
+
rescue StandardError => e
|
107
|
+
{
|
108
|
+
healthy: false,
|
109
|
+
status: 'error',
|
110
|
+
error: e.message,
|
111
|
+
error_class: e.class.name,
|
112
|
+
cached: false,
|
113
|
+
timestamp: start_time,
|
114
|
+
generation_duration: Time.current - start_time
|
115
|
+
}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Assess overall system health based on metrics
|
120
|
+
#
|
121
|
+
# @param health_data [FunctionBasedSystemHealthCounts::HealthMetrics] Health metrics from SQL function
|
122
|
+
# @return [Boolean] Whether system is healthy
|
123
|
+
def assess_system_health(health_data)
|
124
|
+
# System is healthy if:
|
125
|
+
# 1. Database is responding (we got data)
|
126
|
+
# 2. Connection utilization is reasonable (< 90%)
|
127
|
+
# 3. No excessive error accumulation (< 50% error rate)
|
128
|
+
|
129
|
+
return false if health_data.nil?
|
130
|
+
|
131
|
+
# Check database connection utilization
|
132
|
+
if health_data.max_connections.positive?
|
133
|
+
connection_utilization = (health_data.active_connections.to_f / health_data.max_connections) * 100
|
134
|
+
return false if connection_utilization > 90.0
|
135
|
+
end
|
136
|
+
|
137
|
+
# Check task error rate (only if we have tasks)
|
138
|
+
if health_data.total_tasks.positive?
|
139
|
+
task_error_rate = (health_data.error_tasks.to_f / health_data.total_tasks) * 100
|
140
|
+
return false if task_error_rate > 50.0
|
141
|
+
end
|
142
|
+
|
143
|
+
# Check step error rate (only if we have steps)
|
144
|
+
if health_data.total_steps.positive?
|
145
|
+
step_error_rate = (health_data.error_steps.to_f / health_data.total_steps) * 100
|
146
|
+
return false if step_error_rate > 50.0
|
147
|
+
end
|
148
|
+
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Format health data for API response
|
153
|
+
#
|
154
|
+
# @param health_data [FunctionBasedSystemHealthCounts::HealthMetrics] Raw health metrics
|
155
|
+
# @return [Hash] Formatted health data with :metrics and :database keys
|
156
|
+
def format_health_data(health_data)
|
157
|
+
{
|
158
|
+
metrics: {
|
159
|
+
tasks: {
|
160
|
+
total: health_data.total_tasks,
|
161
|
+
pending: health_data.pending_tasks,
|
162
|
+
in_progress: health_data.in_progress_tasks,
|
163
|
+
complete: health_data.complete_tasks,
|
164
|
+
error: health_data.error_tasks,
|
165
|
+
cancelled: health_data.cancelled_tasks
|
166
|
+
},
|
167
|
+
steps: {
|
168
|
+
total: health_data.total_steps,
|
169
|
+
pending: health_data.pending_steps,
|
170
|
+
in_progress: health_data.in_progress_steps,
|
171
|
+
complete: health_data.complete_steps,
|
172
|
+
error: health_data.error_steps
|
173
|
+
},
|
174
|
+
retries: {
|
175
|
+
retryable_errors: health_data.retryable_error_steps,
|
176
|
+
exhausted_retries: health_data.exhausted_retry_steps,
|
177
|
+
in_backoff: health_data.in_backoff_steps
|
178
|
+
}
|
179
|
+
},
|
180
|
+
database: {
|
181
|
+
active_connections: health_data.active_connections,
|
182
|
+
max_connections: health_data.max_connections,
|
183
|
+
connection_utilization: calculate_utilization_percentage(
|
184
|
+
health_data.active_connections,
|
185
|
+
health_data.max_connections
|
186
|
+
) # Return as percentage (e.g., 5.0 for 5%)
|
187
|
+
}
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
# Calculate connection utilization percentage
|
192
|
+
#
|
193
|
+
# @param active [Integer] Active connections
|
194
|
+
# @param max [Integer] Maximum connections
|
195
|
+
# @return [Float] Utilization percentage
|
196
|
+
def calculate_utilization_percentage(active, max)
|
197
|
+
return 0.0 if max.zero?
|
198
|
+
|
199
|
+
((active.to_f / max) * 100).round(2)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
module Tasker
|
7
|
+
# Base identity strategy class that generates a GUID by default
|
8
|
+
#
|
9
|
+
# Identity strategies are used to generate unique identifiers for tasks.
|
10
|
+
# This allows for customizing how task identity is determined and tracked.
|
11
|
+
class IdentityStrategy
|
12
|
+
# Generate a unique identity hash for a task
|
13
|
+
#
|
14
|
+
# The default implementation generates a random UUID.
|
15
|
+
#
|
16
|
+
# @param _task [Tasker::Task] The task to generate an identity for
|
17
|
+
# @param _task_options [Hash] Additional options for identity generation
|
18
|
+
# @return [String] A unique identity hash
|
19
|
+
def generate_identity_hash(_task, _task_options)
|
20
|
+
SecureRandom.uuid
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Strategy that uses SHA256 hash of task identity options
|
25
|
+
#
|
26
|
+
# This strategy is useful when you want identical tasks (with the same
|
27
|
+
# options/parameters) to have the same identity hash.
|
28
|
+
class HashIdentityStrategy < IdentityStrategy
|
29
|
+
# Generate a deterministic identity hash based on task options
|
30
|
+
#
|
31
|
+
# @param _task [Tasker::Task] The task to generate an identity for
|
32
|
+
# @param task_options [Hash] Task options to hash for identity
|
33
|
+
# @return [String] A SHA256 hash of the task options
|
34
|
+
def generate_identity_hash(_task, task_options)
|
35
|
+
Digest::SHA256.hexdigest(task_options.to_json)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tasker
|
4
|
+
module Logging
|
5
|
+
# CorrelationIdGenerator creates unique correlation IDs for distributed tracing
|
6
|
+
#
|
7
|
+
# Generates correlation IDs that are:
|
8
|
+
# - Unique across distributed systems
|
9
|
+
# - URL-safe and log-friendly
|
10
|
+
# - Sortable by generation time
|
11
|
+
# - Human-readable length
|
12
|
+
#
|
13
|
+
# Format: tsk_[timestamp_base36][random_suffix]
|
14
|
+
# Example: tsk_l7k9m2p4_a8x3f9
|
15
|
+
class CorrelationIdGenerator
|
16
|
+
# Prefix for all Tasker correlation IDs
|
17
|
+
PREFIX = 'tsk'
|
18
|
+
|
19
|
+
# Length of random suffix
|
20
|
+
RANDOM_SUFFIX_LENGTH = 6
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Generate a new correlation ID
|
24
|
+
#
|
25
|
+
# @return [String] A unique correlation ID
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# id = Tasker::Logging::CorrelationIdGenerator.generate
|
29
|
+
# # => "tsk_l7k9m2p4_a8x3f9"
|
30
|
+
def generate
|
31
|
+
timestamp_part = generate_timestamp_component
|
32
|
+
random_part = generate_random_component
|
33
|
+
|
34
|
+
"#{PREFIX}_#{timestamp_part}_#{random_part}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generate correlation ID from an existing ID (for HTTP propagation)
|
38
|
+
#
|
39
|
+
# @param existing_id [String, nil] Existing correlation ID from headers
|
40
|
+
# @return [String] Valid correlation ID (existing or newly generated)
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# # With existing valid ID
|
44
|
+
# id = from_existing("req_abc123")
|
45
|
+
# # => "req_abc123"
|
46
|
+
#
|
47
|
+
# # With invalid/missing ID
|
48
|
+
# id = from_existing(nil)
|
49
|
+
# # => "tsk_l7k9m2p4_a8x3f9"
|
50
|
+
def from_existing(existing_id)
|
51
|
+
return generate if existing_id.blank?
|
52
|
+
return existing_id if valid_correlation_id?(existing_id)
|
53
|
+
|
54
|
+
# If existing ID is invalid, create a new one but log the attempt
|
55
|
+
Rails.logger.debug { "Invalid correlation ID received: #{existing_id}. Generating new ID." }
|
56
|
+
generate
|
57
|
+
end
|
58
|
+
|
59
|
+
# Extract correlation ID from HTTP headers
|
60
|
+
#
|
61
|
+
# @param headers [Hash] HTTP headers hash
|
62
|
+
# @param header_name [String] Header name to check (defaults to config)
|
63
|
+
# @return [String, nil] Correlation ID if found and valid
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# id = from_headers(request.headers)
|
67
|
+
# id = from_headers(env, 'X-Request-ID')
|
68
|
+
def from_headers(headers, header_name = nil)
|
69
|
+
header_name ||= Tasker.configuration.telemetry.correlation_id_header
|
70
|
+
|
71
|
+
# Handle different header formats (rack vs rails)
|
72
|
+
header_value = headers[header_name] ||
|
73
|
+
headers["HTTP_#{header_name.upcase.tr('-', '_')}"] ||
|
74
|
+
headers[header_name.downcase]
|
75
|
+
|
76
|
+
from_existing(header_value)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Validate if a string is a valid correlation ID format
|
80
|
+
#
|
81
|
+
# @param id [String] The ID to validate
|
82
|
+
# @return [Boolean] Whether the ID is valid
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# valid_correlation_id?("tsk_l7k9m2p4_a8x3f9") # => true
|
86
|
+
# valid_correlation_id?("invalid") # => false
|
87
|
+
# valid_correlation_id?("") # => false
|
88
|
+
def valid_correlation_id?(id)
|
89
|
+
return false if id.blank?
|
90
|
+
return false if id.length > 100 # Reasonable max length
|
91
|
+
|
92
|
+
# Allow various prefixes for external system compatibility
|
93
|
+
# But ensure it's alphanumeric with underscores/hyphens only
|
94
|
+
id.match?(/\A[a-zA-Z0-9][a-zA-Z0-9_\-]*\z/)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Generate timestamp component in base36 for compactness
|
100
|
+
#
|
101
|
+
# @return [String] Timestamp component
|
102
|
+
def generate_timestamp_component
|
103
|
+
# Use millisecond precision timestamp
|
104
|
+
timestamp_ms = (Time.current.to_f * 1000).to_i
|
105
|
+
timestamp_ms.to_s(36)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Generate random component using secure random
|
109
|
+
#
|
110
|
+
# @return [String] Random component
|
111
|
+
def generate_random_component
|
112
|
+
# Use URL-safe base64 character set: A-Z, a-z, 0-9
|
113
|
+
chars = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
|
114
|
+
|
115
|
+
Array.new(RANDOM_SUFFIX_LENGTH) { chars.sample }.join
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../concerns/event_publisher'
|
4
|
+
require_relative 'retry_header_parser'
|
5
|
+
|
6
|
+
module Tasker
|
7
|
+
module Orchestration
|
8
|
+
# BackoffCalculator handles all backoff logic for API retries
|
9
|
+
#
|
10
|
+
# This component provides unified handling of both server-requested backoff
|
11
|
+
# (via Retry-After headers) and exponential backoff calculations.
|
12
|
+
# It publishes appropriate events for observability.
|
13
|
+
class BackoffCalculator
|
14
|
+
include Tasker::Concerns::EventPublisher
|
15
|
+
|
16
|
+
# Initialize the backoff calculator
|
17
|
+
#
|
18
|
+
# @param config [Object] Configuration object with backoff settings
|
19
|
+
# @param retry_parser [RetryHeaderParser] Parser for Retry-After headers (injectable for testing)
|
20
|
+
def initialize(config: nil, retry_parser: RetryHeaderParser.new)
|
21
|
+
@config = config
|
22
|
+
@retry_parser = retry_parser
|
23
|
+
end
|
24
|
+
|
25
|
+
# Calculate and apply backoff for a step based on response context
|
26
|
+
#
|
27
|
+
# This method determines whether to use server-requested backoff or
|
28
|
+
# exponential backoff, calculates the appropriate delay, and updates
|
29
|
+
# the step with backoff information.
|
30
|
+
#
|
31
|
+
# @param step [Tasker::WorkflowStep] The step requiring backoff
|
32
|
+
# @param context [Hash] Response context containing headers and metadata
|
33
|
+
def calculate_and_apply_backoff(step, context)
|
34
|
+
retry_after = extract_retry_after_header(context)
|
35
|
+
|
36
|
+
if retry_after.present?
|
37
|
+
apply_server_requested_backoff(step, retry_after)
|
38
|
+
elsif backoff_enabled?
|
39
|
+
apply_exponential_backoff(step, context)
|
40
|
+
else
|
41
|
+
Rails.logger.warn(
|
42
|
+
"BackoffCalculator: No backoff strategy available for step #{step.name} (#{step.workflow_step_id})"
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Get backoff configuration with memoization
|
50
|
+
#
|
51
|
+
# @return [Tasker::Types::BackoffConfig] The backoff configuration
|
52
|
+
def backoff_config
|
53
|
+
@backoff_config ||= Tasker.configuration.backoff
|
54
|
+
end
|
55
|
+
|
56
|
+
# Extract Retry-After header from response context
|
57
|
+
#
|
58
|
+
# @param context [Hash] Response context
|
59
|
+
# @return [String, nil] Retry-After header value if present
|
60
|
+
def extract_retry_after_header(context)
|
61
|
+
headers = context[:headers] || {}
|
62
|
+
headers['Retry-After'] || headers['retry-after']
|
63
|
+
end
|
64
|
+
|
65
|
+
# Apply server-requested backoff using Retry-After header
|
66
|
+
#
|
67
|
+
# @param step [Tasker::WorkflowStep] The step to apply backoff to
|
68
|
+
# @param retry_after [String] The Retry-After header value
|
69
|
+
def apply_server_requested_backoff(step, retry_after)
|
70
|
+
backoff_seconds = @retry_parser.parse_retry_after(retry_after)
|
71
|
+
|
72
|
+
# Apply configurable cap for server-requested backoff
|
73
|
+
max_server_backoff = backoff_config.max_backoff_seconds
|
74
|
+
backoff_seconds = [backoff_seconds, max_server_backoff].min
|
75
|
+
|
76
|
+
# Update step with backoff information
|
77
|
+
step.backoff_request_seconds = backoff_seconds
|
78
|
+
|
79
|
+
# Publish backoff event for observability
|
80
|
+
publish_step_backoff(
|
81
|
+
step,
|
82
|
+
backoff_seconds: backoff_seconds,
|
83
|
+
backoff_type: 'server_requested',
|
84
|
+
retry_after: retry_after
|
85
|
+
)
|
86
|
+
|
87
|
+
Rails.logger.info(
|
88
|
+
"BackoffCalculator: Applied server-requested backoff of #{backoff_seconds} seconds " \
|
89
|
+
"for step #{step.name} (#{step.workflow_step_id})"
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Apply exponential backoff calculation
|
94
|
+
#
|
95
|
+
# @param step [Tasker::WorkflowStep] The step to apply backoff to
|
96
|
+
# @param context [Hash] Response context for event publishing
|
97
|
+
def apply_exponential_backoff(step, context)
|
98
|
+
# Ensure attempts is properly initialized
|
99
|
+
step.attempts ||= 0
|
100
|
+
|
101
|
+
# Convert 0-based attempts to 1-based for backoff calculation
|
102
|
+
# (step.attempts = 0 means first attempt, should get backoff[0])
|
103
|
+
attempt_for_calculation = step.attempts + 1
|
104
|
+
backoff_seconds = calculate_exponential_delay(attempt_for_calculation)
|
105
|
+
|
106
|
+
# Update step with backoff information
|
107
|
+
step.backoff_request_seconds = backoff_seconds
|
108
|
+
step.last_attempted_at = Time.zone.now
|
109
|
+
|
110
|
+
# Publish backoff event for observability
|
111
|
+
publish_exponential_backoff_event(step, backoff_seconds, context, attempt_for_calculation)
|
112
|
+
|
113
|
+
Rails.logger.info(
|
114
|
+
"BackoffCalculator: Applied exponential backoff of #{backoff_seconds} seconds " \
|
115
|
+
"for step #{step.name} (#{step.workflow_step_id}), attempt #{step.attempts}"
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Calculate exponential delay with jitter
|
120
|
+
#
|
121
|
+
# @param attempt_number [Integer] The 1-based attempt number
|
122
|
+
# @return [Float] Calculated backoff delay in seconds
|
123
|
+
def calculate_exponential_delay(attempt_number)
|
124
|
+
# Use BackoffConfig's calculate_backoff_seconds method
|
125
|
+
backoff_seconds = backoff_config.calculate_backoff_seconds(attempt_number)
|
126
|
+
|
127
|
+
# Ensure minimum delay of at least half the first backoff value
|
128
|
+
min_delay = backoff_config.default_backoff_seconds.first * 0.5
|
129
|
+
[backoff_seconds, min_delay].max
|
130
|
+
end
|
131
|
+
|
132
|
+
# Publish exponential backoff event with detailed information
|
133
|
+
#
|
134
|
+
# @param step [Tasker::WorkflowStep] The step being backed off
|
135
|
+
# @param backoff_seconds [Float] Calculated backoff delay
|
136
|
+
# @param context [Hash] Response context
|
137
|
+
# @param attempt_number [Integer] The 1-based attempt number
|
138
|
+
def publish_exponential_backoff_event(step, backoff_seconds, _context, attempt_number)
|
139
|
+
publish_step_backoff(
|
140
|
+
step,
|
141
|
+
backoff_seconds: backoff_seconds,
|
142
|
+
backoff_type: 'exponential',
|
143
|
+
attempt: step.attempts,
|
144
|
+
calculated_attempt: attempt_number,
|
145
|
+
base_delay: backoff_config.default_backoff_seconds.first,
|
146
|
+
multiplier: backoff_config.backoff_multiplier,
|
147
|
+
jitter_enabled: backoff_config.jitter_enabled,
|
148
|
+
jitter_max_percentage: backoff_config.jitter_max_percentage
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Check if exponential backoff is enabled
|
153
|
+
#
|
154
|
+
# @return [Boolean] True if exponential backoff should be used
|
155
|
+
def backoff_enabled?
|
156
|
+
return true unless @config # Default to enabled if no config
|
157
|
+
|
158
|
+
if @config.respond_to?(:enable_exponential_backoff)
|
159
|
+
@config.enable_exponential_backoff
|
160
|
+
else
|
161
|
+
true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Get retry delay from configuration
|
166
|
+
#
|
167
|
+
# @return [Float] Base retry delay in seconds
|
168
|
+
def retry_delay
|
169
|
+
return backoff_config.default_backoff_seconds.first.to_f unless @config.respond_to?(:retry_delay)
|
170
|
+
|
171
|
+
@config.retry_delay || backoff_config.default_backoff_seconds.first.to_f
|
172
|
+
end
|
173
|
+
|
174
|
+
# Get jitter factor from configuration
|
175
|
+
#
|
176
|
+
# @return [Float] Jitter factor for randomness (0.0-1.0)
|
177
|
+
def jitter_factor_value
|
178
|
+
return rand unless @config.respond_to?(:jitter_factor)
|
179
|
+
|
180
|
+
@config.jitter_factor || rand
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
module Tasker
|
6
|
+
module Orchestration
|
7
|
+
# ConnectionBuilder handles Faraday connection configuration and building
|
8
|
+
#
|
9
|
+
# This component provides focused responsibility for building and configuring
|
10
|
+
# Faraday connections based on API handler configuration, with support for
|
11
|
+
# custom connection block configuration.
|
12
|
+
class ConnectionBuilder
|
13
|
+
# Build a Faraday connection from configuration
|
14
|
+
#
|
15
|
+
# @param config [Object] Configuration object with connection settings
|
16
|
+
# @yield [Faraday::Connection] Optional block for custom connection configuration
|
17
|
+
# @return [Faraday::Connection] Configured Faraday connection
|
18
|
+
def build_connection(config, &connection_block)
|
19
|
+
validate_config(config)
|
20
|
+
|
21
|
+
Rails.logger.debug do
|
22
|
+
"ConnectionBuilder: Building connection to #{config.url} " \
|
23
|
+
"with #{config.params.keys.size} params and #{config.headers.keys.size} headers"
|
24
|
+
end
|
25
|
+
|
26
|
+
connection = Faraday.new(
|
27
|
+
url: config.url,
|
28
|
+
params: config.params || {},
|
29
|
+
headers: config.headers || {},
|
30
|
+
ssl: config.ssl
|
31
|
+
)
|
32
|
+
|
33
|
+
# Apply custom configuration block if provided
|
34
|
+
if connection_block
|
35
|
+
Rails.logger.debug('ConnectionBuilder: Applying custom connection configuration')
|
36
|
+
yield(connection)
|
37
|
+
end
|
38
|
+
|
39
|
+
connection
|
40
|
+
rescue StandardError => e
|
41
|
+
Rails.logger.error(
|
42
|
+
"ConnectionBuilder: Failed to build connection to #{config&.url}: #{e.message}"
|
43
|
+
)
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
|
47
|
+
# Validate configuration has required fields
|
48
|
+
#
|
49
|
+
# @param config [Object] Configuration to validate
|
50
|
+
# @raise [ArgumentError] If configuration is invalid
|
51
|
+
def validate_config(config)
|
52
|
+
ConfigValidator.validate(config)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Service class to validate connection configuration
|
56
|
+
# Reduces complexity by organizing validation logic
|
57
|
+
class ConfigValidator
|
58
|
+
class << self
|
59
|
+
# Validate configuration object
|
60
|
+
#
|
61
|
+
# @param config [Object] Configuration to validate
|
62
|
+
# @raise [ArgumentError] If configuration is invalid
|
63
|
+
def validate(config)
|
64
|
+
validate_config_presence(config)
|
65
|
+
validate_config_interface(config)
|
66
|
+
validate_url_presence(config)
|
67
|
+
validate_url_format(config)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Validate configuration is present
|
73
|
+
#
|
74
|
+
# @param config [Object] Configuration to validate
|
75
|
+
# @raise [ArgumentError] If configuration is nil
|
76
|
+
def validate_config_presence(config)
|
77
|
+
raise ArgumentError, 'Configuration cannot be nil' if config.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Validate configuration has required interface
|
81
|
+
#
|
82
|
+
# @param config [Object] Configuration to validate
|
83
|
+
# @raise [ArgumentError] If configuration doesn't respond to :url
|
84
|
+
def validate_config_interface(config)
|
85
|
+
raise ArgumentError, 'Configuration must respond to :url' unless config.respond_to?(:url)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Validate URL is present and non-empty
|
89
|
+
#
|
90
|
+
# @param config [Object] Configuration to validate
|
91
|
+
# @raise [ArgumentError] If URL is nil or empty
|
92
|
+
def validate_url_presence(config)
|
93
|
+
return unless config.url.nil? || config.url.strip.empty?
|
94
|
+
|
95
|
+
raise ArgumentError, 'Configuration URL cannot be nil or empty'
|
96
|
+
end
|
97
|
+
|
98
|
+
# Validate URL format using URI parsing
|
99
|
+
#
|
100
|
+
# @param config [Object] Configuration to validate
|
101
|
+
# @raise [ArgumentError] If URL format is invalid
|
102
|
+
def validate_url_format(config)
|
103
|
+
uri = URI.parse(config.url)
|
104
|
+
validate_uri_components(uri)
|
105
|
+
rescue URI::InvalidURIError => e
|
106
|
+
raise ArgumentError, "Configuration URL is not a valid URI: #{e.message}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Validate URI has required components
|
110
|
+
#
|
111
|
+
# @param uri [URI] Parsed URI to validate
|
112
|
+
# @raise [ArgumentError] If URI lacks scheme or host
|
113
|
+
def validate_uri_components(uri)
|
114
|
+
return if uri.scheme && uri.host
|
115
|
+
|
116
|
+
raise ArgumentError, 'Configuration URL must be a valid URI with scheme and host'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|