Skip to content

Commit

Permalink
#1298 cmdline support oas (#1371)
Browse files Browse the repository at this point in the history
* Add OpenAPI 3.1 references test class and update router setup

- Create a new test class `OpenAPI31ReferencesTest` for validating OpenAPI 3.1 references.
- Implement setup method to initialize the router and load OpenAPI specs.
- Update test cases to handle requests for both `/pets` and `/users` endpoints.
- Modify the existing OpenAPI specification to include server information in the YAML configuration.

* Implement OpenAPI specification support in RouterCLI

Add command line option to specify OpenAPI specs and initialize routing with the provided spec. Integrate APIProxy for backend communication and request validation. Update MembraneCommandLine to handle new OpenAPI-related options.

* Refactor RouterCLI to support OpenAPI specs and configuration files

- Extract method to initialize router with OpenAPI spec
- Simplify router initialization logic by adding a method for configuration file setup
- Improve readability and maintainability of the RouterCLI class

* Implement command line options for OpenAPI validation and port configuration

- Add support for request and response validation flags in the RouterCLI.
- Introduce port option to specify the API listening port via command line.
- Update option names for clarity, changing "openapi-spec" to "openapi".

* Add OpenAPI options to command line interface and remove redundant arguments

* Update command line usage message to include script format for help output

* Update command line usage message to include script format for help output

* Add OAS command support to Membrane command line

Enhance the MembraneCommandLine class by introducing support for the "oas" command. Update command validation logic and improve usage message formatting. Remove unused methods related to command validation.

* Refactor command line structure to support OpenAPI command and improve parsing logic

- Rename RouterCLI class to reflect new package structure.
- Introduce CliCommand class for better command handling and option management.
- Update MembraneCommandLine to utilize new command structure, enabling more intuitive command parsing.
- Revise usage of command options such as `-c`, `-t`, `-p`, `-v`, and `-V` for improved functionality.
- Adjust error handling and help printing mechanisms for better user experience.

* Add CLI command tests and improve command option handling

- Introduce `CliCommandTest` to validate parsing of command line options and subcommands.
- Enhance `CliCommand` with methods to check if options are set and retrieve their values.
- Update help output formatting to ensure correct display of commands and options.
- Refactor some lines for better readability in `RouterCLI`.

* Update OpenAPI specification file extension from ".yaml" to ".yml" in RouterCLI.java

* Enhance command line interface for OpenAPI support

- Update CLI help descriptions for clarity and consistency.
- Add required option for specifying OpenAPI document location.
- Modify exception handling to print root namespace help on parse errors.
- Introduce `hasSubcommand` method to check for available subcommands.

* Implement command line help option in RouterCLI and update tests for missing required options

- Add help command functionality in `RouterCLI` to display usage information.
- Introduce a new subcommand with required options in the CLI command structure.
- Validate that an error is thrown when required options are not provided in the CLI tests.

* Implement command line option handling and error management

- Add `MissingRequiredOptionException` to handle missing required options more gracefully.
- Update `RouterCLI` to print help message and exit on missing required options.
- Refactor `CliCommand` to support shared options for commands and manage options setting.
- Introduce new commands and update descriptions for clarity in `MembraneCommandLine`.

* Add descriptions for new sub commands in CLI help text

* Add command line support for OpenAPI file and URL

Implement examples for starting the gateway with OpenAPI configuration in the command line interface. Enhance the `CliCommand` class to manage command usage examples and modify the help output to display these examples. Update the `RouterCLI` to accept OpenAPI file or URL as input for configuration.

* Enhance CLI command structure by adding parent command reference

- Introduce a `parent` property in `CliCommand` to track the parent command.
- Update `addSubcommand` method to set the parent for subcommands.
- Modify `printHelp` to display the full command path using the new `getCommandPath` method.

* Refactor command path retrieval and improve help output formatting

- Change `getCommandPath` method visibility from private to public.
- Enhance help output to include examples section consistently.
- Update test cases to verify command path and help output for commands and options.

* Refactor command line options for clarity and consistency

