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)); }
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}"); } } }
/* * 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)))); } }
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); }
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); }
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}"); } }
/// <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); }
/// <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); }
/* * 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()); }
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); }