sourceafBedSheet::ValueEncoders.fan

using afIoc::ConcurrentState
using afIoc::StrategyRegistry
using afIoc::TypeCoercer

** (Service) - Contribute your 'ValueEncoder' classes to this.
** 
** pre>
**   @Contribute { serviceType=ValueEncoders# }
**   static Void contributeValueEncoders(MappedConfig conf) {
**     conf[MyEntity#] = conf.autobuild(MyEntityEncoder#)
**   }
** <pre
** 
** @uses a MappedConfig of 'Type:ValueEncoder' where 'Type' is what the 'ValueEncoder', um, encodes!
const mixin ValueEncoders {
    
    ** Converts the given 'value' to Str via a contributed `ValueEncoder`. If no 'ValueEncoder' is 
    ** found, 'toStr()' is used. 
    abstract Str? toClient(Type valType, Obj? value)
    
    ** Converts the given 'clientValue' into the given 'valType' via a contributed `ValueEncoder`. 
    ** If no 'ValueEncoder' is found the value is [coerced]`afIoc::TypeCoercer`.
    abstract Obj? toValue(Type valType, Str? clientValue)
}

internal const class ValueEncodersImpl : ValueEncoders {
    private const ConcurrentState   conState    := ConcurrentState(ValueEncodersState#)
    private const StrategyRegistry  valueEncoderStrategy
    
    internal new make(Type:ValueEncoder valueEncoders) {
        this.valueEncoderStrategy = StrategyRegistry(valueEncoders)
    }
    
    override Str? toClient(Type valType, Obj? value) {
        // check the basics first!
        if (value is Str)
            return value
        
        valEnc := get(valType)
        if (valEnc != null)
            try {
                return valEnc.toClient(value)
            } catch (Err cause) {
                throw ValueEncodingErr(BsErrMsgs.valueEncodingBuggered(value, Str#), cause)
            }
        
        return value?.toStr
    }

    override Obj? toValue(Type valType, Str? clientValue) {
        // check the basics first!
        if (valType.fits(Str#))
            return clientValue

        // give the val encs a chance to handle nulls
        valEnc := get(valType)
        if (valEnc != null)
            try {
                return get(valType).toValue(clientValue)
            } catch (Err cause) {
                throw ValueEncodingErr(BsErrMsgs.valueEncodingBuggered(clientValue, valType), cause)
            }
        
        if (clientValue == null)
            return null
        
        if (getState() { it.typeCoercer.canCoerce(Str#, valType) } == false)
            throw ValueEncodingErr(BsErrMsgs.valueEncodingNotFound(valType))
        
        try {
            return getState() { it.typeCoercer.coerce(clientValue, valType) } 
        } catch (Err cause) {
            throw ValueEncodingErr(BsErrMsgs.valueEncodingBuggered(clientValue, valType), cause)
        }
    }

    private ValueEncoder? get(Type valueType) {
        valueEncoderStrategy.findBestFit(valueType, false)
    }

    private Obj? getState(|ValueEncodersState -> Obj| state) {
        conState.getState(state)
    }
}

internal class ValueEncodersState {
    TypeCoercer typeCoercer := TypeCoercer()
}