- Update option descriptions for help text to use consistent phrasing.
- Improve naming of argument names for file paths and ports in command line options.
- Streamline help output messages for user-friendly command line interface.

* Fix CLI help description for OpenAPI command to correct "PListen" to "Listen"

* Enhance command line options to specify OpenAPI validation for requests and responses

* Add command line mode for OpenAPI configuration and validation in README.md

Include usage examples for starting the service with an OpenAPI document and enabling request validation.

* Refactoring

---------

Co-authored-by: Thomas Bayer <bayer@predic8.de>
  • Loading branch information
t-burch and predic8 authored Dec 9, 2024
1 parent 74460dd commit f5cb56f
Show file tree
Hide file tree
Showing 11 changed files with 522 additions and 78 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,25 @@ Try the following snippets by copying them into the `conf/proxies.xml` file.

## Using OpenAPI for Configuration & Validation

Configures APIs from OpenAPI and validates messages against the definitions. Needed data like backend addresses are taken from the OpenAPI
description. [See the example](distribution/examples/openapi)
### 1. Command Line Mode

This configuration is all you need to deploy from OpenAPI:
Start Membrane directly with an OpenAPI document using the `oas` command:

```sh
# Display help
service-proxy.sh -h

# Start gateway with OpenAPI file
service-proxy.sh oas -l conf/fruitshop-api.yml

# Start gateway with OpenAPI URL and request validation enabled
service-proxy.sh oas -v -l https://api.predic8.de/shop/v2/api-docs
```

### 2. Configuration File Mode

Configure OpenAPI in proxies.xml to deploy APIs from OpenAPI descriptions. Backend addresses and other data are taken from the OpenAPI document.
[See the example](distribution/examples/openapi)

