Skip to content

Commit

Permalink
feat: operations base path config option
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgtaylor committed Aug 2, 2023
1 parent 2242e20 commit 5a80159
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 27 deletions.
8 changes: 7 additions & 1 deletion cli/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,13 @@ func Load(entrypoint string, root *cobra.Command) (API, error) {
if l.Detect(resp) {
resp.Body = io.NopCloser(bytes.NewReader(body))

api, err := load(root, *uri, *resolved, resp, name, l)
// Override the operation base path if requested, otherwise
// default to the API entrypoint.
opsBase := uri
if config.OperationBase != "" {
opsBase = uri.ResolveReference(&url.URL{Path: config.OperationBase})

Check warning on line 273 in cli/api.go

View check run for this annotation

Codecov / codecov/patch

cli/api.go#L273

Added line #L273 was not covered by tests
}
api, err := load(root, *opsBase, *resolved, resp, name, l)
if err == nil {
cacheAPI(name, &api)
}
Expand Down
11 changes: 6 additions & 5 deletions cli/apiconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ type APIProfile struct {
// APIConfig describes per-API configuration options like the base URI and
// auth scheme, if any.
type APIConfig struct {
name string
Base string `json:"base" yaml:"base"`
SpecFiles []string `json:"spec_files,omitempty" yaml:"spec_files,omitempty" mapstructure:"spec_files,omitempty"`
Profiles map[string]*APIProfile `json:"profiles,omitempty" yaml:"profiles,omitempty" mapstructure:",omitempty"`
TLS *TLSConfig `json:"tls,omitempty" yaml:"tls,omitempty" mapstructure:",omitempty"`
name string
Base string `json:"base" yaml:"base"`
OperationBase string `json:"operation_base,omitempty" yaml:"operation_base,omitempty" mapstructure:"operation_base,omitempty"`
SpecFiles []string `json:"spec_files,omitempty" yaml:"spec_files,omitempty" mapstructure:"spec_files,omitempty"`
Profiles map[string]*APIProfile `json:"profiles,omitempty" yaml:"profiles,omitempty" mapstructure:",omitempty"`
TLS *TLSConfig `json:"tls,omitempty" yaml:"tls,omitempty" mapstructure:",omitempty"`
}

// Save the API configuration to disk.
Expand Down
27 changes: 27 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ $ restish api sync $NAME

?> This is usually not necessary, as Restish will update the API description every 24 hours. Use this if you want to force an update sooner!

### Editing All APIs

You can edit all APIs at once in your editor of choice via:

```bash
$ restish api edit
```

You will need to have `EDITOR` or `VISUAL` environment variables set to which editor you want to use, e.g. `export VISUAL='code --wait'` for VSCode.

### Persistent headers & query parameters

Follow the prompts to add or edit persistent headers or query parameters. These are values that get sent with **every request** when using that profile.
Expand Down Expand Up @@ -353,3 +363,20 @@ In this case you can download the spec files to your machine and link to them (o
```

!> If more than one file path is specified, then the loaded APIs are merged in the order specified. You will get operations from both APIs, but there can only be a single API title or description so the first encountered non-zero value is used.

### Operation Base Path

Most of the time when an API is served at some sub-path like `https://example.com/my-api` the operation paths should be treated as relative to that sub-path, that is an operation `/foo` would result in a request to `https://example.com/my-api/foo`. Sometimes that is not the behavior you want, for example the OpenAPI operations may already contain the full path including the sub-path.

The `operation_base` parameter can be used to change this behavior. It defaults to the API base path, but can be changed to any URL reference and will be resolved against the base path. For example, to make an operation use `/my-op` rather than `/my-api/v2-beta1/my-op` as its URL path:

```json
{
"my-api-beta": {
"base": "https://example.com/my-api/v2-beta1",
"operation_base": "/"
}
}
```

?> This is an advanced feature which is not needed in most cases.
160 changes: 139 additions & 21 deletions docs/schemas/apis.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
"format": "uri-reference",
"description": "The base URL of the API. This is used to try and fetch the OpenAPI spec as well as resolve relative references. If the base contains a path, OpenAPI operations are assumed relative to that base path."
},
"operation_base": {
"type": "string",
"format": "uri-reference",
"description": "Overrides the base URL path of API operations. This can be used to treat the OpenAPI paths as absolute even when an API is served from a subpath on the server, or make other modifications to support additional use-cases. If unset, this matches the base URL path."
},
"spec_files": {
"type": "array",
"description": "The local filename or remote URL of the OpenAPI spec file(s) to load for this API if autodetection cannot be used. If multiple files are specified, their operations will be merged together.",
Expand Down Expand Up @@ -51,33 +56,146 @@
}
},
"auth": {
"type": "object",
"description": "Authentication & authorization setting for this API profile.",
"required": ["name"],
"properties": {
"name": {
"description": "Authentication & authorization scheme name.",
"anyOf": [
{
"enum": [
"oauth-client-credentials",
"oauth-authorization-code",
"external-tool"
]
"oneOf": [
{
"type": "object",
"description": "Authentication & authorization setting for this API profile.",
"additionalProperties": false,
"required": ["name", "params"],
"properties": {
"name": {
"const": "http-basic",
"description": "Auth scheme name."
},
"params": {
"type": "object",
"description": "Parameters for the auth scheme. For http-basic, this is the username and password to send with each request.",
"additionalProperties": false,
"required": ["username", "password"],
"properties": {
"username": {
"type": "string",
"description": "The username to send with each request."
},
"password": {
"type": "string",
"description": "The password to send with each request."
}
}
}
}
},
{
"type": "object",
"description": "Authentication & authorization setting for this API profile.",
"additionalProperties": false,
"required": ["name", "params"],
"properties": {
"name": {
"const": "oauth-client-credentials",
"description": "Auth scheme name."
},
"params": {
"type": "object",
"description": "Parameters for the auth scheme. For oauth-client-credentials, this is at least the client ID, client secret, and token URL.",
"required": ["client_id", "client_secret", "token_url"],
"properties": {
"audience": {
"type": "string",
"description": "Audience restricts which APIs will accept the generated auth token."
},
"client_id": {
"type": "string",
"description": "The client ID to send with each request."
},
"client_secret": {
"type": "string",
"description": "The client secret to send with each request."
},
"scopes": {
"type": "string",
"description": "A space-separated list of scopes to request, enabling access to certain resources & actions in the API."
},
"token_url": {
"type": "string",
"description": "The URL to request an auth token from."
}
}
}
}
},
{
"type": "object",
"description": "Authentication & authorization setting for this API profile.",
"additionalProperties": false,
"required": ["name", "params"],
"properties": {
"name": {
"const": "oauth-authorization-code",
"description": "Auth scheme name."
},
{
"type": "string"
"params": {
"type": "object",
"description": "Parameters for the auth scheme. For oauth-authorization-code, this is at least the client ID, authorize URL, and token URL.",
"required": ["client_id", "authorize_url", "token_url"],
"properties": {
"audience": {
"type": "string",
"description": "Audience restricts which APIs will accept the generated auth token."
},
"client_id": {
"type": "string",
"description": "The client ID to send with each request."
},
"client_secret": {
"type": "string",
"description": "The client secret (if any) to send with each request."
},
"scopes": {
"type": "string",
"description": "A space-separated list of scopes to request, enabling access to certain resources & actions in the API."
},
"token_url": {
"type": "string",
"description": "The URL to request an auth token from."
},
"authorize_url": {
"type": "string",
"description": "The URL to initiate a web login flow."
}
}
}
]
}
},
"params": {
{
"type": "object",
"description": "Auth parameter names and values to send as additional values in the auth request. These are specific to each auth scheme name and implementation, and include things like the OAuth2 authorize / token URLs, client ID / secret, audience, etc. See https://rest.sh/#/configuration?id=api-auth.",
"additionalProperties": {
"type": "string"
"description": "Authentication & authorization setting for this API profile.",
"additionalProperties": false,
"required": ["name", "params"],
"properties": {
"name": {
"const": "external-tool",
"description": "Auth scheme name."
},
"params": {
"type": "object",
"description": "Parameters for the auth scheme. For external-tool, this is the commandline to run to get the auth token. The commandline must output an object with 'uri' and 'headers' properties.",
"required": ["commandline"],
"properties": {
"commandline": {
"type": "string",
"description": "The commandline to run to get the auth token. The commandline must output an object with 'uri' and 'headers' properties."
},
"omitbody": {
"type": "string",
"description": "If true, do not send the request body to the command on stdin, just the 'method', 'uri', and 'headers' as a JSON object.",
"enum": ["true", "false"]
}
}
}
}
}
}
]
},
"tls": {
"type": "object",
Expand Down

0 comments on commit 5a80159

Please sign in to comment.