Skip to content

Commit b7c0af4

Browse files
committed
Implementing core module.
1 parent f3191d4 commit b7c0af4

10 files changed

Lines changed: 921 additions & 912 deletions
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* FlowForge Core Module - Validation Patterns
3+
*
4+
* File: modules/core/src/main/scala/com/flowforge/core/patterns/CommonValidations.scala Package:
5+
* com.flowforge.core.patterns
6+
*
7+
* This file provides comprehensive validation patterns and combinators for the FlowForge ecosystem.
8+
* It enables composable, functional validation with rich error accumulation and recovery strategies
9+
* using cats ValidatedNel.
10+
*
11+
* Design Patterns Applied:
12+
* - Combinator Pattern: Small, composable validation functions
13+
* - Strategy Pattern: Different validation strategies per data type
14+
* - Chain of Responsibility: Validation pipeline with ordered checks
15+
* - Template Method Pattern: Abstract validation framework with concrete rules
16+
* - Composite Pattern: Complex validations from simple components
17+
*
18+
* Scala Features Showcased:
19+
* - ValidatedNel: Error accumulation without short-circuiting
20+
* - Applicative Functor: Parallel validation with mapN
21+
* - Type Classes: Polymorphic validation across data types
22+
* - Higher-Order Functions: Validation combinators and builders
23+
* - Pattern Matching: Sophisticated error handling and routing
24+
* - Implicit Classes: Fluent validation syntax
25+
* - Generic Programming: Reusable validation patterns
26+
*
27+
* Innovation Highlights:
28+
* - Compositional validation with error accumulation
29+
* - Data quality validation with business rule integration
30+
* - Schema validation with evolution compatibility checking
31+
* - Configuration validation with environment-specific rules
32+
* - Performance-optimized validation for large datasets
33+
* - Conditional validation with context-aware rules
34+
*
35+
* Usage Examples:
36+
* ```scala
37+
* // Compositional validation with error accumulation
38+
* case class User(name: String, email: String, age: Int)
39+
*
40+
* val validateUser: User => ValidationResult[User] = { user =>
41+
* (
42+
* validateNonEmpty("name", user.name),
43+
* validateEmail("email", user.email),
44+
* validateRange("age", user.age, 0, 150)
45+
* ).mapN((_, _, _) => user)
46+
* }
47+
*
48+
* // Data quality validation with business rules
49+
* val dataQualityRules = ValidationRules
50+
* .builder[Dataset]
51+
* .notNull("customer_id")
52+
* .unique("transaction_id")
53+
* .range("amount", min = 0, max = 1_000_000)
54+
* .pattern("email", EmailPattern)
55+
* .freshness(maxAge = 24.hours)
56+
* .build
57+
*
58+
* // Conditional validation based on context
59+
* val environmentRules = ValidationRules.conditional[Config] { config =>
60+
* if (config.environment.isProduction) {
61+
* strictValidation
62+
* } else {
63+
* lenientValidation
64+
* }
65+
* }
66+
* ```
67+
*
68+
* @author
69+
* FlowForge Team
70+
* @version 1.0.0
71+
* @since 2024
72+
*/
73+
package com.flowforge.core.patterns
74+
75+
import cats.syntax.all._
76+
import com.flowforge.core.types.RefinedTypes._
77+
import com.flowforge.core.types._
78+
import com.flowforge.core.patterns.ValidationTypes._
79+
80+
/**
81+
* Pre-built validation patterns for common use cases.
82+
*/
83+
object CommonValidations {
84+
85+
import ValidationCombinators._
86+
87+
/**
88+
* Standard user validation pattern.
89+
*/
90+
case class UserValidation(
91+
name: String,
92+
email: String,
93+
age: Int
94+
)
95+
96+
val validateUser: UserValidation => ConfigValidationResult[UserValidation] = { user =>
97+
(
98+
nonEmpty("name", user.name),
99+
email("email", user.email),
100+
intInRange("age", user.age, 0, 150)
101+
).mapN((_, _, _) => user)
102+
}
103+
104+
/**
105+
* Configuration validation pattern.
106+
*/
107+
def validatePipelineConfig(config: PipelineConfig): ConfigValidationResult[PipelineConfig] = {
108+
val nameValidation = nonEmpty("name", config.name.value)
109+
val environmentValidation = config.environment.validNel // Always valid
110+
val sourceValidation = config.source.validNel // Assume valid for now
111+
val sinkValidation = config.sink.validNel // Assume valid for now
112+
113+
(nameValidation, environmentValidation, sourceValidation, sinkValidation)
114+
.mapN((_, _, _, _) => config)
115+
}
116+
117+
/**
118+
* Data quality validation pattern.
119+
*/
120+
def validateDataQuality[A](
121+
data: List[A],
122+
rules: QualityRules
123+
): QualityValidationResult[List[A]] =
124+
// Simplified quality validation - in practice would be much more sophisticated
125+
if (data.nonEmpty) {
126+
valid(data)
127+
} else {
128+
val violation = QualityConstraint.NotNull(
129+
FieldName.unsafeFrom("data")
130+
)
131+
invalid(violation).asInstanceOf[QualityValidationResult[List[A]]]
132+
}
133+
134+
/**
135+
* Schema compatibility validation pattern.
136+
*/
137+
def validateSchemaCompatibility(
138+
source: DataSchema,
139+
target: DataSchema
140+
): SchemaValidationResult =
141+
SchemaValidation.compatible(source, target)
142+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package com.flowforge.core.patterns
2+
3+
import cats.syntax.all._
4+
import com.flowforge.core.patterns.ValidationTypes.QualityValidationResult
5+
import com.flowforge.core.types.ErrorSeverity
6+
import com.flowforge.core.types.ValidationError.QualityViolation
7+
8+
import java.time.Instant
9+
import scala.concurrent.duration.FiniteDuration
10+
11+
/**
12+
* Specialized validation patterns for data quality assurance. These validators focus on data
13+
* integrity, completeness, and business rules.
14+
*/
15+
object DataQualityValidation {
16+
17+
/**
18+
* Validate data freshness - ensure data is not older than specified duration.
19+
*/
20+
def freshness[A](
21+
fieldName: String,
22+
timestamp: Instant,
23+
maxAge: FiniteDuration
24+
): QualityValidationResult[A] = ???
25+
26+
/**
27+
* Validate data completeness - ensure required percentage of non-null values.
28+
*/
29+
def completeness[A](
30+
fieldName: String,
31+
values: List[Option[A]],
32+
minCompleteness: Double
33+
): QualityValidationResult[List[Option[A]]] = {
34+
val total = values.length
35+
val nonNull = values.count(_.isDefined)
36+
val actualCompleteness = if (total > 0) nonNull.toDouble / total else 0.0
37+
38+
if (actualCompleteness >= minCompleteness) {
39+
values.validNel
40+
} else {
41+
val violation = QualityViolation(
42+
constraint = "completeness",
43+
violatedValue = fieldName,
44+
threshold = Some(minCompleteness.toString),
45+
message = s"Completeness too low: ${actualCompleteness * 100}% < ${minCompleteness * 100}%",
46+
severity = ErrorSeverity.Error
47+
)
48+
violation.invalidNel
49+
}
50+
}
51+
52+
/**
53+
* Validate data uniqueness - ensure no duplicate values.
54+
*/
55+
def uniqueness[A](
56+
fieldName: String,
57+
values: List[A]
58+
): QualityValidationResult[List[A]] = {
59+
val duplicates = values.groupBy(identity).filter(_._2.size > 1)
60+
61+
if (duplicates.isEmpty) {
62+
values.validNel
63+
} else {
64+
val violation = QualityViolation(
65+
constraint = "uniqueness",
66+
violatedValue = fieldName,
67+
threshold = Some(duplicates).map(_.toString),
68+
message = s"Duplicate values found: ${duplicates.keys.mkString(", ")}",
69+
severity = ErrorSeverity.Error
70+
)
71+
violation.invalidNel
72+
}
73+
}
74+
75+
/**
76+
* Validate referential integrity - ensure foreign key references exist.
77+
*/
78+
def referentialIntegrity[A](
79+
fieldName: String,
80+
foreignKeys: List[A],
81+
referenceTable: Set[A]
82+
): QualityValidationResult[List[A]] = {
83+
val invalidKeys = foreignKeys.filterNot(referenceTable.contains)
84+
85+
if (invalidKeys.isEmpty) {
86+
foreignKeys.validNel
87+
} else {
88+
val violation = QualityViolation(
89+
constraint = "referential_integrity",
90+
violatedValue = fieldName,
91+
threshold = Some(invalidKeys).map(_.toString),
92+
message = s"Invalid foreign key references: ${invalidKeys.mkString(", ")}",
93+
severity = ErrorSeverity.Error
94+
)
95+
violation.invalidNel
96+
}
97+
}
98+
99+
/**
100+
* Validate data distribution - ensure values follow expected statistical properties.
101+
*/
102+
def distribution(
103+
fieldName: String,
104+
values: List[Double],
105+
expectedMean: Double,
106+
tolerance: Double
107+
): QualityValidationResult[List[Double]] =
108+
if (values.isEmpty) {
109+
val violation = QualityViolation(
110+
constraint = "distribution",
111+
violatedValue = fieldName,
112+
threshold = None,
113+
message = s"No values provided for distribution validation",
114+
severity = ErrorSeverity.Error
115+
)
116+
violation.invalidNel
117+
} else {
118+
val actualMean = values.sum / values.length
119+
val deviation = math.abs(actualMean - expectedMean)
120+
121+
if (deviation <= tolerance) {
122+
values.validNel
123+
} else {
124+
val violation = QualityViolation(
125+
constraint = "distribution",
126+
violatedValue = fieldName,
127+
threshold = Some(s"$expectedMean + $tolerance"),
128+
message = s"Mean deviation exceeds tolerance: $deviation > $tolerance",
129+
severity = ErrorSeverity.Error
130+
)
131+
violation.invalidNel
132+
}
133+
}
134+
135+
/**
136+
* Validate business rule - custom validation logic for domain-specific constraints.
137+
*/
138+
def businessRule[A](
139+
ruleName: String,
140+
description: String
141+
)(rule: A => Boolean): A => QualityValidationResult[A] = { value =>
142+
if (rule(value)) {
143+
value.validNel
144+
} else {
145+
val violation = QualityViolation(
146+
constraint = "business_rule",
147+
violatedValue = ruleName,
148+
threshold = Some(value).map(_.toString),
149+
message = s"Business rule violation: $description",
150+
severity = ErrorSeverity.Error
151+
)
152+
violation.invalidNel
153+
}
154+
}
155+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.flowforge.core.patterns
2+
3+
import cats.syntax.all._
4+
import com.flowforge.core.patterns.ValidationTypes.ValidationResult
5+
6+
/**
7+
* Named validation rule for better error reporting and debugging.
8+
*/
9+
case class NamedValidationRule[A](
10+
name: String,
11+
validator: A => ValidationResult[A]
12+
) {
13+
14+
def validate(value: A): ValidationResult[A] = validator(value)
15+
16+
def combine(other: NamedValidationRule[A]): NamedValidationRule[A] =
17+
NamedValidationRule(
18+
s"$name+${other.name}",
19+
value => (validator(value), other.validator(value)).mapN((_, _) => value)
20+
)
21+
}

0 commit comments

Comments
 (0)