```xml

Expand Down

This file was deleted.

159 changes: 159 additions & 0 deletions core/src/main/java/com/predic8/membrane/core/cli/CliCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* Copyright 2024 predic8 GmbH, www.predic8.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
package com.predic8.membrane.core.cli;

import com.predic8.membrane.core.util.Pair;
import org.apache.commons.cli.*;
import org.jetbrains.annotations.*;

import java.util.*;

public class CliCommand {
private final String name;
private final String description;
private final List<Pair<String, String>> examples;
private final Map<String, CliCommand> subcommands;
private Options options;
private CliCommand parent;
private CommandLine commandLine;

public CliCommand(String name, String description) {
this.name = name;
this.description = description;
this.subcommands = new LinkedHashMap<>();
this.options = new Options();
this.examples = new ArrayList<>();
}

public CliCommand addSubcommand(CliCommand command) {
subcommands.put(command.getName(), command);
command.parent = this;
return this;
}

public CliCommand addOption(Option option) {
options.addOption(option);
return this;
}

public CliCommand addExample(String exampleDescription, String exampleCommand) {
examples.add(new Pair<>(exampleDescription, exampleCommand));
return this;
}

public CliCommand parse(String[] args) throws ParseException {
if (isCommand(args)) {
String cmd = args[0];
if (hasSubcommand(cmd)) {
return subcommands.get(cmd).parse(Arrays.copyOfRange(args, 1, args.length));
}
throw new ParseException("Unknown command: " + cmd);
}

try {
commandLine = new DefaultParser().parse(options, args, true);
} catch (MissingOptionException e) {
throw new MissingRequiredOptionException(e.getMessage(), this);
}

return this;
}

private static boolean isCommand(String[] args) {
return args.length > 0 && !args[0].startsWith("-");
}

private boolean hasSubcommand(String cmd) {
return subcommands.containsKey(cmd);
}

String getCommandPath() {
if (parent == null) {
return name;
}
return parent.getCommandPath() + " " + name;
}

public void printHelp() {
new HelpFormatter().printHelp(getUsageHelp(), getCommandHelp(), options, getExamplesHelp());
}

private @NotNull String getUsageHelp() {
StringBuilder usage = new StringBuilder(getCommandPath());
if (!subcommands.isEmpty()) {
usage.append(" <command>");
}
if (!options.getOptions().isEmpty()) {
usage.append(" [options]\n\n");
}
return usage.toString();
}

private @NotNull String getExamplesHelp() {
StringBuilder examples = new StringBuilder();
if (!this.examples.isEmpty()) {
examples.append("\nExamples:\n");
this.examples.forEach(example ->
examples.append(" ")
.append(example.first())
.append("\n ")
.append(example.second())
.append("\n"));
}
return examples.toString();
}

private @NotNull String getCommandHelp() {
StringBuilder commands = new StringBuilder();
if (!subcommands.isEmpty()) {
commands.append("Commands:\n");
subcommands.forEach((cmd, ns) ->
commands.append(" ")
.append(cmd)
.append(" - ")
.append(ns.getDescription())
.append("\n")
);
commands.append("\n");
}
if (!options.getOptions().isEmpty()) {
commands.append("Options:\n");
}
return commands.toString();
}

public Options getOptions() {
return options;
}

public void setOptions(Options options) {
this.options = options;
}

public boolean isOptionSet(String opt) {
return commandLine != null && commandLine.hasOption(opt);
}

public String getOptionValue(String opt) {
return commandLine != null ? commandLine.getOptionValue(opt) : null;
}

public String getName() {
return name;
}

public String getDescription() {
return description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Copyright 2024 predic8 GmbH, www.predic8.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
package com.predic8.membrane.core.cli;

import org.apache.commons.cli.*;
import org.jetbrains.annotations.*;

import static org.apache.commons.cli.Option.*;

public class MembraneCommandLine {
private final CliCommand rootNamespace;
private CliCommand currentNamespace;

public MembraneCommandLine() {
rootNamespace = getRootNamespace(getRootOptions());
}

private static Options getRootOptions() {
return new Options().addOption(builder("h").longOpt("help").desc("Display this text").build())
.addOption(builder("c").longOpt("config").argName("proxies.xml location").hasArg().desc("Location of the proxies configuration file").build())
.addOption(builder("t").longOpt("test").argName("proxies.xml location").hasArg().desc("Verifies configuration file and terminates").build());
}

private static @NotNull CliCommand getRootNamespace(Options rootOptions) {
return new CliCommand("service-proxy.sh", "Membrane Service Proxy") {{
setOptions(rootOptions);

addExample("Start gateway configured from OpenAPI file",
"service-proxy.sh oas -l conf/fruitshop-api.yml")
.addExample("Start gateway configured from OpenAPI URL and validate requests",
"service-proxy.sh oas -v -l https://api.predic8.de/shop/v2/api-docs");

addSubcommand(new CliCommand("start", " (Default) Same function as command omitted. Start gateway with configuration from proxies.xml") {{
setOptions(rootOptions);
}});

addSubcommand(new CliCommand("oas", "Use a single OpenAPI document to configure and start gateway") {{
addOption(builder("h").longOpt("help").desc("Display this text").build())
.addOption(builder("l").longOpt("location").argName("OpenAPI location").hasArg().required().desc("(Required) Set URL or path to an OpenAPI document").build())
.addOption(builder("p").longOpt("port").argName("API port").hasArg().desc("Listen port").build())
.addOption(builder("v").longOpt("validate-requests").desc("Validate requests against OpenAPI").build())
.addOption(builder("V").longOpt("validate-responses").desc("Validate responses against OpenAPI").build());

}});
}};
}

public void parse(String[] args) throws ParseException {
currentNamespace = rootNamespace.parse(args);
}

public CliCommand getRootNamespace() {
return rootNamespace;
}

public CliCommand getCommand() {
return currentNamespace;
}

public boolean noCommand() {
return currentNamespace == rootNamespace;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.predic8.membrane.core.cli;

import org.apache.commons.cli.MissingOptionException;

public class MissingRequiredOptionException extends MissingOptionException {
private final CliCommand command;

public MissingRequiredOptionException(String message, CliCommand command) {
super(message);
this.command = command;
}

public CliCommand getCommand() {
return command;
}
}
Loading

0 comments on commit f5cb56f

Please sign in to comment.