Пример #1
0
        public string DoCall(Api.Methods method, string path, IQueryCollection queryStr, Stream body)
        {
            string request = null;

            if (body != null)
            {
                using (StreamReader reader = new StreamReader(body, Encoding.UTF8))
                {
                    request = reader.ReadToEnd();
                }
            }

            if (path.Equals("/"))
            {
                if (!command.IsAdminMode)
                {
                    throw new ArgumentException("Not running in Admin mode");
                }
                string args = queryStr["command"];
                if (args == null)
                {
                    args = queryStr["cmd"];
                }
                return(this.command.AdminMode(args, request));
            }

            Dictionary <string, string> query = queryStr.Keys.Cast <string>()
                                                .ToDictionary(k => k, v => (string)queryStr[v]);

            return(this.simulator.Call(method, path, query, request));
        }
Пример #2
0
        private void WriteApi(StreamWriter file, Api.Methods method, string path, Entity entity,
                              string persist = null, string comment = null)
        {
            file.WriteLine($"  - method: {method}");
            file.WriteLine($"    path: \"{path}\"");

            if (entity != null)
            {
                // Guess Request/Response based on standard methods
                if (method == Api.Methods.GET)
                {
                    // Response only with modifiers to replace attributes with path variables
                    string pathVars = ExtractPathVars(path);
                    if (pathVars.Length > 0)
                    {
                        file.WriteLine($"    response: {entity.Name}, {pathVars}");
                    }
                    else
                    {
                        file.WriteLine($"    response: \"[{entity.Name}]\"");
                    }
                }
                else if (method == Api.Methods.PUT || method == Api.Methods.PATCH)
                {
                    // Request and response
                    string pathVars = ExtractPathVars(path);
                    file.WriteLine($"    request: {entity.Name}, {pathVars}");
                    file.WriteLine($"    response: {entity.Name}, *=~request");
                }
                else if (method == Api.Methods.POST)
                {
                    // Request with modifier to remove first attribute (id) and response
                    string firstAttr = entity.ChildOrder.FirstOrDefault();
                    if (firstAttr != null)
                    {
                        file.WriteLine($"    request: {entity.Name}, !{firstAttr}");
                    }
                    else
                    {
                        file.WriteLine($"    request: {entity.Name}");
                    }

                    file.WriteLine($"    response: {entity.Name}, *=~request");
                }

                if (persist != null)
                {
                    file.WriteLine($"    persist: \"{persist}\"");
                }

                if (comment != null)
                {
                    file.WriteLine($"    # {comment}");
                }
            }
        }
Пример #3
0
        /*
         * Returns the JObject (used like a Dictionary) of the response
         */
        public async Task <JObject> FetchAndParse(Api.Methods method, string path, Dictionary <string, string> body)
        {
            string responseAsString = await Fetch(method, path, body);

            if (responseAsString.StartsWith("{"))
            {
                return(new JObject(new JProperty("result", JObject.Parse(responseAsString))));
            }
            else
            {
                // The API either returns a {data...} which is a JSon object or a [data...] which is an array
                return(new JObject(new JProperty("result", JArray.Parse(responseAsString))));
            }
        }
Пример #4
0
        private bool IsValidMethod(CommandOption option, out Api.Methods method)
        {
            method = Api.Methods.GET;

            if (option.HasValue())
            {
                try
                {
                    method = (Api.Methods)Enum.Parse(typeof(Api.Methods), option.Value(), true);
                }
                catch (ArgumentException)
                {
                    Console.WriteLine($"--method must be one of: {String.Join(", ", Enum.GetNames(typeof(Api.Methods)))}");
                    return(false);
                }
            }

            return(true);
        }
Пример #5
0
        public Api MatchRoute(Api.Methods method, string path, out Cache cache)
        {
            if (this.ApiService == null)
            {
                throw new Exception("Resolver has not been initialised");
            }

            Route route = new Route(path);

            foreach (var api in this.ApiService.Apis)
            {
                if (method == api.Method && route.Equals(api.Path, out cache))
                {
                    return(api);
                }
            }

            cache = null;
            return(null);
        }
Пример #6
0
        public string GetRequest(Api.Methods method, string path, Dictionary <string, string> query)
        {
            // The cache contains resolved values only and stores values
            // for path variables, query variables and all the request attributes.

            // Get matched api and create cache containing path variables
            Api api = this.resolver.MatchRoute(method, path, out var cache);

            if (api == null)
            {
                throw new KeyNotFoundException();
            }

            // Add query pairs to cache
            foreach (KeyValuePair <string, string> pair in query)
            {
                cache.AddResolved("query." + pair.Key, pair.Value);
            }

            if (api.Request == null)
            {
                throw new ArgumentException($"The called API does not have a request defined in the YAML file");
            }

            // Get request
            Entity request;

            try
            {
                request = this.resolver.FindEntity(api.Request.EntityDef);
                return(this.formatter.EntityToJson(request, cache, api.Request.Mods));
            }
            catch (Exception e)
            {
                throw new ArgumentException($"Failed to resolve request entity: {e.Message}");
            }
        }
Пример #7
0
        /// <summary>
        /// COMMAND: Write
        ///
        /// Reads the supplied info (or JSON file) and appends the data to the YAML file
        /// or creates the YAML file it if it doesn't already exist.
        /// </summary>
        public int Write(string yamlFile, Api.Methods?method, string path, string entityName,
                         string jsonFile, bool writeYaml, string request)
        {
            // Path must be valid
            if (path != null)
            {
                path = cleanPath(path);
                if (path.Contains(':') || path.Contains('\\'))
                {
                    Console.WriteLine("--path must not contain a colon or backslash");
                    return(1);
                }
            }

            if (!writeYaml && File.Exists(yamlFile))
            {
                if (!InitResolver(yamlFile, false))
                {
                    return(1);
                }

                if (path != null)
                {
                    // Matching route must not already exist
                    Api.Methods checkMethod = method ?? Api.Methods.GET;
                    var         matchedApi  = this.resolver.MatchRoute(checkMethod, path, out var cache);
                    if (matchedApi != null)
                    {
                        Console.WriteLine($"Matching route '{checkMethod.ToString()} {matchedApi.Path}' already exists in YAML file {yamlFile}");
                        return(1);
                    }
                }
            }

            bool   isSample   = false;
            string entityFile = jsonFile;

            if (entityName != null && entityName.Equals(Entity.SAMPLE))
            {
                isSample   = true;
                entityName = Entity.SAMPLE.Substring(1);
                entityFile = YamlParser.SAMPLE;
            }

            if (request != null)
            {
                if (writeYaml)
                {
                    // Replace entire yaml file after validating new version
                    if (ReplaceYaml(yamlFile, request))
                    {
                        Console.WriteLine($"YAML file has been replaced and reloaded");
                        return(0);
                    }
                    else
                    {
                        return(1);
                    }
                }

                // Need to treat body same as if an input file was specified
                entityFile = "from request";
            }

            Entity entity = null;

            if (entityName != null)
            {
                if (File.Exists(yamlFile))
                {
                    try
                    {
                        entity = this.resolver.FindEntity(entityName);
                    }
                    catch (Exception)
                    {
                        entity = null;
                    }
                }

                if (entityFile == null)
                {
                    // Entity must exist if it is not being added. This is used to add an existing
                    // entity as the request/response of an API that is being written.
                    if (entity == null)
                    {
                        Console.WriteLine($"Entity '{entityName}' not found in YAML file {yamlFile}");
                        return(1);
                    }
                }
                else
                {
                    // Entity must not exist if it is being added
                    if (entity != null)
                    {
                        Console.WriteLine($"Entity '{entityName}' already exists in YAML file {yamlFile}");
                        return(1);
                    }

                    if (isSample)
                    {
                        // Load entity from sample YAML file instead
                        try
                        {
                            ApiService sampleService = this.yamlParser.LoadFile(entityFile);
                            entity = sampleService.EntityRoot.ChildEntities["sample"];
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine($"Failed to load sample YAML file {entityFile}: {e.Message}");
                            return(1);
                        }
                    }
                    else if (request != null)
                    {
                        // JSON body must be valid
                        try
                        {
                            entity = this.jsonParser.LoadEntity(entityName, request);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine($"Failed to parse request JSON: {e.Message}");
                            return(1);
                        }
                    }
                    else
                    {
                        // JSON file must be valid
                        try
                        {
                            entity = this.jsonParser.LoadEntityFromFile(entityName, entityFile);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine($"Failed to load JSON file {entityFile}: {e.Message}");
                            return(1);
                        }
                    }
                }
            }

            // If YAML file already exists, back it up before updating it
            string[] lines          = new string[0];
            int      entityLastLine = -1;
            int      aliasLastLine  = -1;
            int      apiLastLine    = -1;

            bool backedUp = false;

            if (File.Exists(yamlFile))
            {
                BackupFile(yamlFile);
                backedUp = true;

                // Read the YAML file as an array of lines
                lines = File.ReadAllLines(yamlFile);

                // Find the end of the entity and api sections (so we can append to them)
                bool inEntity = false;
                bool inAlias  = false;
                bool inApi    = false;
                int  lastLine = -1;
                for (int i = 0; i <= lines.Length; i++)
                {
                    string line;
                    if (i == lines.Length)
                    {
                        // Force start of new section
                        line = "<END>";
                    }
                    else
                    {
                        line = lines[i].TrimEnd();
                    }

                    if (line.Length > 0 && line[0] != ' ' && line[0] != '#')
                    {
                        // New section
                        if (inEntity)
                        {
                            entityLastLine = lastLine;
                        }
                        else if (inAlias)
                        {
                            aliasLastLine = lastLine;
                        }
                        else if (inApi)
                        {
                            apiLastLine = lastLine;
                        }

                        inEntity = line.Equals("entity:");
                        inAlias  = line.Equals("alias:");
                        inApi    = line.Equals("api:");
                    }

                    if (line.Length > 0)
                    {
                        lastLine = i;
                    }
                }
            }

            // Write the YAML file and insert the new data
            try
            {
                using (StreamWriter file = new StreamWriter(yamlFile))
                {
                    for (int i = -1; i < lines.Length; i++)
                    {
                        if (i >= 0)
                        {
                            file.WriteLine(lines[i]);
                        }

                        // Add entity to YAML file
                        if (entityFile != null && i == entityLastLine)
                        {
                            if (entityLastLine == -1)
                            {
                                file.WriteLine($"entity:");
                                file.Write(Entity.PrettyPrint(entity, "  "));
                                if (aliasLastLine != -1 || apiLastLine != -1)
                                {
                                    file.WriteLine();
                                }
                            }
                            else
                            {
                                file.WriteLine();
                                file.Write(Entity.PrettyPrint(entity, "  "));
                            }
                        }

                        // Add alias to YAML file (complete CRUD set only)
                        if (method == null && path != null && entity != null && i == aliasLastLine)
                        {
                            string aliasDef = $"  {entity.Name}_list: \"[{entity.Name}, 5]\"";
                            if (aliasLastLine == -1)
                            {
                                file.WriteLine($"\nalias:\n{aliasDef}");
                                if (apiLastLine != -1)
                                {
                                    file.WriteLine();
                                }
                            }
                            else
                            {
                                file.WriteLine(aliasDef);
                            }
                        }

                        // Add API to YAML file
                        if (path != null && ((i != -1 && i == apiLastLine) || (apiLastLine == -1 && i == lines.Length - 1)))
                        {
                            if (apiLastLine == -1)
                            {
                                if (lines.Length > 0 || entityFile != null)
                                {
                                    file.WriteLine();
                                }
                                file.WriteLine($"api:");
                            }
                            else
                            {
                                file.WriteLine();
                            }

                            WriteApis(file, method, path, entity);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to write YAML file '{yamlFile}': {e.Message}");
                if (backedUp)
                {
                    RestoreFile(yamlFile);
                    return(1);
                }
            }

            Console.WriteLine($"Updated file: {yamlFile}");

            if (IsAdminMode)
            {
                // Reload yaml file to validate it
                if (!InitResolver(yamlFile, false))
                {
                    return(1);
                }

                Console.WriteLine($"YAML file has been reloaded");
            }

            return(0);
        }
Пример #8
0
        /// <summary>
        /// COMMAND: Call
        ///
        /// Simulates calling the specified endpoint and writes the response to stdout or a file.
        /// Can also output the request rather than the response if required.
        ///
        /// The response will have any modifiers applied that are specified in the YAML file's
        /// api response definition which is what makes the output different from the Read
        /// command's entity output.
        /// </summary>
        public int Call(string yamlFile, Api.Methods method, string path, bool wantRequest, string requestFile, string outFile)
        {
            if (!InitResolver(yamlFile, true))
            {
                return(1);
            }

            // Redirect output if outFile specified
            if (outFile != null && !IsAdminMode)
            {
                if (!redirectOutput(outFile, true))
                {
                    return(1);
                }
            }

            // Extract query string from path and process
            var query = new Dictionary <string, string>();
            int sep   = path.IndexOf('?');

            if (sep != -1)
            {
                string queryStr = path.Substring(sep + 1);
                path = path.Substring(0, sep);
                var queryPairs = queryStr.Split('&');
                foreach (var queryPair in queryPairs)
                {
                    var pair = queryPair.Split('=');
                    var key  = pair[0].Trim();
                    if (key.Length > 0)
                    {
                        if (pair.Length == 1)
                        {
                            query.Add(key, "true");
                        }
                        else
                        {
                            query.Add(key, pair[1].Trim());
                        }
                    }
                }
            }

            string body = null;

            if (requestFile != null)
            {
                body = File.ReadAllText(requestFile);
            }

            string response;

            try
            {
                if (wantRequest)
                {
                    response = this.simulator.GetRequest(method, path, query);
                }
                else
                {
                    response = this.simulator.Call(method, path, query, body);
                }
            }
            catch (KeyNotFoundException)
            {
                response = "404 Not Found";
            }
            catch (ArgumentException e)
            {
                response = $"400 Bad Request - {e.Message}";
            }

            Console.WriteLine(response);

            if (outFile != null && !IsAdminMode)
            {
                redirectOutput(outFile, false);
                Console.WriteLine($"Output written to file: {outFile}");
            }

            return(0);
        }
Пример #9
0
        /*
         * Returns the content of a request
         */
        public async Task <string> Fetch(Api.Methods method, string path, Dictionary <string, string> parameters = null)
        {
            if (parameters == null)
            {
                parameters = new Dictionary <string, string>();
            }

            if (!parameters.ContainsKey("api_key"))
            {
                parameters.Add("api_key", credentials.Token);
            }

            FormUrlEncodedContent content = new FormUrlEncodedContent(parameters);

            // Full Path
            string fullpath = "https://" + CHALLONGE_API_URL + "/" + path;
            string query    = "";

            HttpResponseMessage response;

            switch (method)
            {
            case Methods.GET:
                foreach (KeyValuePair <string, string> entry in parameters)
                {
                    query += HttpUtility.UrlEncode(entry.Key) + "=" + HttpUtility.UrlEncode(entry.Value) + "&";
                }
                response = await client.GetAsync(fullpath + "?" + query);

                break;

            case Methods.POST:
                response = await client.PostAsync(fullpath, content);

                break;

            case Methods.PUT:
                response = await client.PutAsync(fullpath, content);

                break;

            case Methods.DELETE:
                foreach (KeyValuePair <string, string> entry in parameters)
                {
                    query += HttpUtility.UrlEncode(entry.Key) + "=" + HttpUtility.UrlEncode(entry.Value) + "&";
                }
                response = await client.DeleteAsync(fullpath + "?" + query);

                break;

            default:
                response = null;
                break;
            }

            if (response == null)
            {
                return("");
            }
            return(await response.Content.ReadAsStringAsync());
        }
Пример #10
0
        public string Call(Api.Methods method, string path, Dictionary <string, string> query, string request)
        {
            // The cache contains resolved values only and stores values
            // for path variables, query variables and all the request attributes.

            // Get matched api and create cache containing path variables
            Api api = this.resolver.MatchRoute(method, path, out var cache);

            if (api == null)
            {
                throw new KeyNotFoundException();
            }

            // Add query pairs to cache
            foreach (KeyValuePair <string, string> pair in query)
            {
                cache.AddResolved("query." + pair.Key, pair.Value);
            }

            // Parse request and add to cache
            if (request == null)
            {
                // Fail if request is expected
                if (api.Request != null)
                {
                    throw new ArgumentException("Request body was expected but not supplied");
                }
            }
            else
            {
                // Fail if request is not expected
                if (api.Request == null)
                {
                    throw new ArgumentException("Request body was supplied but not expected. " +
                                                "The called API does not have a request defined in the YAML file");
                }

                Entity requestEntity;
                try
                {
                    requestEntity = this.jsonParser.LoadEntity(api.Request.EntityName, request);
                }
                catch (Exception e)
                {
                    throw new ArgumentException($"Failed to parse request JSON: {e.Message}");
                }

                RequestToCache("request", requestEntity, cache);

                // If any modded request attributes are missing from the request,
                // add them to the cache.
                foreach (var entry in api.Request.Mods)
                {
                    if (entry.Value != null)
                    {
                        string attrib = "request." + entry.Key;
                        if (!cache.HasValue(attrib))
                        {
                            if (entry.Value[0] == '~')
                            {
                                // Mod is a reference
                                try
                                {
                                    string value = cache.GetValue(entry.Value.Substring(1)).Value;
                                    cache.AddResolved(attrib, value);
                                }
                                catch (Exception)
                                {
                                    // Do nothing
                                }
                            }
                            else
                            {
                                cache.AddResolved(attrib, entry.Value);
                            }
                        }
                    }
                }
            }

            Entity response = null;

            if (api.Response != null)
            {
                // Get response entity
                try
                {
                    response = this.resolver.FindEntity(api.Response.EntityDef);
                }
                catch (Exception e)
                {
                    throw new ArgumentException($"Response entity {e.Message}");
                }

                if (response == null)
                {
                    return(null);
                }
            }

            string json = null;

            if (api.Persist == null)
            {
                if (response != null)
                {
                    // Generate response
                    try
                    {
                        addResponseMods(cache, api.Response.Mods);
                        json = this.formatter.EntityToJson(response, cache, api.Response.Mods);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException($"Failed to resolve response entity: {e.Message}");
                    }
                }
            }
            else if (api.Method == Api.Methods.GET)
            {
                if (response != null)
                {
                    // Determine if response is an array or single object
                    while (response.Type == Entity.Types.REF)
                    {
                        Entity refEntity;

                        try
                        {
                            refEntity = this.resolver.FindEntity(response.Value);
                        }
                        catch (Exception e)
                        {
                            throw new ArgumentException($"Entity '{response.Name}' has bad reference '{response.Value}': {e.Message}");
                        }

                        response = refEntity;
                    }

                    if (response.Type == Entity.Types.REPEAT)
                    {
                        // Get array response from persisted files
                        var sb = new StringBuilder();
                        foreach (var filename in Directory.GetFiles(api.Persist.Folder, api.Persist.WildPattern).OrderBy(f => f))
                        {
                            if (sb.Length == 0)
                            {
                                sb.Append("[");
                            }
                            else
                            {
                                sb.Append(", ");
                            }

                            sb.Append(LoadResponse(filename, cache, api, false));
                        }

                        if (sb.Length == 0)
                        {
                            sb.Append("[]");
                        }
                        else
                        {
                            sb.Append("]");
                        }

                        json = sb.ToString();
                    }
                    else
                    {
                        // Get object response from persisted file
                        string filename;
                        try
                        {
                            filename = api.Persist.InsertVars(cache, api.Request, api.Response);
                        }
                        catch (Exception e)
                        {
                            throw new ArgumentException(e.Message);
                        }

                        bool forceResolve = false;
                        if (filename.Contains('*'))
                        {
                            // Choose file at random
                            var filenames = Directory.GetFiles(api.Persist.Folder, Path.GetFileName(filename));
                            if (filenames.Length == 0)
                            {
                                throw new KeyNotFoundException();
                            }

                            int chosen = random.Next(filenames.Length);
                            filename = filenames[chosen];

                            // Need to force resolve so id gets replaced
                            // with requested one (by response mods).
                            forceResolve = true;
                        }
                        else
                        {
                            // Load specific file
                            if (!File.Exists(filename))
                            {
                                throw new KeyNotFoundException();
                            }
                        }

                        json = LoadResponse(filename, cache, api, forceResolve);
                    }
                }
            }
            else
            {
                if (response != null)
                {
                    // Generate response
                    try
                    {
                        json = this.formatter.EntityToJson(response, cache, api.Response.Mods);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException($"Failed to resolve response entity: {e.Message}");
                    }
                }

                string filename;
                try
                {
                    filename = api.Persist.InsertVars(cache, api.Request, api.Response);
                }
                catch (Exception e)
                {
                    throw new ArgumentException(e.Message);
                }

                if (api.Method == Api.Methods.DELETE)
                {
                    // Delete persisted file
                    File.Delete(filename);
                }
                else
                {
                    // Save persisted file
                    File.WriteAllText(filename, json);
                }
            }

            return(json);
        }