Skip to content

Commit

Permalink
Add DefaultReadmeGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFossAWS committed Mar 22, 2023
1 parent e2325f6 commit 0d4eb34
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public final class TypeScriptSettings {
private static final String PROTOCOL = "protocol";
private static final String PRIVATE = "private";
private static final String PACKAGE_MANAGER = "packageManager";
private static final String CREATE_DEFAULT_README = "createDefaultReadme";

private String packageName;
private String packageDescription = "";
Expand All @@ -72,6 +73,7 @@ public final class TypeScriptSettings {
private RequiredMemberMode requiredMemberMode =
RequiredMemberMode.NULLABLE;
private PackageManager packageManager = PackageManager.YARN;
private boolean createDefaultReadme = false;

@Deprecated
public static TypeScriptSettings from(Model model, ObjectNode config) {
Expand Down Expand Up @@ -103,6 +105,8 @@ public static TypeScriptSettings from(Model model, ObjectNode config, ArtifactTy
settings.packageJson = config.getObjectMember(PACKAGE_JSON).orElse(Node.objectNode());
config.getStringMember(PROTOCOL).map(StringNode::getValue).map(ShapeId::from).ifPresent(settings::setProtocol);
settings.setPrivate(config.getBooleanMember(PRIVATE).map(BooleanNode::getValue).orElse(false));
settings.setCreateDefaultReadme(
config.getBooleanMember(CREATE_DEFAULT_README).map(BooleanNode::getValue).orElse(false));
settings.setPackageManager(
config.getStringMember(PACKAGE_MANAGER)
.map(s -> PackageManager.fromString(s.getValue()))
Expand Down Expand Up @@ -261,6 +265,14 @@ public void setPrivate(boolean isPrivate) {
this.isPrivate = isPrivate;
}

public boolean createDefaultReadme() {
return createDefaultReadme;
}

public void setCreateDefaultReadme(boolean createDefaultReadme) {
this.createDefaultReadme = createDefaultReadme;
}

/**
* Returns if the generated package will be a client.
*
Expand Down Expand Up @@ -427,11 +439,12 @@ public String getDefaultSigningName() {
public enum ArtifactType {
CLIENT(SymbolVisitor::new,
Arrays.asList(PACKAGE, PACKAGE_DESCRIPTION, PACKAGE_JSON, PACKAGE_VERSION, PACKAGE_MANAGER,
SERVICE, PROTOCOL, TARGET_NAMESPACE, PRIVATE, REQUIRED_MEMBER_MODE)),
SERVICE, PROTOCOL, TARGET_NAMESPACE, PRIVATE, REQUIRED_MEMBER_MODE,
CREATE_DEFAULT_README)),
SSDK((m, s) -> new ServerSymbolVisitor(m, new SymbolVisitor(m, s)),
Arrays.asList(PACKAGE, PACKAGE_DESCRIPTION, PACKAGE_JSON, PACKAGE_VERSION, PACKAGE_MANAGER,
SERVICE, PROTOCOL, TARGET_NAMESPACE, PRIVATE, REQUIRED_MEMBER_MODE,
DISABLE_DEFAULT_VALIDATION));
DISABLE_DEFAULT_VALIDATION, CREATE_DEFAULT_README));

private final BiFunction<Model, TypeScriptSettings, SymbolProvider> symbolProviderFactory;
private final List<String> configProperties;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.smithy.typescript.codegen.integration;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.typescript.codegen.TypeScriptCodegenContext;
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
import software.amazon.smithy.utils.IoUtils;
import software.amazon.smithy.utils.SmithyInternalApi;
import software.amazon.smithy.utils.StringUtils;

@SmithyInternalApi
public final class DefaultReadmeGenerator implements TypeScriptIntegration {

public static final String README_FILENAME = "README.md";
public static final String DEFAULT_CLIENT_README_TEMPLATE = "default_readme_client.md.template";
public static final String DEFAULT_SERVER_README_TEMPLATE = "default_readme_server.md.template";

@Override
public void customize(TypeScriptCodegenContext codegenContext) {
TypeScriptSettings settings = codegenContext.settings();

if (!settings.createDefaultReadme()) {
return;
}

String file = settings.generateClient() ? DEFAULT_CLIENT_README_TEMPLATE : DEFAULT_SERVER_README_TEMPLATE;

Model model = codegenContext.model();

codegenContext.writerDelegator().useFileWriter(README_FILENAME, "", writer -> {
ServiceShape service = settings.getService(model);
String resource = IoUtils.readUtf8Resource(getClass(), file);
resource = resource.replaceAll(Pattern.quote("${packageName}"), settings.getPackageName());

String clientName = StringUtils.capitalize(service.getId().getName(service));

resource = resource.replaceAll(Pattern.quote("${serviceId}"), clientName);

String rawDocumentation = service.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
.orElse("");
String documentation = Arrays.asList(rawDocumentation.split("\n")).stream()
.map(StringUtils::trim)
.collect(Collectors.joining("\n"));
resource = resource.replaceAll(Pattern.quote("${documentation}"), Matcher.quoteReplacement(documentation));

TopDownIndex topDownIndex = TopDownIndex.of(model);
OperationShape firstOperation = topDownIndex.getContainedOperations(service).iterator().next();
String operationName = firstOperation.getId().getName(service);
resource = resource.replaceAll(Pattern.quote("${commandName}"), operationName);

// The $ character is escaped using $$
writer.write(resource.replaceAll(Pattern.quote("$"), Matcher.quoteReplacement("$$")));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ software.amazon.smithy.typescript.codegen.integration.AddDefaultsModeDependency
software.amazon.smithy.typescript.codegen.integration.AddHttpApiKeyAuthPlugin
software.amazon.smithy.typescript.codegen.integration.AddBaseServiceExceptionClass
software.amazon.smithy.typescript.codegen.integration.AddSdkStreamMixinDependency
software.amazon.smithy.typescript.codegen.integration.DefaultReadmeGenerator
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<!-- generated file, do not edit directly -->

# ${packageName}

## Description

SDK for JavaScript ${serviceId} Client for Node.js, Browser and React Native.

${documentation}

## Installing
To install the this package, simply type add or install ${packageName}
using your favorite package manager:
- `npm install ${packageName}`
- `yarn add ${packageName}`
- `pnpm add ${packageName}`

## Getting Started

### Import

To send a request, you only need to import the `${serviceId}Client` and
the commands you need, for example `${commandName}Command`:

```js
// CJS example
const { ${serviceId}Client, ${commandName}Command } = require("${packageName}");
```

```ts
// ES6+ example
import { ${serviceId}Client, ${commandName}Command } from "${packageName}";
```

### Usage

To send a request, you:

- Initiate client with configuration.
- Initiate command with input parameters.
- Call `send` operation on client with command object as input.
- If you are using a custom http handler, you may call `destroy()` to close open connections.

```js
// a client can be shared by different commands.
const client = new ${serviceId}Client();

const params = { /** input parameters */ };
const command = new ${commandName}Command(params);
```

#### Async/await

We recommend using [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
operator to wait for the promise returned by send operation as follows:

```js
// async/await.
try {
const data = await client.send(command);
// process data.
} catch (error) {
// error handling.
} finally {
// finally.
}
```

Async-await is clean, concise, intuitive, easy to debug and has better error handling
as compared to using Promise chains or callbacks.

#### Promises

You can also use [Promise chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#chaining)
to execute send operation.

```js
client.send(command).then(
(data) => {
// process data.
},
(error) => {
// error handling.
}
);
```

Promises can also be called using `.catch()` and `.finally()` as follows:

```js
client
.send(command)
.then((data) => {
// process data.
})
.catch((error) => {
// error handling.
})
.finally(() => {
// finally.
});
```

#### Callbacks

We do not recommend using callbacks because of [callback hell](http://callbackhell.com/),
but they are supported by the send operation.

```js
// callbacks.
client.send(command, (err, data) => {
// process err and data.
});
```

### Troubleshooting

When the service returns an exception, the error will include the exception information,
as well as response metadata (e.g. request id).

```js
try {
const data = await client.send(command);
// process data.
} catch (error) {
const { requestId, httpStatusCode } = error.$$metadata;
console.log({ requestId, httpStatusCode });
/**
* The keys within exceptions are also parsed.
* You can access them by specifying exception names:
* if (error.name === 'SomeServiceException') {
* const value = error.specialKeyInException;
* }
*/
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!-- generated file, do not edit directly -->

# ${packageName}

## Description

JavaScript Server SDK for ${serviceId}

${documentation}

## Installing
To install this package, simply type add or install ${packageName}
using your favorite package manager:
- `npm install ${packageName}`
- `yarn add ${packageName}`
- `pnpm add ${packageName}`

## Getting Started

Below is an example service handler created for the ${commandName} operation.

```ts
import { createServer, IncomingMessage, ServerResponse } from "http";
import { HttpRequest } from "@aws-sdk/protocol-http";
import {
${serviceId}Service as __${serviceId}Service,
${commandName}Input,
${commandName}Output,
get${serviceId}ServiceHandler
} from "${packageName}";
import { convertEvent, convertResponse } from "@aws-smithy/server-node";

class ${serviceId}Service implements __${serviceId}Service {
${commandName}(input: ${commandName}Input, request: HttpRequest): ${commandName}Output {
// Populate your business logic
}
}

const serviceHandler = get${serviceId}ServiceHandler(new ${serviceId}Service());

const server = createServer(async function (
req: IncomingMessage,
res: ServerResponse<IncomingMessage> & { req: IncomingMessage }
) {
// Convert NodeJS's http request to an HttpRequest.
const httpRequest = convertRequest(req);

// Call the service handler, which will route the request to the GreetingService
// implementation and then serialize the response to an HttpResponse.
const httpResponse = await serviceHandler.handle(httpRequest);

// Write the HttpResponse to NodeJS http's response expected format.
return writeResponse(httpResponse, res);
});

server.listen(3000);
```
Loading

0 comments on commit 0d4eb34

Please sign in to comment.