Skip to content

Commit

Permalink
Merge pull request #888 from pmcelhaney/openapi-optional
Browse files Browse the repository at this point in the history
option to run Counterfact without OpenAPI
  • Loading branch information
pmcelhaney authored May 6, 2024
2 parents b289e94 + 5fe28a5 commit 3e92e32
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-dodos-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"counterfact": minor
---

option to run Counterfact without OpenAPI (#834)
14 changes: 10 additions & 4 deletions bin/counterfact.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async function main(source, destination) {
padTagLine(taglines[Math.floor(Math.random() * taglines.length)]),
"",
`| API Base URL ==> ${url}`,
`| Swagger UI ==> ${swaggerUrl}`,
source === "_" ? undefined : `| Swagger UI ==> ${swaggerUrl}`,
"",
"| Instructions ==> https://counterfact.dev/docs/usage.html",
"| Help/feedback ==> https://github.com/pmcelhaney/counterfact/issues",
Expand All @@ -100,7 +100,9 @@ async function main(source, destination) {
"Starting REPL, type .help for more info",
];

process.stdout.write(introduction.join("\n"));
process.stdout.write(
introduction.filter((line) => line !== undefined).join("\n"),
);

process.stdout.write("\n\n");

Expand All @@ -120,9 +122,13 @@ async function main(source, destination) {
program
.name("counterfact")
.description(
"Counterfact is a tool for generating a REST API from an OpenAPI document.",
"Counterfact is a tool for mocking REST APIs in development. See https://counterfact.dev for more info.",
)
.argument(
"[openapi.yaml]",
'path or URL to OpenAPI document or "_" to run without OpenAPI',
"_",
)
.argument("<openapi.yaml>", "path or URL to OpenAPI document")
.argument("[destination]", "path to generated code", ".")
.option("--port <number>", "server port number", DEFAULT_PORT)
.option("--swagger", "include swagger-ui")
Expand Down
45 changes: 45 additions & 0 deletions docs/usage-without-openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# What if I don't have an OpenAPI document?

## Running the server

If you don't have an OpenAPI document, you can still use Counterfact. You just won't have the benefit of code generation and you'll have to write your mocks by hand.

If you run Counterfact without any arguments, it will run in "OpenAPI-free" mode.

```sh
npx counterfact
```

If you want to specify the path where code is generated, enter "\_" in place of the path or URL to the OpenAPI document. For example, to put the code in an `api` directory under the working directory:

```sh
npx counterfact _ api
```

## Creating routes

In the file where the code is generated, you should find a directory -- initially empty -- called `paths`. Here you can start adding mock APIs by adding JS or TS files. For example, to mock an API that responds to `GET /hello/world`, with "World says hello!" you would create `paths/hello/world.js` that looks like this.

```js
// hello/world.js
export const GET() {
return "World says hello!";
}
```

If part of the path is variable, name the file or directory where the variable part is `{variableName}`. Here's a more advanced version of "hello world" where both the greeting and the subject are variable.

```js
//{greeting}/{subject}.js
export const GET($) {
return `${$.path.subject} says ${$.path.greeting}!`;
}
```

For more information on the `$` object, see the [usage guide](./usage.md).

## Now that you know how to work without an OpenAPI doc, here's why you should have one anyway

OpenAPI is the de-facto standard for documenting REST APIs. Counterfact is just one of dozens of tools that use it. And if you pass Counterfact an OpenAPI doc, it will save you a lot of time by automatically generating default type-safe implementations of each API, with powerful context-sensitive autocomplete when you want to make changes.

Not many people love writing documentation. Fewer people still love working on APIs that are not documented. Counterfact makes documentation useful, with immediate ROI, so that so that maintaining the docs is just as rewarding as writing code.
9 changes: 4 additions & 5 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ This will generate TypeScript code for the Swagger Pet Store and start the serve
<summary>Here are the full details on CLI usage</summary>

```txt
Usage: counterfact [options] <openapi.yaml> [destination]
Usage: counterfact [options] [openapi.yaml] [destination]
Counterfact is a tool for generating a REST API from an OpenAPI document.
Counterfact is a tool for mocking REST APIs in development. See https://counterfact.dev for more info.
Arguments:
openapi.yaml path or URL to OpenAPI document
openapi.yaml path or URL to OpenAPI document or "_" to run without OpenAPI (default: "_")
destination path to generated code (default: ".")
Options:
Expand All @@ -41,7 +41,6 @@ Options:
--proxy-url <string> proxy URL
--prefix <string> base path from which routes will be served (e.g. /api/v1)
-h, --help display help for command
```

</details>
Expand Down Expand Up @@ -76,7 +75,7 @@ The code under `components` and `path-types` is regenerated every time you run C
See also [Generated Code FAQ](./faq-generated-code.md)

> [!TIP]
> You don't have to use the code generator. It wasn't even part of Counterfact originally. You can also create the files under `paths` by hand. The main benefit of generating code is all the type information that's managed for you and kept in sync with OpenAPI.
> You don't have to use the code generator. It wasn't even part of Counterfact originally. You can also create the files under `paths` by hand. The main benefit of generating code is all the type information that's managed for you and kept in sync with OpenAPI. See [What if I don't have an OpenAPI document?](./usage-without-openapi.md)
## Routing is where it's at 🔀

Expand Down
17 changes: 11 additions & 6 deletions src/typescript-generator/specification.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import { Requirement } from "./requirement.js";

const debug = createDebug("counterfact:typescript-generator:specification");

const EMPTY_OPENAPI = `{
"openapi": "3.0.0",
"info": {
"title": "Sample API",
"version": "1.0.0"
},
"paths": {}
}`;

export class Specification {
constructor(rootUrl) {
this.cache = new Map();
Expand All @@ -20,13 +29,11 @@ export class Specification {
debug("getting requirement at %s from %s", url, fromUrl);

const [file, path] = url.split("#");

const filePath = nodePath
.join(fromUrl.split("#").at(0), file)
.replaceAll("\\", "/")
// eslint-disable-next-line prefer-named-capture-group, regexp/prefer-named-capture-group
.replace(/:\/([^/])/u, "://$1");

const fileUrl = filePath === "." ? this.rootUrl : filePath;

debug("reading specification at %s", fileUrl);
Expand All @@ -52,18 +59,16 @@ export class Specification {

debug("cache miss, reading file at %s", urlOrPath);

const source = await readFile(urlOrPath, "utf8");
const source =
urlOrPath === "_" ? EMPTY_OPENAPI : await readFile(urlOrPath, "utf8");

debug("read file");

debug("parsing YAML");

const data = await yaml.load(source);

debug("parsed YAML: %o", data);

this.cache.set(urlOrPath, data);

this.rootRequirement = new Requirement(data, `${urlOrPath}#`, this);

return data;
Expand Down

0 comments on commit 3e92e32

Please sign in to comment.