Esempio n. 1
0
        /// <summary>
        /// Adds the (not yet resolved) attributes of the specified entity to the list.
        /// Throws an error if any attribute has children (must be a flat structure).
        ///
        /// Returns true if the entity is a repeating entity.
        /// </summary>
        private bool AddAttributes(List <ResolvedValue> attributes, string parentName, Entity entity, string entityName = "")
        {
            string fullName;

            if (parentName != null)
            {
                fullName = parentName + "." + entity.Name;
            }
            else
            {
                fullName = entity.Name;
            }

            if (entityName.Length == 0)
            {
                entityName = entity.Name;
            }

            if (entity.Type == Entity.Types.PARENT || entity.Type == Entity.Types.ARRAY || entity.Type == Entity.Types.REPEAT)
            {
                if (attributes.Count == 0 && entity.Type == Entity.Types.PARENT)
                {
                    foreach (var childName in entity.ChildOrder)
                    {
                        Entity child = entity.ChildEntities[childName];
                        AddAttributes(attributes, fullName, child);
                    }
                }
                else if (attributes.Count == 0 && entity.Type == Entity.Types.REPEAT)
                {
                    return(true);
                }
                else
                {
                    throw new Exception($"Cannot output attribute '{entity.Name}' in SQL or CSV format as it has children. " +
                                        "Only flat structures can be output in these formats.");
                }
            }
            else if (entity.Type == Entity.Types.REF)
            {
                Entity refEntity;
                try
                {
                    refEntity = this.resolver.FindEntity(entity.Value);
                }
                catch (Exception)
                {
                    throw new Exception($"Attribute '{entityName}' references unknown entity '{entity.Value}'");
                }

                AddAttributes(attributes, null, refEntity, entityName);
            }
            else
            {
                var attribute = new ResolvedValue(fullName, entity.Type, entity.Value);
                attributes.Add(attribute);
            }

            return(false);
        }
Esempio n. 2
0
        public void TestResolveRef()
        {
            string yaml = @"
entity:
  test: STR, This is a test
  myref: REF, test

alias:
  myalias: test
            ";

            var apiService = this.yamlParser.Load(yaml);

            this.resolver.Init(apiService);

            var entity = this.resolver.FindEntity("myref");

            Assert.NotNull(entity);

            var resolving = new ResolvedValue("test", entity.Type, entity.Value);

            this.resolver.Resolve(resolving, new Cache());
            Assert.Equal("This is a test", resolving.Value);

            var entity2 = this.resolver.FindEntity("myalias");

            Assert.NotNull(entity2);

            var resolving2 = new ResolvedValue("test2", entity2.Type, entity2.Value);

            this.resolver.Resolve(resolving2, new Cache());
            Assert.Equal("This is a test", resolving2.Value);
        }
