morc

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2024 License: MIT Imports: 22 Imported by: 0

README

MORC

Command-line based REST client for testing. Built out of frustration with tools such as Postman and Insomnia starting as free and useful programs and eventually moving to for-profit models when all I used them for was a quick testing environment. This tool was created to fulfill the need for testing and provide a way to do so from CLI as a bonus. The name Morc comes from more-curl, or, a CLI MORonically-simple Client due to it not being very fancy.

Installation

Get a distribution from the releases page of the GitHub repo for this project and untar it. Place the morc command somewhere on your path.

Quickstart

Send a one-off request:

morc oneoff www.example.com -X PUT -u www.example.com -d @datafile.json -H 'Content-Type: application/json'

Send a request using a project:

morc init   # create the project, if it doesn't yet exist
morc reqs --new get-google --url http://google.com/ -X GET
morc send get-google  # actually fire it off

Usage

MORC has two primary ways that it can be used: project-oriented, or standalone request sending. With project-oriented use, MORC operates within the context of a project to create named request templates that can be sent as many times as desired by referencing them by name. It tends to require more initial setup, but is suitable for saving testing flows as data. Standalone-oriented use avoids the use of separate project but requires that the entire request be specified every time it is sent; using this mode is more similar to raw curl usage, with some optional support for saving basic state info in between requests.

Project-Oriented Use

MORC is generally designed to operate on a MORC project. A project has requests and flows of requests defined within it that can be sent multiple times without having to fully specify them each time they are sent. This is similar to what you'd see in the main view of a GUI-based REST client, such as Postman or Insomnia.

Beyond creating and sending requests, a project tracks sent request history and sets of variables that can be set from the response of a request and used in later requests. This, combined with defining sequences of requests in flows, allows entire testing sequences to be defined and then run on-demand, which can be useful for automated testing scenarios.

Initializing A MORC Project

A MORC project is created with morc init. This puts all project files by default in a new .morc directory in the directory that morc is called from, and sets up cookie storage and history tracking.

morc init

If you want, you can give a name to the new project; otherwise, MORC will fall back to using a default one.

morc init 'Testing Suite'

Now you can see the details of the project by running morc proj from the same directory:

morc proj

Output:

Project: Unnamed Project
0 requests, 0 flows
0 history items
0 variables across 1 environment
0 cookies in active session

Cookie record lifetime: 24h0m0s
Project file on record: .morc/project.json
Session file on record: ::PROJ_DIR::/session.json
History file on record: ::PROJ_DIR::/history.json
Cookie recording is ON
History tracking is ON

Using default var environment

If you want to change things about the project, you can do that by passing flags to set project attributes:

morc proj --name 'My Cool Project'

Or if you are looking for very fine-grained control over new project creation, you can instead use morc proj with the --new flag. See morc help proj for information on using it.

Project Requests

So, you've got a project rolling! Congrats. Now you can take a look at all the requests that are loaded into it:

morc reqs

If this is in a brand new project, there won't be anything there.

Request Creation

You can add a new request with the --new flag:

morc reqs --new create-user --url localhost:8080/users -X POST -d '{"name":"Vriska Serket"}' -H 'Content-Type: application/json'

The URL, method, body payload, and headers can be specified with flags. Alternatively, if you want to load the body from a file, put '@' followed by the file name as the argument for -d and it will load the body data from that file and use that as the body of the newly-created request:

morc reqs --new update-user --url localhost:8080/users -X PATCH -d '@vriska.json' -H 'Content-Type: application/json'

After adding several requests, morc reqs will have much more interesting output:

morc reqs

Output:

POST   create-user
GET    get-token
GET    get-user
GET    list-users
DELETE remove-user
PATCH  update-user

Each request name is listed along with the HTTP method that the request is configured to use.

Request Sending

Once a request is set up in a project and has at least a method and a URL defined on it, it can be sent to the remote server and the response can be viewed.

Use the send subcommand with the name of the request to be send.

morc send list-users

Output:

HTTP/1.1 200 OK
[
    {"name": "Vriska"},
    {"name": "Nepeta"},
    {"name": "Kanaya"},
    {"name": "Terezi"}
]

The remote server's response as well as any body in the payload will be shown. There's a lot of options to view additional details, such as seeing the headers in the response, outputting the request, and format selection all available as CLI options; take a look at morc help send to see them all.

If there are any variables in the request body, URL, or headers, they are filled with their current values before the request is sent. See the section on Using Variables below for more information on using variables within requests.

Request Viewing

You can examine a request in detail by passing it as an argument to reqs:

morc reqs create-user

Output:

POST http://example.com

HEADERS:
Content-Type: application/json

BODY:
{"name": "Vriska Serket"}

VAR CAPTURES: (NONE)

AUTH FLOW: (NONE)

The request method and URL are shown first, along with any headers, body, and variable captures. Auth flow is for an upcoming feature and is not currently used.

To see only one of the items in a request, you can specify the item to get with the --get CLI flag:

morc reqs create-user --get data

Ouput:

{"name": "Vriska Serket"}

The --get flag can be used to retrieve any of the following attributes: name, data, method, url, headers, captures, and auth.

