private static IComponentFactory GetComponentJson(IExceptionContext ectx, Type signatureType, string name, JObject settings, ComponentCatalog catalog) { Contracts.AssertValue(ectx); ectx.AssertValue(signatureType); ectx.AssertNonEmpty(name); ectx.AssertValueOrNull(settings); ectx.AssertValue(catalog); if (!catalog.TryGetComponentKind(signatureType, out string kind)) { throw ectx.Except($"Component type '{signatureType}' is not a valid signature type."); } if (!catalog.TryFindComponent(kind, name, out ComponentCatalog.ComponentInfo component)) { var available = catalog.GetAllComponents(kind).Select(x => $"'{x.Name}'"); throw ectx.Except($"Component '{name}' of kind '{kind}' is not found. Available components are: {string.Join(", ", available)}"); } var inputBuilder = new InputBuilder(ectx, component.ArgumentType, catalog); if (settings != null) { foreach (var pair in settings) { if (!inputBuilder.TrySetValueJson(pair.Key, pair.Value)) { throw ectx.Except($"Unexpected value for component '{name}', field '{pair.Key}': '{pair.Value}'"); } } } var missing = inputBuilder.GetMissingValues().ToArray(); if (missing.Length > 0) { throw ectx.Except($"The following required inputs were not provided for component '{name}': {string.Join(", ", missing)}"); } return(inputBuilder.GetInstance() as IComponentFactory); }
private static object ParseJsonValue(IExceptionContext ectx, Type type, Attributes attributes, JToken value, ComponentCatalog catalog) { Contracts.AssertValue(ectx); ectx.AssertValue(type); ectx.AssertValueOrNull(value); ectx.AssertValue(catalog); if (value == null) { return(null); } if (value is JValue val && val.Value == null) { return(null); } if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Optional <>) || type.GetGenericTypeDefinition() == typeof(Nullable <>))) { if (type.GetGenericTypeDefinition() == typeof(Optional <>) && value.HasValues) { value = value.Values().FirstOrDefault(); } type = type.GetGenericArguments()[0]; } if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Var <>))) { string varName = value.Value <string>(); ectx.Check(VariableBinding.IsBindingToken(value), "Variable name expected."); var variable = Activator.CreateInstance(type) as IVarSerializationHelper; var varBinding = VariableBinding.Create(ectx, varName); variable.VarName = varBinding.VariableName; return(variable); } if (type == typeof(JArray) && value is JArray) { return(value); } TlcModule.DataKind dt = TlcModule.GetDataType(type); try { switch (dt) { case TlcModule.DataKind.Bool: return(value.Value <bool>()); case TlcModule.DataKind.String: return(value.Value <string>()); case TlcModule.DataKind.Char: return(value.Value <char>()); case TlcModule.DataKind.Enum: if (!Enum.IsDefined(type, value.Value <string>())) { throw ectx.Except($"Requested value '{value.Value<string>()}' is not a member of the Enum type '{type.Name}'"); } return(Enum.Parse(type, value.Value <string>())); case TlcModule.DataKind.Float: if (type == typeof(double)) { return(value.Value <double>()); } else if (type == typeof(float)) { return(value.Value <float>()); } else { ectx.Assert(false); throw ectx.ExceptNotSupp(); } case TlcModule.DataKind.Array: var ja = value as JArray; ectx.Check(ja != null, "Expected array value"); Func <IExceptionContext, JArray, Attributes, ComponentCatalog, object> makeArray = MakeArray <int>; return(Utils.MarshalInvoke(makeArray, type.GetElementType(), ectx, ja, attributes, catalog)); case TlcModule.DataKind.Int: if (type == typeof(long)) { return(value.Value <long>()); } if (type == typeof(int)) { return(value.Value <int>()); } ectx.Assert(false); throw ectx.ExceptNotSupp(); case TlcModule.DataKind.UInt: if (type == typeof(ulong)) { return(value.Value <ulong>()); } if (type == typeof(uint)) { return(value.Value <uint>()); } ectx.Assert(false); throw ectx.ExceptNotSupp(); case TlcModule.DataKind.Dictionary: ectx.Check(value is JObject, "Expected object value"); Func <IExceptionContext, JObject, Attributes, ComponentCatalog, object> makeDict = MakeDictionary <int>; return(Utils.MarshalInvoke(makeDict, type.GetGenericArguments()[1], ectx, (JObject)value, attributes, catalog)); case TlcModule.DataKind.Component: var jo = value as JObject; ectx.Check(jo != null, "Expected object value"); // REVIEW: consider accepting strings alone. var jName = jo[FieldNames.Name]; ectx.Check(jName != null, "Field '" + FieldNames.Name + "' is required for component."); ectx.Check(jName is JValue, "Expected '" + FieldNames.Name + "' field to be a string."); var name = jName.Value <string>(); ectx.Check(jo[FieldNames.Settings] == null || jo[FieldNames.Settings] is JObject, "Expected '" + FieldNames.Settings + "' field to be an object"); return(GetComponentJson(ectx, type, name, jo[FieldNames.Settings] as JObject, catalog)); default: var settings = value as JObject; ectx.Check(settings != null, "Expected object value"); var inputBuilder = new InputBuilder(ectx, type, catalog); if (inputBuilder._fields.Length == 0) { throw ectx.Except($"Unsupported input type: {dt}"); } if (settings != null) { foreach (var pair in settings) { if (!inputBuilder.TrySetValueJson(pair.Key, pair.Value)) { throw ectx.Except($"Unexpected value for component '{type}', field '{pair.Key}': '{pair.Value}'"); } } } var missing = inputBuilder.GetMissingValues().ToArray(); if (missing.Length > 0) { throw ectx.Except($"The following required inputs were not provided for component '{type}': {string.Join(", ", missing)}"); } return(inputBuilder.GetInstance()); } } catch (FormatException ex) { if (ex.IsMarked()) { throw; } throw ectx.Except(ex, $"Failed to parse JSON value '{value}' as {type}"); } }
public JObject GetJsonObject(object instance, Dictionary <string, List <ParameterBinding> > inputBindingMap, Dictionary <ParameterBinding, VariableBinding> inputMap) { Contracts.CheckValue(instance, nameof(instance)); Contracts.Check(instance.GetType() == _type); var result = new JObject(); var defaults = Activator.CreateInstance(_type); for (int i = 0; i < _fields.Length; i++) { var field = _fields[i]; var attr = _attrs[i]; var instanceVal = field.GetValue(instance); var defaultsVal = field.GetValue(defaults); if (inputBindingMap.TryGetValue(field.Name, out List <ParameterBinding> bindings)) { // Handle variables. Contracts.Assert(bindings.Count > 0); VariableBinding varBinding; var paramBinding = bindings[0]; if (paramBinding is SimpleParameterBinding) { Contracts.Assert(bindings.Count == 1); bool success = inputMap.TryGetValue(paramBinding, out varBinding); Contracts.Assert(success); Contracts.AssertValue(varBinding); result.Add(field.Name, new JValue(varBinding.ToJson())); } else if (paramBinding is ArrayIndexParameterBinding) { // Array parameter bindings. var array = new JArray(); foreach (var parameterBinding in bindings) { Contracts.Assert(parameterBinding is ArrayIndexParameterBinding); bool success = inputMap.TryGetValue(parameterBinding, out varBinding); Contracts.Assert(success); Contracts.AssertValue(varBinding); array.Add(new JValue(varBinding.ToJson())); } result.Add(field.Name, array); } else { // Dictionary parameter bindings. Not supported yet. Contracts.Assert(paramBinding is DictionaryKeyParameterBinding); throw Contracts.ExceptNotImpl("Dictionary of variables not yet implemented."); } } else if (instanceVal == null && defaultsVal != null) { // Handle null values. result.Add(field.Name, new JValue(instanceVal)); } else if (instanceVal != null && (attr.Input.IsRequired || !instanceVal.Equals(defaultsVal))) { // A required field will be serialized regardless of whether or not its value is identical to the default. var type = instanceVal.GetType(); if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional <>)) { var isExplicit = ExtractOptional(ref instanceVal, ref type); if (!isExplicit) { continue; } } if (type == typeof(JArray)) { result.Add(field.Name, (JArray)instanceVal); } else if (type.IsGenericType && ((type.GetGenericTypeDefinition() == typeof(Var <>)) || type.GetGenericTypeDefinition() == typeof(ArrayVar <>) || type.GetGenericTypeDefinition() == typeof(DictionaryVar <>))) { result.Add(field.Name, new JValue($"${((IVarSerializationHelper)instanceVal).VarName}")); } else if (type == typeof(bool) || type == typeof(string) || type == typeof(char) || type == typeof(double) || type == typeof(float) || type == typeof(int) || type == typeof(long) || type == typeof(uint) || type == typeof(ulong)) { // Handle simple types. result.Add(field.Name, new JValue(instanceVal)); } else if (type.IsEnum) { // Handle enums. result.Add(field.Name, new JValue(instanceVal.ToString())); } else if (type.IsArray) { // Handle arrays. var array = (Array)instanceVal; var jarray = new JArray(); var elementType = type.GetElementType(); if (elementType == typeof(bool) || elementType == typeof(string) || elementType == typeof(char) || elementType == typeof(double) || elementType == typeof(float) || elementType == typeof(int) || elementType == typeof(long) || elementType == typeof(uint) || elementType == typeof(ulong)) { foreach (object item in array) { jarray.Add(new JValue(item)); } } else { var builder = new InputBuilder(_ectx, elementType, _catalog); foreach (object item in array) { jarray.Add(builder.GetJsonObject(item, inputBindingMap, inputMap)); } } result.Add(field.Name, jarray); } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary <,>) && type.GetGenericArguments()[0] == typeof(string)) { // Handle dictionaries. // REVIEW: Needs to be implemented when we will have entry point arguments that contain dictionaries. } else if (typeof(IComponentFactory).IsAssignableFrom(type)) { // Handle component factories. bool success = _catalog.TryFindComponent(type, out ComponentCatalog.ComponentInfo instanceInfo); Contracts.Assert(success); var builder = new InputBuilder(_ectx, type, _catalog); var instSettings = builder.GetJsonObject(instanceVal, inputBindingMap, inputMap); ComponentCatalog.ComponentInfo defaultInfo = null; JObject defSettings = new JObject(); if (defaultsVal != null) { var deftype = defaultsVal.GetType(); if (deftype.IsGenericType && deftype.GetGenericTypeDefinition() == typeof(Optional <>)) { ExtractOptional(ref defaultsVal, ref deftype); } success = _catalog.TryFindComponent(deftype, out defaultInfo); Contracts.Assert(success); builder = new InputBuilder(_ectx, deftype, _catalog); defSettings = builder.GetJsonObject(defaultsVal, inputBindingMap, inputMap); } if (instanceInfo.Name != defaultInfo?.Name || instSettings.ToString() != defSettings.ToString()) { var jcomponent = new JObject { { FieldNames.Name, new JValue(instanceInfo.Name) } }; if (instSettings.ToString() != defSettings.ToString()) { jcomponent.Add(FieldNames.Settings, instSettings); } result.Add(field.Name, jcomponent); } } else { // REVIEW: pass in the bindings once we support variables in inner fields. // Handle structs. var builder = new InputBuilder(_ectx, type, _catalog); result.Add(field.Name, builder.GetJsonObject(instanceVal, new Dictionary <string, List <ParameterBinding> >(), new Dictionary <ParameterBinding, VariableBinding>())); } } } return(result); }