Esempio n. 3
0
        private ResolvedValue GetRefValue(string refName, Cache cache, string parent, IResolver resolver, IFormatter formatter = null)
        {
            if (refName.Length > 1 && refName[0] == '~')
            {
                refName = refName.Substring(1);
            }

            string fullRefName = parent + "." + refName;
            Entity refAttrib;

            try
            {
                refAttrib = resolver.FindEntity(fullRefName);
                var refValue = new ResolvedValue(fullRefName, refAttrib.Type, refAttrib.Value);
                try
                {
                    resolver.Resolve(refValue, cache);
                }
                catch (Exception e)
                {
                    throw new Exception($"references unresolvable attribute '{refName}': {e.Message}");
                }

                return(refValue);
            }
            catch (Exception)
            {
                // Allow an unknown attribute if it's in the cache (might be a temporary attribute created by a mod)
                try
                {
                    return(cache.GetValue(fullRefName));
                }
                catch
                {
                    // do nothing
                }

                if (formatter != null)
                {
                    // Might be a complete entity rather than an attribute
                    Entity refEntity;
                    try
                    {
                        refEntity = resolver.FindEntity(refName);
                        // The value is the entity in JSON format
                        string strValue = formatter.EntityToJson(refEntity, cache);
                        var    refValue = new ResolvedValue("", Entity.Types.OBJ, strValue);
                        refValue.SetValue(strValue);
                        return(refValue);
                    }
                    catch (Exception)
                    {
                        throw new Exception($"references unknown attribute or entity '{refName}'");
                    }
                }

                throw new Exception($"references unknown attribute '{refName}'");
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Resolves an unresolved value and adds it to the cache.
        /// If any other values need to be resolved before this one
        /// (dependencies) then they are also resolved and added to
        /// the cache.
        ///
        /// The value and any dependencies are added to the cache
        /// once resolved. The cache provides a consistent view with
        /// consistent genders across a complete entity. When resolving
        /// a new entity pass in a new cache.
        /// </summary>
        public void Resolve(ResolvedValue resolving, Cache cache, IFormatter formatter = null)
        {
            if (formatter != null)
            {
                this.formatter = formatter;
            }

            try
            {
                ResolveValue(resolving, cache);
            }
            catch (Exception e)
            {
                throw new Exception($"Cannot resolve {resolving.Name}: {e.Message}");
            }
        }
Esempio n. 5
0
        /// <summary>
        /// Resolve all mods and add to cache (ignore special cases)
        /// </summary>
        private void addResponseMods(Cache cache, Dictionary <string, string> mods)
        {
            foreach (var mod in mods)
            {
                ResolvedValue resolving;

                if (!mod.Key.Equals("*") && mod.Value != null)
                {
                    if (mod.Value[0] == '~')
                    {
                        // Try to retrieve modded value from cache
                        string findName = mod.Value.Substring(1);

                        var refEntity = this.resolver.FindEntity(findName);
                        resolving = new ResolvedValue(findName, refEntity.Type, refEntity.Value);
                        this.resolver.Resolve(resolving, cache, this.formatter);

                        // Also add to cache using mod key name
                        cache.AddResolved(mod.Key, resolving.Value);
                    }
                    else
                    {
                        // Mod value is a literal
                        string value;
                        if (mod.Value[0] == '"' && mod.Value[mod.Value.Length - 1] == '"')
                        {
                            value = mod.Value.Substring(1, mod.Value.Length - 2);
                        }
                        else
                        {
                            value = mod.Value;

                            // Still needs resolving if it contains funcs
                            //if (value.Contains("func."))
                            //{
                            //    resolving = new ResolvedValue(mod.Key, Entity.Types.STR, value);
                            //    this.resolver.Resolve(resolving, cache, this.formatter);
                            //    return;
                            //}
                        }

                        cache.AddResolved(mod.Key, value);
                    }
                }
            }
        }
Esempio n. 6
0
        public void TestResolveCombinedFuncs()
        {
            /// Note: Individual funcs are tested by FuncsTests

            string yaml = @"
entity:
  test: ""STR, func.pick(me)-func.pick(me2) : func.pick(me3)""
            ";

            var apiService = this.yamlParser.Load(yaml);

            this.resolver.Init(apiService);

            var entity = this.resolver.FindEntity("test");

            Assert.NotNull(entity);

            var resolving = new ResolvedValue("test", entity.Type, entity.Value);

            this.resolver.Resolve(resolving, new Cache());
            Assert.Equal("me-me2 : me3", resolving.Value);
        }
Esempio n. 7
0
        /// <summary>
        /// Outputs the entity in JSON format after resolving all values
        /// </summary>
        private string ToJson(string parentName, Entity entity, Cache cache,
                              Dictionary <string, string> mods, int nestedLevel, string knownName = "")
        {
            if (knownName.Length == 0)
            {
                knownName = entity.Name;
            }

            string fullName;

            if (parentName != null)
            {
                int parentLast = parentName.LastIndexOf('.');
                if (parentLast != -1 && parentName[parentLast + 1] == '~' && entity.Type == Entity.Types.PARENT)
                {
                    fullName = parentName;
                }
                else
                {
                    fullName = parentName + "." + entity.Name;
                }
            }
            else
            {
                fullName = entity.Name;
            }

            // Get mod
            string mod    = null;
            bool   modAll = false;

            if (mods != null)
            {
                if (mods.TryGetValue(fullName, out mod))
                {
                    if (mod == null)
                    {
                        // Attribute should be ignored
                        return("");
                    }
                }
                else if (mods.TryGetValue("*", out mod))
                {
                    modAll = true;
                }
            }

            if (nestedLevel > 500 && parentName != null)
            {
                throw new Exception($"Cannot resolve entity '{parentName}' as it contains a circular reference");
            }

            StringBuilder sb           = new StringBuilder();
            bool          isArrayChild = knownName.StartsWith("~") || knownName.StartsWith('#');

            if (entity.Type == Entity.Types.PARENT | entity.Type == Entity.Types.ARRAY)
            {
                if (parentName != null && !isArrayChild)
                {
                    sb.Append($"{entity.Name}: ");
                }

                if (entity.Type == Entity.Types.PARENT)
                {
                    sb.Append("{");
                }
                else
                {
                    sb.Append("[");
                }

                bool isFirst = true;
                foreach (var entityName in entity.ChildOrder)
                {
                    var toJsonStr = ToJson(fullName, entity.ChildEntities[entityName], cache, mods, nestedLevel + 1);
                    if (toJsonStr.Length > 0)
                    {
                        if (!isFirst)
                        {
                            sb.Append(", ");
                        }
                        else
                        {
                            isFirst = false;
                        }

                        sb.Append(toJsonStr);
                    }
                }

                if (entity.Type == Entity.Types.PARENT)
                {
                    sb.Append("}");
                }
                else
                {
                    sb.Append("]");
                }
            }
            else if (entity.Type == Entity.Types.REF)
            {
                Entity refEntity;

                try
                {
                    refEntity = this.resolver.FindEntity(entity.Value);
                }
                catch (Exception e)
                {
                    throw new Exception($"Attribute '{knownName}' has bad reference '{entity.Value}': {e.Message}");
                }

                string refParent;
                if (modAll)
                {
                    refParent = fullName;
                }
                else
                {
                    refParent = null;
                }

                if (refEntity.Type == Entity.Types.REPEAT)
                {
                    sb.Append($"{ToJson(refParent, refEntity, cache, mods, nestedLevel + 1, "#" + knownName)}");
                }
                else
                {
                    sb.Append($"{ToJson(refParent, refEntity, cache, mods, nestedLevel + 1, knownName)}");
                }
            }
            else if (entity.Type == Entity.Types.REPEAT)
            {
                if (knownName.StartsWith('#'))
                {
                    sb.Append($"{knownName.Substring(1)}: ");
                }

                Entity refEntity;
                try
                {
                    refEntity = this.resolver.FindEntity(entity.Value);
                }
                catch (Exception)
                {
                    throw new Exception($"Attribute '{knownName}' references unknown entity '{entity.Value}'");
                }

                string modParent;
                int    repeatCount;
                if (modAll && mod[0] == '~')
                {
                    modParent = parentName + ".~";
                    string childGroup = mod.Substring(1) + "." + modParent;
                    repeatCount = 0;
                    while (true)
                    {
                        if (!cache.HasValue(childGroup + (repeatCount + 1)))
                        {
                            break;
                        }
                        repeatCount++;
                    }
                }
                else
                {
                    // Choose a random repeat count in the range specified
                    modParent   = null;
                    repeatCount = random.Next(entity.Min, entity.Max + 1);
                }

                sb.Append("[");
                for (int i = 0; i < repeatCount; i++)
                {
                    if (i > 0)
                    {
                        sb.Append(", ");
                    }

                    if (modParent == null)
                    {
                        // Use a clean cache for each instance (so they are not related)
                        sb.Append($"{ToJson(null, refEntity, new Cache(), mods, nestedLevel + 1, knownName)}");
                    }
                    else
                    {
                        // Getting data from request which is in cache
                        sb.Append($"{ToJson(modParent + (i + 1), refEntity, cache, mods, nestedLevel + 1, knownName)}");
                    }
                }
                sb.Append("]");
            }
            else
            {
                if (!isArrayChild)
                {
                    sb.Append($"{knownName}: ");
                }

                ResolvedValue resolving = null;

                if (modAll)
                {
                    // Apply mod prefix to attribute name.
                    // e.g. if *=~request, employee.id becomes ~request.employee.id
                    mod += "." + fullName;
                    if (mod[0] == '~')
                    {
                        mod = mod.Substring(1);
                    }

                    // Retrieve from cache
                    try
                    {
                        resolving = cache.GetValue(mod);
                    }
                    catch (Exception e)
                    {
                        // Not an error if request or query value not found
                        if (!mod.StartsWith("request.") && !mod.StartsWith("query."))
                        {
                            throw e;
                        }
                        resolving = null;
                    }
                }

                if (resolving == null)
                {
                    // No mod applied
                    resolving = new ResolvedValue(fullName, entity.Type, entity.Value);
                    this.resolver.Resolve(resolving, cache, this);
                }

                sb.Append(resolving.GetValue(false));
            }

            return(sb.ToString());
        }
Esempio n. 8
0
        /// <summary>
        /// Evaluates a single function. Function name must be a known
        /// function or an exception will be thrown.
        ///
        /// The arg list is resolved (as a whole) before being split into
        /// separate arguments so it may contain embedded func.xxx() calls.
        /// </summary>
        private string eval(string funcName, string argList, Cache cache, string parentName, out Sample.Genders gender)
        {
            string called = $"func.{funcName}({argList})";

            // Validate function name
            Funcs.FuncNames func;
            try
            {
                func = (Funcs.FuncNames)Enum.Parse(typeof(Funcs.FuncNames), funcName.Trim(), true);
            }
            catch (Exception)
            {
                throw new Exception($"Unknown function 'func.{funcName}(...)'. Must be one of: {String.Join(", ", Enum.GetNames(typeof(Funcs.FuncNames)))}");
            }

            // Resolve arg list (in case funcs are being passed as args to other funcs) but don't add
            // to cache as we want them to evaluate differently each time (if they're random).
            string nocacheParent;
            int    sep = parentName.IndexOf('.');

            if (sep == -1)
            {
                nocacheParent = parentName + ".NOCACHE";
            }
            else
            {
                nocacheParent = parentName.Substring(0, sep) + ".NOCACHE";
            }
            var resolvingArgs = new ResolvedValue(nocacheParent, Entity.Types.STR, argList);

            ResolveValue(resolvingArgs, cache);
            gender = resolvingArgs.Gender;

            // Replace escaped brackets and split up arguments
            var args = SplitArgs(resolvingArgs.Value.Replace("\\(", "(").Replace("\\)", ")").Trim());

            // Call the relevant function
            string result;

            Sample.Genders newGender;
            switch (func)
            {
            case Funcs.FuncNames.DATE:
                return(this.funcs.FuncDate(called, args));

            case Funcs.FuncNames.GEN:
                return(this.funcs.FuncGen(called, args));

            case Funcs.FuncNames.IF:
                return(this.funcs.FuncIf(called, args, cache, parentName, this, this.formatter));

            case Funcs.FuncNames.MATH:
                return(this.funcs.FuncMath(called, args, cache, parentName, this));

            case Funcs.FuncNames.NUM:
                return(this.funcs.FuncNum(called, args, cache, parentName, this));

            case Funcs.FuncNames.PICK:
                return(this.funcs.FuncPick(called, args));

            case Funcs.FuncNames.RAND:
                return(this.funcs.FuncRand(called, args));

            case Funcs.FuncNames.REF:
                result = this.funcs.FuncRef(called, args, cache, parentName, this, out newGender);
                if (newGender != Sample.Genders.NEUTRAL)
                {
                    gender = newGender;
                }
                return(result);

            case Funcs.FuncNames.SAMPLE:
                result = this.funcs.FuncSample(called, args, cache, parentName, this, out newGender);
                if (newGender != Sample.Genders.NEUTRAL)
                {
                    gender = newGender;
                }
                return(result);

            case Funcs.FuncNames.SPLIT:
                return(this.funcs.FuncSplit(called, args, cache, parentName, this));

            case Funcs.FuncNames.STR:
                return(this.funcs.FuncStr(called, args));

            case Funcs.FuncNames.TIME:
                return(this.funcs.FuncTime(called, args));

            default:
                throw new Exception($"Missing function name: {func}");
            }
        }
Esempio n. 9
0
        /// <summary>
        /// Takes a literal string and looks for any embedded
        /// func.xxx(...) calls. Each embedded function is resolved
        /// and the result is included in the returned string.
        ///
        /// Throws an exception if the value cannot be resolved
        /// </summary>
        private void ResolveValue(ResolvedValue resolving, Cache cache)
        {
            // See if it's already been resolved
            var resolved = cache.GetResolved(resolving.Name);

            if (resolved != null)
            {
                resolving.SetValue(resolved.Value, resolved.Gender);
                return;
            }

            if (resolving.UnresolvedValue == null)
            {
                throw new Exception("Unresolved value cannot be null");
            }

            // If value is already concrete it's resolved
            string value = resolving.UnresolvedValue;

            if (!value.Contains("func."))
            {
                resolving.SetValue(value);
                cache.SetResolved(resolving);
                return;
            }

            StringBuilder resolvedStr = new StringBuilder();

            Sample.Genders gender = Sample.Genders.NEUTRAL;

            int idx = 0;

            while (idx < value.Length)
            {
                int funcPos = value.IndexOf("func.", idx, StringComparison.CurrentCultureIgnoreCase);
                if (funcPos == -1)
                {
                    resolvedStr.Append(value.Substring(idx));
                    break;
                }

                resolvedStr.Append(value.Substring(idx, funcPos - idx));

                // Find function name
                idx = funcPos + 5;
                int argStart = value.IndexOf('(', idx);
                if (argStart == -1)
                {
                    throw new Exception($"Function 'func.{value.Substring(idx)}' has missing opening bracket");
                }
                string funcName = value.Substring(idx, argStart - idx).Trim();

                // Find matching close bracket
                argStart++;
                int argEnd       = argStart;
                int openBrackets = 0;
                while (true)
                {
                    if (argEnd == value.Length)
                    {
                        throw new Exception($"Function 'func.{value.Substring(idx)}' has missing closing bracket");
                    }

                    // Ignore escaped brackets
                    if (value[argEnd] == '\\' && argEnd < value.Length - 1 && (value[argEnd + 1] == '(' || value[argEnd + 1] == ')'))
                    {
                        argEnd += 2;
                        continue;
                    }

                    if (value[argEnd] == '(')
                    {
                        openBrackets++;
                    }
                    else if (value[argEnd] == ')')
                    {
                        if (openBrackets == 0)
                        {
                            break;
                        }
                        else
                        {
                            openBrackets--;
                        }
                    }

                    argEnd++;
                }

                string argList = value.Substring(argStart, argEnd - argStart);
                string parentName;
                int    pos = resolving.Name == null? -1 : resolving.Name.LastIndexOf('.');
                if (pos == -1)
                {
                    parentName = resolving.Name;
                }
                else
                {
                    parentName = resolving.Name.Substring(0, pos);
                }

                resolvedStr.Append(eval(funcName, argList, cache, parentName, out var newGender));
                if (newGender != Sample.Genders.NEUTRAL)
                {
                    gender = newGender;
                }

                idx = argEnd + 1;
            }

            resolving.SetValue(resolvedStr.ToString(), gender);
            cache.SetResolved(resolving);
        }
Esempio n. 10
0
        /// <summary>
        /// Sample function
        ///
        /// The sample function has a circular dependency on the resolver (so we can
        /// have samples based on the gender of other fields which may not be resolved
        /// yet) so we have to pass the resolver in as a parameter.
        /// </summary>
        public string FuncSample(string called, string[] args, Cache cache, string parent, IResolver resolver,
                                 out Sample.Genders gender)
        {
            string help = "Use func.sample(--help) for help.";

            if (args.Length == 1 && args[0].Equals("--help", StringComparison.CurrentCultureIgnoreCase))
            {
                // Show usage
                string usage = "Usage: func.sample(arg1, [arg2]) where arg1 is the name of the samples file to use and arg2 is " +
                               "either another attribute to take the gender from, or M or F to use a fixed gender.";
                throw new Exception(usage);
            }

            if (args.Length < 1 || args.Length > 2)
            {
                throw new Exception($"{called} has bad number of arguments. {help}");
            }

            if (args[0].Length == 0)
            {
                throw new Exception($"{called} has empty argument. {help}");
            }

            gender = Sample.Genders.NEUTRAL;
            if (args.Length == 2)
            {
                if (args[1].Equals("M", StringComparison.CurrentCultureIgnoreCase))
                {
                    gender = Sample.Genders.MALE;
                }
                else if (args[1].Equals("F", StringComparison.CurrentCultureIgnoreCase))
                {
                    gender = Sample.Genders.FEMALE;
                }
                else
                {
                    // Inherit gender from referenced attribute.
                    string refName;
                    if (args[1].Length > 1 && args[1][0] == '~')
                    {
                        refName = parent + "." + args[1].Substring(1);
                    }
                    else
                    {
                        refName = parent + "." + args[1];
                    }

                    Entity refAttrib;
                    try
                    {
                        refAttrib = resolver.FindEntity(refName);
                    }
                    catch (Exception)
                    {
                        throw new Exception($"{called} references unknown attribute '{refName}'");
                    }

                    var refValue = new ResolvedValue(refName, refAttrib.Type, refAttrib.Value);
                    try
                    {
                        resolver.Resolve(refValue, cache);
                    }
                    catch (Exception e)
                    {
                        throw new Exception($"{called} references unresolvable attribute '{refName}': {e.Message}");
                    }

                    gender = refValue.Gender;
                }
            }

            var samples = resolver.GetSamples(args[0]);
            var sample  = samples.Pick(gender);

            gender = sample.Gender;
            return(sample.Value);
        }
Esempio n. 11
0
 /// <see cref="JsonPlusValue.ToString(int, int)"/>
 public string ToString(int indent, int indentSize)
 {
     return(ResolvedValue.ToString(indent, indentSize));
 }
Esempio n. 12
0
 /// <summary>
 /// Returns a string representation of this <see cref="JsonPlusSubstitution"/>.
 /// </summary>
 /// <returns>A string representation of this <see cref="JsonPlusSubstitution"/>.</returns>
 public override string ToString()
 {
     return(ResolvedValue.ToString(0, 2));
 }
Esempio n. 13
0
 /// <see cref="IJsonPlusNode.GetValue()"/>
 public JsonPlusValue GetValue()
 {
     return(ResolvedValue?.GetValue() ?? new JsonPlusValue(Parent));
 }
Esempio n. 14
0
 /// <see cref="IJsonPlusNode.GetObject()"/>
 public JsonPlusObject GetObject()
 {
     return(ResolvedValue?.GetObject() ?? new JsonPlusObject(Parent));
 }
Esempio n. 15
0
 /// <see cref="IJsonPlusNode.GetArray()"/>
 public List <IJsonPlusNode> GetArray()
 {
     return(ResolvedValue?.GetArray() ?? new List <IJsonPlusNode>());
 }
Esempio n. 16
0
 /// <see cref="IJsonPlusNode.GetString()"/>
 public string GetString()
 {
     return(ResolvedValue?.GetString());
 }