The headers attribute will print all headers set on the request. If you want to get only the value (or values) of a specific header, use --get-header with the name of the header to retrieve.

Request Editing

If you need to update a request, pass the attribute to be updated and its new value using flags:

morc reqs create-user -d '{"name": "Nepeta Leijon"}'

You can show the request again to confirm that the update was applied:

morc reqs create-user --get data

Output:

{"name": "Nepeta Leijon"}
Request Deletion

If you're completely done with a request and want to permanently remove it from the project, use the --delete/-D flag with the name of the request:

morc reqs --delete get-token

It will be cleared from the project, which you can confirm by listing the requests:

morc reqs
POST   create-user
GET    get-user
GET    list-users
DELETE remove-user
PATCH  update-user
Using Variables

MORC supports the use of variables in requests. These are values in requests that are filled in only when actually sending the request, and they can be changed in between sends. Perhaps, for instance, you'd like to be able to swap whether a request is sent via a plaintext HTTP request or using TLS (HTTPS). You could do that by declaring a variable called ${SCHEME} in the URL of the request:

morc reqs get-user --url '${SCHEME}://localhost:8080/users'

# MAKE SURE to put text with a dollar-leading ${variable} in it in single quotes
# or your shell may mess with the variable

Then, you just need to make sure that the value for the variable is available when sending it.

The simplest way is to provide it with -V when sending the request:

morc send get-user -V SCHEME=https --request  # --request will print the request as it is sent

# note that when providing a var like this, it does NOT start with a $, so you
# do not need to single-quote it.

Output:

------------------- REQUEST -------------------
(https://localhost:8080/users)
GET /users HTTP/1.1
Host: localhost:8080
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip


(no request body)
----------------- END REQUEST -----------------
HTTP/1.1 200 OK
(no response body)

The scheme isn't included in the HTTP request itself, but it is shown in the line just above the request proper. It's set to HTTPS, because ${SCHEME} was substituted with the user-supplied variable.

All variables have the form ${SOME_NAME} when used inside a request, with SOME_NAME replaced by the actual name of the variable (or var for short). They are supported in the URL, body data, and headers of a request. Variables are case-insensitive and their names can be made up of the letters, numbers, underscores, and hyphens.

When substituting a var in a request in preperation for sending, MORC will check in a few different places for values for that var. First, it will use any value directly given by the CLI invocation with -V; this will override any values stored within the project. Next, it will check to see if there are any stored variables in the project that match, in the current variable environment. If there are none, and the current variable environment is not the default, it will check in the default variable environment. If it still can't find any values, MORC will refuse to send the request.

Stored Variables

Variables do not always need to be provided at the time that you send request. MORC maintains a variable store inside of project files that can hold the values of variables indefinitately; they'll exist until the project is deleted, they are deleted, or they are updated automatically via a variable capture.

The variable store is accessed and manipulated with the vars subcommand. By itself, it will list all of the values that it would use for any sent request:

morc vars

With nothing defined, it will give output indicating that:

(none)

With variables set, it will list them out:

${SCHEME} = "https"
${TEST_CAP} = "octyp"
${THETHING} = "doctyp"

Strictly speaking, there are actually two layers of variables possible within the store; a currently-selected one, and a default one. These are organized as separate environments, and they are covered in detail in the Variable Environments section below. Unless it mentioned otherwise, this README will assume you're working with the project set to use only the default environment, as that is the situation when a project is first created.

Setting & Getting Variables

To set the value of a variable, give the name of the variable and the value as arguments to morc vars:

morc vars USER_ID 24f6dc51-17ba-4aca-937c-52b40b9b715c

It will then be shown when listing all variables:

morc vars

Output:

${USER_ID} = "24f6dc51-17ba-4aca-937c-52b40b9b715c"

You can get only that variable's value by giving the name of the variable with no value:

morc vars USER_ID

Output:

24f6dc51-17ba-4aca-937c-52b40b9b715c

Note that there is a difference between a variable being undefined and a variable being defined and set to the empty string. Requests can use any variable defined at send time, including any that are set to the empty string, but they cannot be sent if they use variables that are undefined.

morc vars USER_ID ""

# ${USER_ID} can still be used in templates, even though its value is being set to ""

If you want to actually remove (undefine) a variable from the project var store, use the -D option with the name of a variable:

morc vars -D USER_ID

Then the variable will be completely undefined:

morc vars USER_ID

Output:

"USER_ID" is not defined

The deleted (undefined) variable will be unusable in requests until it is re-defined, but it also will no longer take up space in the project file.

Variable Capturing

MORC has the ability to automatically set the values of variables in the store from data that it receives in response to a sent request. This makes MORC capable of automating entire sequences of request activity; it could, for instance, POST a new user with one request, then save the generated UUID of the user in a var, then use that same UUID in subsequent requests that operate on the user.

This is known as Variable Capturing. Each request in a project can have one or more variables that it saves data to, known as "Variable Captures" or just "caps" for short.

For the examples in this section, we will assume there is a Users API in a local server that allows basic CRUD operations on its resources.

First, we will make a creation request:

morc reqs --new create-user           \
  --url http://localhost:8080/users   \
  -X POST                             \
  -d '{"name": "Vriska Serket"}'      \
  -H 'Content-Type: application/json'

And a deletion request that uses a variable in the URL to indicate which user to delete:

morc reqs --new delete-user                        \
  --url 'http://localhost:8080/users/${USER_ID}'   \
  -X DELETE                                        \
  -H 'Content-Type: application/json'

# note that the URL is in single-quotes because it contains a variable

Now, with those requests, we could manually run the first:

morc send create-user

Output:

HTTP/1.1 201 Created
{
    "id": "92ed835e-252e-40e4-8aa3-423b496bf33d",
    "name": "Vriska Serket"
}

Then we could note the user ID of the new user, and supply it to the delete request manually:

morc send delete-user -V USER_ID=92ed835e-252e-40e4-8aa3-423b496bf33d

Output:

HTTP/1.1 204 No Content
(no response body)

But we could also do the same thing automatically by adding a var capture to the first request.

Captures on a request are accessed by using the caps subcommand with the name of the request whose captures are to be examined. When given no other arguments, it will list out all defined captures on the request, which will be none so far:

morc caps create-user

Output:

(none)

To add a new capture, use the --new flag with the name of the variable to save the data to and give the flag -s with a capture spec. The capture spec gives where in the response to retrieve the value from, and supports byte offsets in format :START,END where START and END are byte offsets, or in format of a JSON path specified by giving keys and array slices needed to navigate from the top level of a JSON body in the response to the desired value, such as .top-level-key.next-level-key.some_array[3].item.

In this example, we will add a new cap that gets its value from the 'id' field of the JSON object in the response:

morc caps create-user --new USER_ID -s .id   # or just id with no period; the leading period is not required

Then, it will be created:

morc caps create-user

Output:

USER_ID from .id

Now when the first request is sent, the value in .id is saved to USER_ID in the variable store:

morc send create-user

Output:

HTTP/1.1 201 Created
{
    "id": "c2328061-da05-4241-9a42-012f2e39ff72",
    "name": "Vriska Serket"
}

You can check the var store to be sure:

morc vars

Output:

USER_ID = "c2328061-da05-4241-9a42-012f2e39ff72"

And now you can send the delete request without having to manually specify the user:

morc send delete-user

Output:

HTTP/1.1 204 No Content
(no response body)

If you ever need to update a capture, you can do so by passing the name of the capture to delete to the -D flag:

morc caps create-user -D USER_ID

And if you want to update one without deleting it, you can specify the property to update and the new value with flags:

morc caps create-user USER_ID --spec :3,8       # capture data from the 3rd to 8th byte instead of the JSON path
morc caps create-user USER_ID --var USER_UUID   # save it to USER_UUID instead
Variable Environments

The variable store in MORC supports having multiple sets of vars that you can easily switch between. Each of these sets is called an environment; they might be set up to, say, change the requests to be applicable for testing different deploy scenarios.

For instance, one might have two sets of variables such as the following:

# set 1:

${BASE} = "http://staging.internal.example.com/api/v2"
${USER} = "myTestUser"

# set 2:

${BASE} = "https://example.com/api/v2"
${USER} = "internalTesting"

These two sets of vars could be assigned to two different environments in the MORC var store to allow easy switching between testing against a staging server and testing against a production server.

Whenever a variable in a MORC project is created, set, deleted, or used in a request, it will be done in whatever environment MORC is set to use. When a project is first started, this will be what is known as the default environment.

You can check what environment MORC is in with the env subcommand:

morc env

Output:

<DEFAULT>

It gives the special string <DEFAULT> to indicate that MORC is currently set to use the default environment.

To swap to a new environment, give the name of the environment to swap to. You can swap to one that doesn't yet exist; using it will automatically create it when necessary:

morc env STAGING

Names are case-insensitive, like variable names.

Once swapped to the new environment, you can start defining variables!

morc vars BASE http://staging.internal.example.com/api/v2
morc vars USER myTestUser

You can then swap to another environment to give them different values:

morc env PROD

morc vars BASE https://example.com/api/v2
morc vars USER internalTesting

Now the values can be easily swapped to be appropriate for each environment:

morc env STAGING

morc vars

Output:

${BASE} = "http://staging.internal.example.com/api/v2"
${USER} = "myTestUser"
morc env PROD

morc vars

Output:

${BASE} = "https://example.com/api/v2"
${USER} = "internalTest"

You can swap back to the default environment by giving --default as an option to env:

morc env --default

morc vars

Output:

${BASE} = ""
${USER} = ""

The default environment will have a copy of every var that is set on any other environment, but it will be set to the empty string unless explicitly set. More info on the interactions between variables in the default environment and those in others is available in the Defaulting section below.

There is shorthand for setting or viewing vars in a particular environment so you do not need to swap every time just to perform a few operatoins. For instance, when listing vars, you can pass in --env to list variable values defined only in that environment, or --default to see it for the default environment:

morc env STAGING
morc vars --env PROD

Output:

${BASE} = "http://staging.internal.example.com/api/v2"
${USER} = "myTestUser"

The same flag is available when setting or retrieving a particular variable's value:

morc vars BASE http://staging.internal.example.com/api/v3 --env PROD
Defaulting

When MORC is in a non-default environment, the default environment still exists and is used for getting "default" values of a variable. The default environment will contain at least one copy of every single var that is defined in at least one other environment, or put more simply, every time you set a variable in a non-default environment, if it doesn't already exist in the default, it is added there as well with a value of the empty string.

When in a non-default environment, it's possible to send a request that has variables in it that are not defined in the current environment. In that case, the default environment is consulted for its value on it and that is used if it exists. This can make it easier when creating new environments in cases where there are already several variables defined; instead of requiring you to redefine every single one in the new environment, you only need to redefine the ones you wish to update.

It's an invariant that if a variable is defined in at least one environment, it will be defined in the default environment. As a result, deleting a variable from the default environment is only possible if you also want to delete it from every other environment that defines it, and this is required to be explicitly stated via command line flags, or MORC will return an error.

# swap to env staging:
morc env staging

# create a new variable called PASSWORD in staging env; this will *also* create
# a var PASSWORD="" in the default environment
morc vars PASSWORD grimAuxiliatrix

# swap back to default environment
morc env --default

# attempt to delete PASSWORD from the default env:
morc vars -D PASSWORD

Output:

Error: current env is default and "PASSWORD" is defined in other envs: STAGING
Use --all to delete from all environments

If you're absolutely sure that you want to clear it from all environments, give the --all flag as well:

morc vars -D PASSWORD --all
Creating Sequences Of Requests With Flows

Flows are sequences of requests that will be fired one after another. It can be useful to use with variable captures to perform a full sequence of communication with a server.

Use morc flows and pass the name of the new flow to the --new flag to create a new one. Once created, morc exec FLOW will actually send off each request. Any variable captures from request sends are used to set the values of subsequent requests.

Request History

MORC projects maintain a history of requests and responses that were sent. If the project was created with morc init, history will be enabled by default.

To view a list of all requests that the project has in its history, use hist with no other options:

morc hist

Output:

0: 2024-05-12T08:13:09-05:00 - create-user - POST http://localhost:8080/users - 201 Created - 0s
1: 2024-05-13T08:50:57-05:00 - get-user - GET http://localhost:8080/users/c2328061-da05-4241-9a42-012f2e39ff72 - 200 OK - 0s
2: 2024-05-15T07:49:12-05:00 - list-users - GET http://localhost:8080/users - 200 OK - 1s
3: 2024-05-15T11:37:02-05:00 - update-user - PATCH http://localhost:8080/users/c2328061-da05-4241-9a42-012f2e39ff72 - 200 OK - 1s
4: 2024-05-15T11:39:32-05:00 - delete-user - DELETE http://localhost:8080/users/c2328061-da05-4241-9a42-012f2e39ff7 - 404 Not Found - 0s
4: 2024-05-15T11:39:38-05:00 - delete-user - DELETE http://localhost:8080/users/c2328061-da05-4241-9a42-012f2e39ff72 - 204 No Content - 1s
8: 2024-05-16T11:00:58-05:00 - create-user - POST http://localhost:8080/users - 200 OK - 0s

Each history entry begins with an entry index. A particular entry can be played back by giving an entry index number, along with any other output formatting options as would be accepted by morc send or morc oneoff:

morc hist 0

Output:

Request template: create-user
Request sent:          2024-05-12T08:13:09-05:00
Response received:     2024-05-12T08:13:09-05:00
Total round-trip time: 0s
HTTP/1.1 201 Created
{
    "id": "c2328061-da05-4241-9a42-012f2e39ff72",
    "name": "Vriska Serket"
}

To turn off history recording, use the --off flag:

morc hist --off

To turn history recording on, use the --on flag:

morc hist --on

To check the current status of history and see a summary of the store, use the --info flag:

morc hist --info

Output:

9 entries in .morc/history.json

History is ON

To clear out the current history store, either delete the file listed in --info output from the filesystem, or use the --clear flag:

morc hist --clear

MORC projects save cookies received in responses from remote servers. Due to internal limitations, it specifically tracks all cookies requested to be set by a Set-Cookie header, not necessarily whether it would actually be sent.

MORC will save cookies for only as long as the currently-configured cookie lifetime in the project is, regardless of when their expiry is set.

In a project created by morc init, cookie recording will be on and the cookie lifetime will be defaulted to 24 hours.

To list all cookies that are currently in the session store, use the cookies subcommand with no other arguments:

morc cookies

Output:

http://www.google.com/:
2024-05-15T11:37:03-05:00 1P_JAR=2024-05-15-16; Path=/; Domain=google.com; Expires=Fri, 14 Jun 2024 16:37:03 GMT; Secure
2024-05-15T11:37:03-05:00 AEC=AQTF6HwJeI71s5iqRHg95Jus2P9Vlc-e-eBtv9yCD0etgcjVzKxJPPo5upY; Path=/; Domain=google.com; Expires=Mon, 11 Nov 2024 16:37:03 GMT; HttpOnly; Secure; SameSite=Lax

https://www.google.com/:
2024-05-15T11:40:04-05:00 1P_JAR=2024-05-15-16; Path=/; Domain=google.com; Expires=Fri, 14 Jun 2024 16:40:04 GMT; Secure

If you want to see the exact cookies that would be sent in Cookie: headers on a request to a particular URL, you can give it with --url:

morc cookies --url https://google.com/

Output:

1P_JAR=2024-05-15-16
AEC=AQTF6Hzwnh-yB8n0D7lG6i_2d9XqUyun2su9vCd_rTOQDFr1r3BQnpXN7Q

To turn session (cookie) recording off, use the --off flag:

morc cookies --off

To turn session (cookie) recording on, use the --on flag:

morc hist --on

To check the current status of cookies and see a summary of the session store, use the --info flag:

morc cookies --info

Output:

3 cookies across 2 domains in .morc/session.json

Cookie recording is ON

To clear out the current session store, either delete the file listed in --info output from the filesystem, or use the --clear flag:

morc cookies --clear

Standalone Use

MORC can send one-off requests by using morc oneoff:

morc oneoff -X GET http://localhost/cool

Data and headers are specified with curl-like syntax:

morc oneoff -X POST https://localhost/cool -H 'Content-Type: application/json' -d '@./datafile'

For convenience, top-level subcommands for each of the common eight HTTP methods are defined. Calling one is exactly the same as calling morc oneoff -X METHOD and support all args except for '-X'.

morc get http://localhost:8080/cool  # same as morc oneoff -X GET http://localhost:8080/cool

Documentation

Overview

Package morc provides a scriptable REST client.

Index

Constants

View Source
const (
	ProjDirVar = "::PROJ_DIR::"

	DefaultProjectPath = ".morc/project.json"
	DefaultSessionPath = ProjDirVar + "/session.json"
	DefaultHistoryPath = ProjDirVar + "/history.json"

	FiletypeProject = "MORC/PROJECT"
	FiletypeSession = "MORC/SESSION"
	FiletypeHistory = "MORC/HISTORY"

	CurFileVersion = 1
)
View Source
const Version = "0.3.1"

Variables

This section is empty.

Functions

func NewFlowExistsError added in v0.3.0

func NewFlowExistsError(name string) error

func NewFlowNotFoundError added in v0.3.0

func NewFlowNotFoundError(name string) error

func NewReqExistsError added in v0.3.0

func NewReqExistsError(name string) error

func NewReqNotFoundError added in v0.3.0

func NewReqNotFoundError(name string) error

func OutputRequest

func OutputRequest(req *http.Request, opts OutputControl) error

func OutputResponse

func OutputResponse(resp *http.Response, caps map[string]string, opts OutputControl) error

func ParseVarName added in v0.2.0

func ParseVarName(name string) (string, error)

Types

type Flow

type Flow struct {
	Name  string     `json:"name"`
	Steps []FlowStep `json:"steps"`
}

func (*Flow) InsertStep added in v0.3.0

func (flow *Flow) InsertStep(idx int, step FlowStep) error

func (*Flow) MoveStep added in v0.3.0

func (flow *Flow) MoveStep(from, to int) error

func (*Flow) RemoveStep added in v0.3.0

func (flow *Flow) RemoveStep(idx int) error

type FlowStep

type FlowStep struct {
	Template string `json:"template"`
}

type Format

type Format int
const (
	FormatPretty Format = iota
	FormatLine
)

type HistoryEntry

type HistoryEntry struct {
	Template string
	ReqTime  time.Time
	RespTime time.Time
	Request  *http.Request
	Response *http.Response
	Captures map[string]string
}

func LoadHistoryFromDisk

func LoadHistoryFromDisk(histFilename string) ([]HistoryEntry, error)

func (HistoryEntry) MarshalJSON

func (h HistoryEntry) MarshalJSON() ([]byte, error)

func (*HistoryEntry) UnmarshalJSON

func (h *HistoryEntry) UnmarshalJSON(data []byte) error

type OutputControl

type OutputControl struct {

	// Request controls whether the request should be output to stdout
	// immediately prior to sending it. All variable substitution will be
	// applied prior to outputting the request; the output will show the final
	// request exactly as it will be sent.
	Request bool

	// Captures controls whether captured variables and their values should be
	// output to stdout after the response is received.
	Captures bool

	// Headers controls whether the response headers should be output to stdout
	// after the response is received.
	Headers bool

	// SuppressResponseBody controls whether the response body should be output
	// to stdout after the response is received.
	SuppressResponseBody bool

	// Format sets the format of the output. The default is "pretty", which is
	// human-readable. "line" is a more compact format that is slightly more
	// machine-readable. "sr" is a format that is shorthand for "line" but
	// including only the status and response payload.
	Format Format

	// Writer is the writer to which output should be written. If not set,
	// output will be written to os.Stdout.
	Writer io.Writer
}

type Project

type Project struct {
	Name      string
	Templates map[string]RequestTemplate // note: Names must be manually synched across Templates, Flows, and History
	Flows     map[string]Flow
	Vars      VarStore
	History   []HistoryEntry
	Session   Session
	Config    Settings
}

func LoadProjectFromDisk

func LoadProjectFromDisk(projFilename string, all bool) (Project, error)

func (Project) CookiesForURL added in v0.2.0

func (p Project) CookiesForURL(u *url.URL) []*http.Cookie

func (Project) Dump added in v0.2.0

func (p Project) Dump(w io.Writer) error

Dump writes the contents of the project in "project-file" format to the given io.Writer.

func (Project) DumpHistory added in v0.2.0

func (p Project) DumpHistory(w io.Writer) error

DumpHistory writes the contents of the history in "history-file" format to the given io.Writer.

func (*Project) EvictOldCookies added in v0.2.0

func (p *Project) EvictOldCookies()

EvictOldCookies immediately applies the eviction of old cookie sets using the project's current cookie lifetime. If the project has not loaded its session, or if the session has no cookies, this method will do nothing.

func (Project) FlowsWithTemplate

func (p Project) FlowsWithTemplate(template string) []string

func (Project) IsExecableFlow added in v0.2.0

func (p Project) IsExecableFlow(name string) bool

func (Project) PersistHistoryToDisk

func (p Project) PersistHistoryToDisk() error

func (Project) PersistSessionToDisk

func (p Project) PersistSessionToDisk() error

func (Project) PersistToDisk

func (p Project) PersistToDisk(all bool) error

PersistToDisk writes up to 3 files; one for the suite, one for the session, and one for the history. If p.ProjFile is empty, it will be written to the current working directory at path .morc/suite.json. If p.SeshFile is empty, it will be written to the current working directory at path .morc/session.json. If p.HistFile is empty, it will be written to the current working directory at path .morc/history.json.

type RESTClient

type RESTClient struct {
	Vars         map[string]string
	VarOverrides map[string]string // Cleared after every call to SendRequest.
	VarPrefix    string

	Scrapers []VarScraper
	// contains filtered or unexported fields
}

Do not use default RESTClient, call NewRESTClient instead.

func NewRESTClient

func NewRESTClient(cookieLifetime time.Duration, httpClient *http.Client) *RESTClient

NewRESTClient creates a new RESTClient. 0 for cookie lifetime will default it to 24 hours. If an http.Client is provided, it will be used for making calls, but its cookie jar will be replaced with MORC's timeoutable variant. Callers should use other methods to load cookies in. If httpClient is set to nil, it will default to a new http.Client with a default transport and timeout of 30 seconds.

func (*RESTClient) CreateRequest

func (r *RESTClient) CreateRequest(method string, url string, data []byte, hdrs http.Header) (*http.Request, error)

CreateRequest creates a request to the given endpoint. Values set in Vars and VarOverrides are used to fill any variables in the URL, data, and headers.

func (*RESTClient) ReadState

func (r *RESTClient) ReadState(rd io.Reader) error

func (*RESTClient) SendRequest

func (r *RESTClient) SendRequest(req *http.Request) (*http.Response, map[string]string, error)

SendRequest sends the given request and returns the response. VarOverrides will be cleared after this is called. Prior to returning, the response is scanned for var captures and those that are captured are stored in Vars and re

func (*RESTClient) SetCookieJar

func (r *RESTClient) SetCookieJar(jar *TimedCookieJar)

func (*RESTClient) Substitute

func (r *RESTClient) Substitute(s string) (string, error)

func (*RESTClient) WriteState

func (r *RESTClient) WriteState(w io.Writer) error

type RequestTemplate

type RequestTemplate struct {
	Name     string
	Captures map[string]VarScraper
	Body     []byte
	URL      string
	Method   string
	Headers  http.Header
	AuthFlow string
}

func (RequestTemplate) Sendable

func (r RequestTemplate) Sendable() bool

type SendOptions

type SendOptions struct {
	// Vars are request variables and their values that are set only for this
	// request. A variable in Vars with the same name as one that is in any
	// loaded state (should that be requested) will override the loaded state
	// value, but will not be saved in resulting state.
	Vars map[string]string

	// Captures is a list of variable scrapers that will be used to extract
	// values from the response body. The captured values *will* be kept in any
	// saved state (should state save be requested).
	Captures []VarScraper

	// LoadStateFile is the path to a state file that should be loaded before
	// sending the request. If this is set, the state file will be loaded and
	// used to populate the initial RESTClient state before applying any further
	// options given in this struct.
	LoadStateFile string

	// SaveStateFile is the path to a state file that should be saved after
	// sending the request, for non-project based state saving. If set, state
	// will be saved in this file immediately after the response is received.
	SaveStateFile string

	// Body is bytes of data that make up the body of the request to be sent. If
	// not set, the request will be sent with no body. Variable substitution
	// will be performed on the data prior to sending.
	Body []byte

	// Headers is a map of headers to be sent with the request. If not set, the
	// request will be sent with default headers only. Variable substitution
	// will be performed on the header names and values prior to sending.
	Headers http.Header

	// Cookies loads the given cookies from a set of SetCookiesCalls into
	// the client before sending the request.
	Cookies []SetCookiesCall

	// CookieLifetime is the lifetime of cookie records in the client. It is
	// used to evict old cookie records regardless of actual lifetime in the
	// Set-Cookie header that originally caused it to be set. If not set, it
	// will default to 24 hours.
	CookieLifetime time.Duration

	// Output contains output control options that determine what output is
	// generated after the request is sent.
	Output OutputControl

	// Client is a user-provided HTTP client that will be used to make external
	// HTTP calls. If left nil, a default one is used. If provided, note that
	// its cookie jar will be replaced with an internal variant of a cookie jar
	// that allows for cookie record keeping. Also note that TLS configuration
	// provided in this struct will override any in the given client.
	//
	// TODO: this is almost entirely used for testing and should probably not
	// be exposed to callers. Perhaps via setting a global? Or even giving as
	// a ctor.
	Client *http.Client

	// InsecureSkipVerify is a flag that, if set, will cause the client to skip
	// verification of TLS certificates when making HTTPS requests. This is
	// useful for testing against self-signed certificates, but note that this
	// should generally NOT be used in production code. THIS IS INSECURE AND
	// SHOULD BE USED WITH CAUTION.
	InsecureSkipVerify bool
}

SendOptions is used to encapsulate non-critical options for sending a request via the Send function.

type SendResult

type SendResult struct {

	// SendTime is the time that the request was sent to the remote host.
	SendTime time.Time

	// RecvTime is the time that the response was received from the remote host.
	RecvTime time.Time

	// Request is the request exactly as it was sent.
	Request *http.Request

	// Response is the received response.
	Response *http.Response

	// Captures is map of variables to their values that were captured from the
	// response body
	Captures map[string]string

	// Cookies is all cookies available in the client after the request was
	// sent.
	Cookies []SetCookiesCall
}

func Send

func Send(method, URL, varSymbol string, opts SendOptions) (SendResult, error)

Send performs standardized sending of a request, along with standardized output control options. A RESTClient is built and populated and used to send the request. All requests sent from a CLI command should be sent using this function.

type Session

type Session struct {
	Cookies []SetCookiesCall
}

func LoadSessionFromDisk

func LoadSessionFromDisk(seshFilename string) (Session, error)

func (Session) Dump added in v0.2.0

func (s Session) Dump(w io.Writer) error

Dump writes the contents of the session in "session-file" format to the given io.Writer.

func (Session) MarshalJSON

func (s Session) MarshalJSON() ([]byte, error)

func (Session) TotalCookieSets

func (s Session) TotalCookieSets() int

TotalCookieSets returns the total number of individual cookies that this Session has a record of being set across all URLs. This may include the same cookie being set multiple times.

func (*Session) UnmarshalJSON

func (s *Session) UnmarshalJSON(data []byte) error

type SetCookiesCall

type SetCookiesCall struct {
	Time    time.Time      `json:"time"`
	URL     *url.URL       `json:"url"`
	Cookies []*http.Cookie `json:"cookies"`
}

func (SetCookiesCall) MarshalBinary

func (sc SetCookiesCall) MarshalBinary() ([]byte, error)

func (SetCookiesCall) String

func (sc SetCookiesCall) String() string

func (*SetCookiesCall) UnmarshalBinary

func (sc *SetCookiesCall) UnmarshalBinary(data []byte) error

type Settings

type Settings struct {
	ProjFile       string        `json:"project_file"`
	HistFile       string        `json:"history_file"`
	SeshFile       string        `json:"session_file"`
	CookieLifetime time.Duration `json:"cookie_lifetime"`
	RecordHistory  bool          `json:"record_history"`
	RecordSession  bool          `json:"record_cookies"`
}

func (Settings) HistoryFSPath

func (s Settings) HistoryFSPath() string

HistoryFSPath returns the file-system compatible path to the history file. If s.HistFile contains ProjDirVar, it will be replaced with the directory that the project file is in. If s.HistFile is empty, or if s.ProjFile is referred to with ProjDirVar and is itself empty, this will return the empty string.

func (Settings) SessionFSPath

func (s Settings) SessionFSPath() string

SessionFSPath returns the file-system compatible path to the session file. If s.SeshFile contains ProjDirVar, it will be replaced with the directory that the project file is in. If s.SeshFile is empty, or if s.ProjFile is referred to with ProjDirVar and is itself empty, this will return the empty string.

type State

type State struct {
	Cookies []SetCookiesCall
	Vars    map[string]string
}

State holds all information in a saved state file.

type TimedCookieJar

type TimedCookieJar struct {
	// contains filtered or unexported fields
}

TimedCookieJar wraps a net/http.CookieJar implementation and does quick and dirty recording of all cookies that are received. Because it cannot examine the policy of the wrapped jar, it simply records calls to SetCookies and stores enough information to reproduce all said calls, and additionally records the time that each call was made.

This record can be persisted to bytes, and later played back to restore the state of the cookie jar. Note that depending on the policy of the wrapped jar, cookies that are valid at persistence time may be invalid at playback time.

The time of each call is used for record eviction. At load time and periodically during calls to other methods, the jar will remove any records that are older than a certain threshold. This threshold is stored in the Lifetime member of the TimedCookieJar.

The zero value of TimedCookieJar is not valid. Use NewTimedCookieJar to create one.

It uses trickiness inside of unmarshal that relies on assumption that it is being called on a valid one whose wrapped cookiejar hasn't yet been called.

TODO: this is a highly-coupled structure with RESTClient. Doesn't really make sense to export it.

func NewTimedCookieJar

func NewTimedCookieJar(wrapped http.CookieJar, lifetime time.Duration) *TimedCookieJar

NewTimedCookieJar creates a new TimedCookieJar with the given lifetime. If lifetime is 0, the default lifetime of 24 hours is used. If wrapped is nil, a new net/http/cookiejar.Jar is created with default options and used as wrapped.

func (*TimedCookieJar) Cookies

func (j *TimedCookieJar) Cookies(u *url.URL) []*http.Cookie

func (*TimedCookieJar) ForwardSetCookieCalls

func (j *TimedCookieJar) ForwardSetCookieCalls(fn func(u *url.URL, cookies []*http.Cookie)) int

func (*TimedCookieJar) SetCookies

func (j *TimedCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie)

func (*TimedCookieJar) SetCookiesFromCalls

func (j *TimedCookieJar) SetCookiesFromCalls(calls []SetCookiesCall)

func (*TimedCookieJar) StopForwardingSetCookieCalls

func (j *TimedCookieJar) StopForwardingSetCookieCalls(idx int)

type TraversalStep

type TraversalStep struct {
	Key   string // if set, index is ignored
	Index int
}

func (TraversalStep) String

func (t TraversalStep) String() string

func (TraversalStep) Traverse

func (t TraversalStep) Traverse(data interface{}) (interface{}, error)

type VarScraper

type VarScraper struct {
	Name        string
	OffsetStart int
	OffsetEnd   int
	Steps       []TraversalStep // if non-nil, OffsetStart and OffsetEnd are ignored
}

func ParseVarScraper

func ParseVarScraper(s string) (VarScraper, error)

func ParseVarScraperSpec

func ParseVarScraperSpec(name, spec string) (VarScraper, error)

func (VarScraper) EqualSpec added in v0.3.0

func (v VarScraper) EqualSpec(other VarScraper) bool

func (VarScraper) IsJSONSpec added in v0.3.0

func (v VarScraper) IsJSONSpec() bool

func (VarScraper) IsOffsetSpec added in v0.3.0

func (v VarScraper) IsOffsetSpec() bool

func (VarScraper) Scrape

func (v VarScraper) Scrape(data []byte) (string, error)

func (VarScraper) Spec added in v0.3.0

func (v VarScraper) Spec() string

func (VarScraper) String

func (v VarScraper) String() string

type VarStore

type VarStore struct {
	Environment string
	// contains filtered or unexported fields
}

VarStore is a collection of variables that can be accessed by name within multiple environments. The zero value of this type is not valid; create a new VarStore with NewVarStore().

func NewVarStore

func NewVarStore() VarStore

func (*VarStore) All

func (v *VarStore) All() []string

All returns the names of all variables defined between the current environment and the default environment. If a variable is defined in both environments, it will only be included once.

func (*VarStore) Count

func (v *VarStore) Count() int

Count returns the number of variables accessible from the current environment. This includes any in the default environment that are not overridden by the current environment. This will match the number of elements returned by All().

func (*VarStore) Defined

func (v *VarStore) Defined() []string

Defined returns the names of all variables defined in the current environment. It does not include any vars that are only defined in the default environment.

func (*VarStore) DefinedIn

func (v *VarStore) DefinedIn(env string) []string

DefinedIn returns the names of all variables defined in the given environment. It does not include any vars that are only defined in the default environment unless "" is given as env.

func (*VarStore) DeleteEnv added in v0.2.0

func (v *VarStore) DeleteEnv(env string)

DeleteEnv immediately removes the given environment and all of its variables. The given environment must not be the default environment.

func (*VarStore) EnvCount

func (v *VarStore) EnvCount() int

func (*VarStore) EnvNames

func (v *VarStore) EnvNames() []string

func (*VarStore) Get

func (v *VarStore) Get(key string) string

func (*VarStore) GetFrom

func (v *VarStore) GetFrom(key, env string) string

GetFrom has no fallback to default, unlike Get.

func (*VarStore) IsDefined

func (v *VarStore) IsDefined(key string) bool

func (*VarStore) IsDefinedIn

func (v *VarStore) IsDefinedIn(key, env string) bool

func (VarStore) MarshalJSON

func (v VarStore) MarshalJSON() ([]byte, error)

func (VarStore) MergedSet added in v0.2.0

func (v VarStore) MergedSet(overrides map[string]string) map[string]string

MergedSet returns the set of keys and values of all variables accessible from the current environment, with the given map of vars taking precedence over any that it has stored.

func (VarStore) NonDefaultEnvsWith added in v0.2.0

func (v VarStore) NonDefaultEnvsWith(name string) []string

func (*VarStore) Remove

func (v *VarStore) Remove(key string)

Remove removes the variable from all environments, including the default one.

func (*VarStore) Set

func (v *VarStore) Set(key, value string)

func (*VarStore) SetIn

func (v *VarStore) SetIn(key, value, env string)

func (*VarStore) UnmarshalJSON

func (v *VarStore) UnmarshalJSON(data []byte) error

func (*VarStore) Unset

func (v *VarStore) Unset(key string)

Unset removes the variable from the current environemnt. If the current environment is not the default environment, the variable will not be removed from the default environment. Use Remove to remove the variable from all environments.

If the current environment *is* the default environment, calling this method has the same effect as calling Remove, as variables are not allowed to exist in only a non-default environment.

func (*VarStore) UnsetIn

func (v *VarStore) UnsetIn(key, env string)

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL