Package: apitax
ApitaxPronounced:ahhp-ehh-taxtl;dr: Code examples are at the verybottomof this documentation; however, I highly suggest you read through the documentation to learn what is possible.Finally, as Apitax features an exponential amount of various interactions, not all of them are documented here. Experiment with the syntax, Learn from the syntax, and Enjoy Apitax!What is itApitax is an API automation framework utilizing Commandtax and Scriptax. Commandtax is an API language which helps to quickly prototype powerful rest API requests. Scriptax is an automation language which utilizes Commandtax.A more winded (and more detailed!) description calledWhy use Apitaxis at the very bottom of this documentation.Builds and InstallationStarterPackI highly recommend you use the starter pack to get Apitax up and running. It provides you with a templated repository of files which you can change to better suit your application.The Apitax StarterPack can be found here:https://github.com/Apitax/StarterPackStarterPack Benefits and Features:A complete set of working files to bring up Apitax with a single commandIntegrates Apitax/Core, Apitax/Drivers, Apitax/Scripts, and Apitax/CLI out of the box to give you a great experienceProvides options to launch Apitax/Dashboard as wellCompletely customizable as if you were trying to use the raw Apitax code yourselfEvery service launches in Docker in one swift motionNotes:StarterPack utilizes Pip and Docker to build the application. For this reason, an internet connection is required on the machine you use to build these images.PipApitax on PyPi:https://pypi.org/project/apitax/Using pip, you can bring in Apitax to your project.DockerDocker is the recommended way to deploy Apitax. Please see the StarterPack for examples on how this can be achieved. While building Apitax yourself is possible, it is only recommended to do so if you are developing Apitax.Warning: Apitax/Core is a base Apitax image. It is not meant to be deployed by itself, and must be used as a base for an application image which uses Apitax. Please see the StarterPack for more details.For the programmers out there: Think of the Apitax image as anabstract classin a programming language, and it is up to you to build the class that inheirits Apitax.DockerHubApitax is available as an image on DockerHub:https://hub.docker.com/r/shawnclake/apitax/Build yourself using DockerfileYou can use the provided Docker file to quickly build an Apitax image. However, this image will not function correctly as it will be missingconfig.txt,project.py, and will have no drivers. Please see the StarterPack for more details on how this should all be included.Steps:Clone the Apitax repositoryBuild the image:docker build --no-cache -t my-amazing-image .Run the newly created image:docker run -d -p 5080:5080 my-amazing-imageThis will run the container in the background, to run the container in the foreground remove the-dflag.JenkinsJenkins is available here:https://openrubicon.com/blue/organizations/jenkins/Apitax/activityThis address may change in the futureBuilds are not available on the Jenkins platform, and they likely never will beBuilds are triggered periodically or via a Git PushBuild artifacts will likely be pushed to a downloads server in the near futureOnly non-dependency versions will be offered. On average, these are about 7.5% of the size of the versions which includes dependenciesMajor and minor releases will also feature a dependency build, however bug and security releases will not feature a dependency versionIt is best practice to always download the no-deps version and ensure you download the dependencies yourselfPip Packaging InstructionsUse either Powershell or BashBuilding the package:python setup.py sdist bdist_wheelUpload the package to Pypi:twine upload dist/* -r pypiMore information can be found here:https://gist.github.com/ShawnClake/759e9d09af868ef18f8c7b39d1684ad4Readme Conversion InstructionsInstallpandocif it is not already installed:sudo apt-get install pandocRun the command:pandoc -o readme.docx -f markdown -t docx README.mdCompile Antlr Grammar from another directoryThis is sometimes necessary due to a bug in the antlr compiler with regards to pathsDownload the antlr compiler .jar file and save it somewhere.Inside of the directory where the .jar was saved, create the following folder hierarchybuildsrcscriptslogsCreate a .bat or .sh file (dependent on OS) and add the following script:#!/bin/bashgrammardir=~/grammarapitaxdir=~/apitax-dev/Apitax/apitax/grammarantlr='antlr-4.7.1-complete.jar'java-jar$grammardir/$antlr-Dlanguage=Python3$grammardir/src/$2java-jar$grammardir/$antlr-lib$grammardir/src-o$grammardir/build-listener-visitor-Dlanguage=Python3$grammardir/src/$1cp-r$grammardir/build/*$apitaxdir/build
cp$grammardir/src/$2$apitaxdir/src
cp$grammardir/src/$1$apitaxdir/srcAnytime you want to make changes to the grammar, do it from the new ~/grammar/src directory and run the scriptYou must pass in your parser and lexer grammars as argumentsbash ~/grammar/run.sh Ah210.g4 AhLex210.g4Documentation and UsageCommandtax - Data Gathering, Manipulating, UsageCommandtax is an efficient and fluent command language for Rest API's. Think of it as a better curl supporting better logging, better debugging, and more sensible flags.Existingscript <pathToSomeScript>Sequences of commands to automate the execution of API requestsScripts can include additional script commands which effectively means scripts can be nestedScripts are run in order from top to bottom, if a nested script is found it executes the nested script before continuing through the current scriptcustom <someCustomCommand>Processes a custom request which is not baked into the utilityParameters--get : Uses a get method--post : Uses a post method--put : Uses a put method--patch : Uses a patch method--delete : Uses a delete method--url : (string) The endpoint--debug : Sets debug mode--sensitive : Sets sensitive mode--data-post : (json string) Any post data--data-query : (json string) Any query parameters  ie. Endpoint.com/something?this=queryparam--data-path : (json string) Any url path variables ie. Endpoint.com/users/{path_var}/showComing Soonshell <someCommand>Runs the command in the shell and returns the response from the shellAn optional abreviated syntax for each command flag. These examples are subject to change--get -> -g--post -> -p--put -> -q--patch -> -w--delete -> -e--url -> -u--debug -> -d--sensitive -> -s--data-post -> -z--data-query -> -x--data-path -> -cScriptax - Control Flow, Scoping, and AutomationScriptax is an API first automation language. Scriptax is not a general purpose language, instead it exists as a language tailored to optimizing automation in an API based environment.Languagect(required: "\<someCommand\>") {% %}Commandtax executionA command is executed during the parsing of the line and its response is returnedSupports async, callbacksget(required: "\<someCommand\>", optional: {dataObj}, optional: param1...n) {% %}Executes a get requestData Object takes optional keys which correspond to Commandtax custom data:post, query, path, headerReturns the resultSupports async, callbacksput(required: "\<someCommand\>", optional: {dataObj}, optional: param1...n) {% %}Executes a put requestData Object takes optional keys which correspond to Commandtax custom data:post, query, path, headerReturns the resultSupports async, callbackspatch(required: "\<someCommand\>", optional: {dataObj}, optional: param1...n) {% %}Executes a patch requestData Object takes optional keys which correspond to Commandtax custom data:post, query, path, headerReturns the resultSupports async, callbackspost(required: "\<someCommand\>", optional: {dataObj}, optional: param1...n) {% %}Executes a post requestData Object takes optional keys which correspond to Commandtax custom data:post, query, path, headerReturns the resultSupports async, callbacksdelete(required: "\<someCommand\>", optional: {dataObj}, optional: param1...n) {% %}Executes a delete requestData Object takes optional keys which correspond to Commandtax custom data:post, query, path, headerReturns the resultSupports async, callbacks{dataObj}JSON object which includes optional arguments for the command such as:auth - The set of credentials to use for this requestpost - json - An object detailing any post parametersquery - json - An object detailing any query parameterspath - json - An object detailing any path parametersdriver - The driver to use for this requeststrict - default: true - Whether or not the entire script should fail if the requests returns a status code >299 or <200new OpenstackNetworks(optional=parameters);Creates a new instance of the OpenstackNetworks scriptThe optional parameters should correspond to the sig line\<attributes\> \<methodName\>(optional=parameters) {}Adds a method to the current scriptAttributes can be eitherapiorscript\<methodName\>(optional=parameters);Executes a method with the optional parametersasyncAdd this keyword in front of any keyword which supports async to run the operation in a new threadCan also be used in front of a method statement:api async getNetworks() { }async get("http://placeholderjson.com/users", {}) {% %}someVar = async get("http://placeholderjson.com/users", {}) {% %}Callbacks execute prior to storing the result of the request into a variableVariable will be initialized with a new thread instance and once the thread completes it will be replaced with the result as returned via the callbackawait \<someOptionalVar\>;When a variable is specified, wait until the async execution specified by that variable completes.When no variable is specified, wait until all of the open threads in the current script complete before moving onCan also be used when using a method call:result = await openstack.getNetworks();{% %};Callback blockThe contents will be executed in an isolated scope, usually only having access to aresultsvariablestr()Cast to stringint()Cast to rounded integerdec()Cast to floatbool()Cast to true/falselist()Cast to listOnly works on stringsdict()Cast to dictionaryWorks on lists, strings, ints, decs\#Return the length of the variableWorks on strings, lists, dictionaries (Only returns the top level count)\$Escapes the label name such that labels which are the same as language keywords can be used\!When adding this to the very start of a statement, the execution of that line will not be logged or printed\@Returns the reflection of a variableextends(\<someScriptPath\>);Adds polymorphism to the script by extending another scriptdel(\<someVar\>)Remove someVar from the current scopereturn \<optionalVar\>Exits the script immediately and returns some expressionoptions(optional=parameters)Used to specify options for the scriptsig(param1Required, thisParam=isOptional, thisOneisRequired);Specify parameters for a scriptif (condition) {}IF statementwhile (condition) {}While loopfor \<someVar\> in \<existingVar\> {}Loop through each item in a list in orderfor \<someVar\> in \<someNumber\> {}Loop from 1 to someNumber and set someVar to the current iterationeach \<someList\> {% %};Loop through a list settingresultsto the current item and executing instructions in an isolated callback\<someVar\> = \<someExpression\>Sets a variableSupports expressions, strings, numbers, booleans, dictionaries, lists, and commandtax responses"this is a string {{ someVar }}"Injects the contents of a variableFancy stuff is possible such as: set newVar = ct("{{someVar}}")\< someExpression \>Injects the expressionIn this case, the arrow brackets must be includedfrom driverName import someScript as someNameThis imports other scriptax to be used inside of the current scriptfromandasare optionalauth(\<someAuthObject\>)Sets the default auth for the current executing scriptlogin(username={{someUser}}, password={{somePass}}, token={{someToken}}, driver={{someDriver}}, extra={{someJSONObj}})Each parameter in login is optional. The required parameters is defined via the driver to be used by the login command. This driver is either the default script driver or the driver specified in the login parameters.Returns an auth objectCan be used directly withauthsuch as:auth login(username=test, password=test123);endpoint(someEndpointName@someDriver)Using the driversgetCatalogmethod, this will return the the endpoint found in the catalog of the specified driverThis can be used directly withurlsuch as:url endpoint('keystone@openstack')url(\<someUrl\>)Sets the current working URL to be used in further commandtaxlog("log some output to the console & log file")Supports expressions// some commentInline comment/* some comment spanning multiple lines */Block commentScopesIn Scriptax, there is no global scope. Every piece of data belongs to a scope. There are many different scope types within Scriptax:Script Scope - These are symbols that are accessible within an entire script. Examples of these are sig parameters or script level variables.Method Scope - These are symbols that are accessible from within a method. Of course, this scope inherits from the script scope.Flow/Block Scope - These are symbols that are accessible within a flow block such as if statements or loops. Of course, this scope inherits from the script scope.Callback Scope - These are symbols that are accessible within a callback. Usually, aresultvariable will be accessible as well as any other values passed in via(optional=parameter) -> {% %}syntax. Typically callback scope is read only and any changes made to any variables in here will only be changed within the callback scope. The only exception to this is theresultvariable which is generally used to pass data back in a return style.Script,Method, andFlow/Blockscopes will all inherit their parent scopes.Callbackscopes on the other hand are completely isolated and must be told explicilty which symbols are allowed in and they will only be allowed in as read-only.ArchitectureIn Scriptax, every file is considered an object. In other words, every file is analogous toclassin other programming and scripting languages.Instances in Scriptax only ensure that variables in the script scope are unique.Methods in Scriptax are inherentlystatic. In other words, methods are not unique per instance and can be called on the script itself or on a script instance. If a method is called via the script itself and attempts to utilize script scope variables, an error will be thrown.StructureScriptax has a structure which must be followed or else errors will be thrown or the script will complete with errors.The first few lines of a script are dedicated toglobal_statements. These statements includeextends,signature,options, and finallyimports. Scriptax is extremely strict when it comes toglobal_statementsand thus they must even be in order. If you choose to useextendsit must be the first statement in the file with no exceptions. If you choose to usesignatureit must come immediatly afterextends, but if you aren't usingextendsthensignaturemust be the first statement within the file, so on and so forth. The order ofglobal_statementsis as follows:extendssignatureoptionsimportsWhileextends,signature, andoptionsshould only ever occur 0 or 1 times within a script file,importscan occur 0..n times within a file.After theglobal_statementsfeel free to use any non global statements as you see fit.Scriptax Gotchas/Tips/TricksAccessing parameters as defined bysigshould be done like so:param.firstParamwherefirstParamis the name of the parameterFor best compatbility with Apitax/Dashboard and Apitax/CLI, please start each Scriptax file with asigline if nessecary.You can use arrays via dot notationset someVar.1 = num1set someVar.2 = num2set someVar.<counter> = num3When doing this, the first usage of a variable must either be someVar = "{}" or an index as part of that object. Failure to do this will result in errors being thrown.Best PracticesEnd each script withawait;to ensure all async requests are completed before it returns to the parent script.Ending withawait;followed byreturn \<someVar\>;is also acceptableDelete unused variables from the root scope when they are no longer neededdel \<someVar\>Only export/return necessary variables from subscriptsKeep the root scope as clean as possibleScripts should be small, containerized pieces of code that strictly follow SRP.Think of Scripts as lego blocks, eventually you can put a bunch of them together to build something really coolScriptax is a flexible, forgiving, and powerful languagePlay around to see what you can and cannot do. There are too many edge cases to list explicitly.Coming SoonA time methodCommandline Interface (CLI)You can activate Apitax from the CLI directly without needing a wrapper packageRun the tests.py file found in the root Apitax directory, and supply the following arguments--cli : (Optional) Quickly select CLI mode--api : (Optional) Quickly select web server mode--debug : (Optional) Output the request response status, headers, and body-u: (Optional) Specify the authentication username - Only applicable in CLI mode-p : (Optional) Ask for password input right away. If -u is specified, but this is not, the application will ask for a new set of credentials for authentication. But, it will use the -u value for any username fields within further requests. This allows someone to authenticate as admin, but run commands applicable to another user.-r: (Optional) The request - Only applicable in CLI mode-s: (Optional) Run a script of requests specified by the file path, authentication for all of these requests are specified by -u & -pModesCLI : Make a request from the command lineAPI : Start the api serverConfigurationAn example configuration file is stored in the root of the repoSupported AuthenticationAuthentication is prebuilt for HTTP Basic and Token based authenticationAuthentication is largely left up to a developer in custom scenariosDriver files facilitate this requirementDrivers and Plugins** This has been redone and requires updated configuration **Drivers and plugins are used to extend the functionality of Apitax to an arbitrary APIDrivers must be dynammically added to the application via theproject.pyfile. Please see Apitax/StarterPack for an example of how this can be done.Examples of Apitax in Action:Commandtax Examplescustom --get --url <someEndpoint>
custom --get --url <someEndpoint> --data-param '{"is_domain": true}'
custom --post --url <someEndpoint> --data-post '{"title": "im the title"}'
custom --put --url <someEndpoint> 
custom --patch --url <someEndpoint>
custom --delete --url <someEndpoint> 
custom --get --url <someEndpoint> --data-param '{"user.id": "1"}'
custom --get --url <someEndpoint>/with/some/{ohyear}/url/params/{981} --data-param '{"is_domain": true}' --data-path '{"ohyeah":"no", "981": "yes"}'script ~/path/to/my/script.ahScriptax Examples// async-tests.ah

url "https://jsonplaceholder.typicode.com";

bob = [];
threads = [];

/*each get("/users", {})
{%
	url "https://jsonplaceholder.typicode.com";
	threads[] = async get("/posts", {
          	"query": {
                "userId": result.id,
            },
        }) {%
        	log("The user has these posts: " + result);    
        %};
%};*/

for user in get("/users", {})
    threads[] = async get("/posts", {
            "query": {
                "userId": user.id,
            },
        }) {%
        log("The user has these posts: " + result);
        //bob[] = result;
    %};

await threads;

del threads;

script("apitax/grammar/scripts/base.ah");

log("yo man im at the end");


/*for user in get("/users", {})
    threads = async get("/posts", {
            "query": {
                "userId": user.id,
            },
        }) {%
        log("The user has these posts: " + result);
        bob[] = result
    %};

//bob[] = "hey";

for t in threads
	await t;

log(bob);

log("Im quite confused");*/// base.ah

url "https://jsonplaceholder.typicode.com";
for user in get("/users")
{
    /*result = get("/posts", {
        "query": {
            "userId": user.id,
        }
    });
    log("The user has these posts: " + result);*/

    log("the user has ID: " + user.id);

    async get("/posts", {
            "query": {
                "userId": user.id,
            }
        }) {%
        log("The user has these posts: " + result);
    %};

}  

response = post("/posts", {
    "post": {
        "title":'foo',
        "body":'bar',
        "userId":1
    }
});

log(script("apitax/grammar/scripts/jen.ah", {}, "i am parameter 1", "i am parameter 2"));

//response = ;

log("Please " + ct("tests my script", {}, "i should be a parameter"));


testlist = ["one", "two", 'three', ["a", 'b', 'c', 'd'], {"test": "failed", "yes": "no"}];

log(#testlist.0);


/*async ct("some command execution")
{
log("some integrated callback");
}

async ct("some command execution");

status = async ct("some command execution");

await status;

status = [firstAsync, secondAsync];
await status;

async bobo in get("/users")
{
    log("Async callback: " + bobo);
}*/

async get("/users");



somelabel = async get("/users")
{%
    log("I am an optional callback");
%};
//anotherRequest0 = async get("/users");
//anotherRequest1 = async get("/users");
//anotherRequest2 = async get("/users");

await somelabel;

log("i am that label" + somelabel);


get("/users") {%

log(result);

%};

async get("/users") {%

log(result);

%};


for iter in 10 {

    async get("/posts", {
            "query": {
                "userId": iter,
            }
        }) {%
        log("The user has these posts: " + result);
    %};

}

log("here is where the magic happens");

get("/posts", {
        "query": {
            "userId": 5,
        }
    }) {%
    log("The user has these posts: " + result);
%};


await;// jen.ah

options dict('{"params": ["name", "game"]}');
answer=2+2;
name "quinn";

set qwer = "im in jens script";


export qwer;

//export ct("script apitax/grammar/scripts/shawn.ah");

set iam.hope.this.works = ct("custom --get --url https://jsonplaceholder.typicode.com/users");

export iam;

set test = "hello";

//return '{"cool": "beans"}';

log(params.passed.0);

log(params.passed.1);

log(params.name);

log(params.game);

log(2+2);

return params.passed.1;

//return "test";


await;// my_script.ah


options dict('{"params": ["first"]}');

log("I got here");

log(params.first);

return "work";


await;Why use ApitaxLet's presume that a backend exists only as an entity that does nothing until it is told to do something.If this is the case let's also assume there will be no interface into the backend other than a RESTful API.Okay, so let's say we want to build a frontend that will list every user's 10 most recent posts.One way we can do this is to make an API request to retrieve a list of our users. Then, in JavaScript, we can loop through that list of users, and for each one we can make an API request to get their most recent posts. In Javascript, we can then compile this list of users together and display it.Sounds simple - except how do we then speed up this process by utilizing asynchronous code. We begin to introduce all sorts of nested callbacks, desync's in our JavaScript that are complex to sort out, difficult bug testing, and we start to break DRY as we copy bits and pieces of this code to different files/pages to list different types of data in different ways.We can solve some of our DRY problems by creating giant API classes that have helper methods for each of these types of calls, but it will still become quite a mess.What we want is to focus on frontend development - not the API request garbage. By doing this, we are enforcing SRP of the frontend architecture as a whole. The bulk of the frontend code should be dedicated to displaying data, getting input, and showing users responses from their input. It should not be thousands of lines of us making API calls and trying to organize the data. There has to be a simpler way, right?What if the frontend only had 4 different API requests to make. 1 for authentication, 1 for getting a catalog of endpoints, 1 for getting the status of the system, and finally 1 to facilitate the transfer of bidirectional data. Here is where Apitax/Commandtax comes into play. We now have a single endpoint to send a request to, and that request's payload is commandtax.Great! Now, instead of a bunch of helper methods all over the place, now we might just have some helper string constants that store frequently used commands. Already much cleaner and more workable. But, this still doesn't solve our DRY problem, SRP, or the callback and desync hell we will face from having to make several requests in a row to accomplish a task.Enter: Scriptax. Now, any of us can create little self-contained sub module files which do these sequential requests for you. These files are called Scripts. One script might have the responsibility of getting a list of users that follows some set of parameters, and another might call that users script and then using the response returned, it could get a list of posts for each of those users and return them.Okay, now our frontend only has 1 short line of commandtax which is executing a script. If the scripts are written well, there is no more callback hell, SRP is preserved, DRY is enforced, and the data returned to us will already be in a very workable format. Kaboomskies, you just saved yourself 2 dozen lines of hard to debug JavaScript, and instead, it's a one liner that returns the data already in a workable format=======
Brings together Commandtax, Scriptax, Standard Library, and the API to create a powerful automation framework. Please use StarterPack to quickly incorporate additional drivers, configuration, and custom code into Apitax.