Step 1: Understand the Requirement
Let’s say we want to add a new feature to convert temperature units (Celsius to Fahrenheit and vice versa).Step 2: Identify Where to Extend
We need to create:- A new
TemperatureConversionRuleclass that implementsMappingRule - A new builder to construct this rule
- Extension methods on
TargetBuilderto use this feature
Step 3: Implement the Mapping Rule
Copy
package xyz.mahmoudahmed.dsl.custom
import xyz.mahmoudahmed.adapter.DataNode
import xyz.mahmoudahmed.dsl.core.MappingContext
import xyz.mahmoudahmed.dsl.core.MappingRule
import xyz.mahmoudahmed.dsl.core.SimpleMapping
/**
* Rule for converting temperature between different units.
*/
class TemperatureConversionRule(
private val sourcePath: String,
private val targetPath: String,
private val sourceUnit: TemperatureUnit,
private val targetUnit: TemperatureUnit
) : MappingRule {
override fun apply(context: MappingContext, target: Any) {
// Get the source value
val sourceValue = context.getValueByPath(sourcePath)
// Convert only if we have a number
if (sourceValue is Number) {
val sourceTemp = sourceValue.toDouble()
val convertedTemp = convertTemperature(sourceTemp, sourceUnit, targetUnit)
// Set the converted value
if (target is DataNode.ObjectNode) {
SimpleMapping.setValueInDataNode(target, targetPath, convertedTemp)
}
}
}
private fun convertTemperature(
value: Double,
fromUnit: TemperatureUnit,
toUnit: TemperatureUnit
): Double {
return when {
fromUnit == toUnit -> value
fromUnit == TemperatureUnit.CELSIUS && toUnit == TemperatureUnit.FAHRENHEIT ->
(value * 9/5) + 32
fromUnit == TemperatureUnit.FAHRENHEIT && toUnit == TemperatureUnit.CELSIUS ->
(value - 32) * 5/9
fromUnit == TemperatureUnit.CELSIUS && toUnit == TemperatureUnit.KELVIN ->
value + 273.15
fromUnit == TemperatureUnit.KELVIN && toUnit == TemperatureUnit.CELSIUS ->
value - 273.15
fromUnit == TemperatureUnit.FAHRENHEIT && toUnit == TemperatureUnit.KELVIN ->
(value - 32) * 5/9 + 273.15
fromUnit == TemperatureUnit.KELVIN && toUnit == TemperatureUnit.FAHRENHEIT ->
(value - 273.15) * 9/5 + 32
else -> throw IllegalArgumentException("Unsupported conversion: $fromUnit to $toUnit")
}
}
}
/**
* Temperature units supported by the converter.
*/
enum class TemperatureUnit {
CELSIUS,
FAHRENHEIT,
KELVIN
}
Step 4: Create a Builder for the New Feature
Copy
package xyz.mahmoudahmed.dsl.custom
import xyz.mahmoudahmed.dsl.builders.TargetBuilder
/**
* Builder for configuring temperature conversion.
*/
class TemperatureConversionBuilder(
private val parent: TargetBuilder,
private val sourcePath: String,
private val sourceUnit: TemperatureUnit
) {
/**
* Specify the target temperature unit.
*
* @param targetUnit The unit to convert to
* @return Builder for specifying the target path
*/
fun to(targetUnit: TemperatureUnit): TemperatureTargetBuilder {
return TemperatureTargetBuilder(parent, sourcePath, sourceUnit, targetUnit)
}
}
/**
* Builder for specifying the target path for temperature conversion.
*/
class TemperatureTargetBuilder(
private val parent: TargetBuilder,
private val sourcePath: String,
private val sourceUnit: TemperatureUnit,
private val targetUnit: TemperatureUnit
) {
/**
* Specify the target path where the converted temperature will be stored.
*
* @param targetPath The path in the target object
* @return The parent target builder
*/
fun at(targetPath: String): TargetBuilder {
val rule = TemperatureConversionRule(sourcePath, targetPath, sourceUnit, targetUnit)
parent.addRule(rule)
return parent
}
}
Step 5: Extend TargetBuilder with the New Feature
Copy
package xyz.mahmoudahmed.dsl.builders
import xyz.mahmoudahmed.dsl.custom.TemperatureConversionBuilder
import xyz.mahmoudahmed.dsl.custom.TemperatureUnit
/**
* Extension functions for TargetBuilder to support temperature conversion.
*/
/**
* Starts a temperature conversion from Celsius.
*
* @param sourcePath The path to the temperature value in Celsius
* @return Builder for configuring the temperature conversion
*/
fun TargetBuilder.convertCelsius(sourcePath: String): TemperatureConversionBuilder {
return TemperatureConversionBuilder(this, sourcePath, TemperatureUnit.CELSIUS)
}
/**
* Starts a temperature conversion from Fahrenheit.
*
* @param sourcePath The path to the temperature value in Fahrenheit
* @return Builder for configuring the temperature conversion
*/
fun TargetBuilder.convertFahrenheit(sourcePath: String): TemperatureConversionBuilder {
return TemperatureConversionBuilder(this, sourcePath, TemperatureUnit.FAHRENHEIT)
}
/**
* Starts a temperature conversion from Kelvin.
*
* @param sourcePath The path to the temperature value in Kelvin
* @return Builder for configuring the temperature conversion
*/
fun TargetBuilder.convertKelvin(sourcePath: String): TemperatureConversionBuilder {
return TemperatureConversionBuilder(this, sourcePath, TemperatureUnit.KELVIN)
}
Step 6: Use the New Feature
Copy
// Create a mapping that uses the temperature conversion
val weatherMapping = Platymap.flow("weatherData")
.to("processedWeather")
.map("location.name").to("city")
.map("location.country").to("country")
// Use our new temperature conversion feature
.convertCelsius("temperature.current")
.to(TemperatureUnit.FAHRENHEIT)
.at("temperature.fahrenheit")
.convertFahrenheit("temperature.fahrenheit")
.to(TemperatureUnit.KELVIN)
.at("temperature.kelvin")
.build()
// Sample weather data
val weatherJson = """
{
"location": {
"name": "London",
"country": "UK",
"coordinates": {
"latitude": 51.5074,
"longitude": -0.1278
}
},
"temperature": {
"current": 22.5,
"min": 19.2,
"max": 24.8
},
"conditions": "Partly Cloudy",
"humidity": 65,
"wind": {
"speed": 12,
"direction": "NW"
}
}
"""
// Execute the mapping
val result = weatherMapping.executeToJson(weatherJson)
println(result)
Copy
{
"city": "London",
"country": "UK",
"temperature": {
"fahrenheit": 72.5,
"kelvin": 295.65
}
}
Step 7: Add Tests
Copy
@Test
fun testTemperatureConversion() {
// Create a simple mapping that uses temperature conversion
val mapping = Platymap.flow("source")
.to("target")
.convertCelsius("celsius")
.to(TemperatureUnit.FAHRENHEIT)
.at("fahrenheit")
.convertFahrenheit("fahrenheit")
.to(TemperatureUnit.CELSIUS)
.at("backToCelsius")
.build()
// Test data
val sourceJson = """
{
"celsius": 25.0
}
"""
// Execute the mapping
val result = mapping.execute(sourceJson)
// Verify results
val targetNode = result as DataNode.ObjectNode
val fahrenheit = targetNode.get("fahrenheit") as DataNode.NumberValue
val backToCelsius = targetNode.get("backToCelsius") as DataNode.NumberValue
assertEquals(77.0, fahrenheit.value.toDouble(), 0.01)
assertEquals(25.0, backToCelsius.value.toDouble(), 0.01)
}

