Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions global.d.ts

This file was deleted.

1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ module.exports = {
coverageDirectory: 'coverage/ts',
collectCoverage: true,
coverageReporters: ['json', 'lcov', 'text'],
coveragePathIgnorePatterns: ['/node_modules/', '/tests/data/'],
};
11 changes: 8 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"test:debug": "npx --node-arg=--inspect jest --runInBand"
},
"engines": {
"node": ">=10.0.0",
"node": ">=10.4.0",
"npm": ">=5.6.0"
},
"repository": {
Expand All @@ -34,6 +34,7 @@
"homepage": "https://github.com/eduardomourar/cloudformation-cli-typescript-plugin#readme",
"dependencies": {
"autobind-decorator": "^2.4.0",
"class-transformer": "^0.3.1",
"promise-sequential": "^1.1.1",
"reflect-metadata": "^0.1.13",
"tombok": "https://github.com/eduardomourar/tombok/releases/download/v0.0.1/tombok-0.0.1.tgz",
Expand Down
9 changes: 7 additions & 2 deletions python/rpdk/typescript/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from rpdk.core.jsonutils.resolver import ContainerType, resolve_models
from rpdk.core.plugin_base import LanguagePlugin

from .resolver import contains_model, translate_type
from .resolver import contains_model, get_inner_type, translate_type
from .utils import safe_reserved

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -42,6 +42,7 @@ def __init__(self):
)
self.env.filters["translate_type"] = translate_type
self.env.filters["contains_model"] = contains_model
self.env.filters["get_inner_type"] = get_inner_type
self.env.filters["safe_reserved"] = safe_reserved
self.env.globals["ContainerType"] = ContainerType
self.namespace = None
Expand Down Expand Up @@ -159,7 +160,11 @@ def generate(self, project):
LOG.debug("Writing file: %s", path)
template = self.env.get_template("models.ts")
contents = template.render(
lib_name=SUPPORT_LIB_NAME, type_name=project.type_name, models=models,
lib_name=SUPPORT_LIB_NAME,
type_name=project.type_name,
models=models,
primaryIdentifier=project.schema.get("primaryIdentifier", []),
additionalIdentifiers=project.schema.get("additionalIdentifiers", []),
)
project.overwrite(path, contents)

Expand Down
47 changes: 45 additions & 2 deletions python/rpdk/typescript/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,61 @@

PRIMITIVE_TYPES = {
"string": "string",
"integer": "number",
"integer": "integer",
"boolean": "boolean",
"number": "number",
UNDEFINED: "Object",
UNDEFINED: "object",
}
PRIMITIVE_WRAPPERS = {
"string": "String",
"integer": "Integer",
"boolean": "Boolean",
"number": "Number",
"object": "Object",
}


class InnerType:
def __init__(self, item_type):
self.primitive = False
self.classes = []
self.type = self.resolve_type(item_type)
self.wrapper_type = self.type
if self.primitive:
self.wrapper_type = PRIMITIVE_WRAPPERS[self.type]

def resolve_type(self, resolved_type):
if resolved_type.container == ContainerType.PRIMITIVE:
self.primitive = True
return PRIMITIVE_TYPES[resolved_type.type]
if resolved_type.container == ContainerType.MULTIPLE:
self.primitive = True
return "object"
if resolved_type.container == ContainerType.MODEL:
return resolved_type.type
if resolved_type.container == ContainerType.DICT:
self.classes.append("Map")
elif resolved_type.container == ContainerType.LIST:
self.classes.append("Array")
elif resolved_type.container == ContainerType.SET:
self.classes.append("Set")
else:
raise ValueError(f"Unknown container type {resolved_type.container}")

return self.resolve_type(resolved_type.type)


def get_inner_type(resolved_type):
return InnerType(resolved_type)


def translate_type(resolved_type):
if resolved_type.container == ContainerType.MODEL:
return resolved_type.type
if resolved_type.container == ContainerType.PRIMITIVE:
return PRIMITIVE_TYPES[resolved_type.type]
if resolved_type.container == ContainerType.MULTIPLE:
return "object"

item_type = translate_type(resolved_type.type)

Expand Down
53 changes: 25 additions & 28 deletions python/rpdk/typescript/templates/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { ResourceModel } from './models';
// Use this logger to forward log messages to CloudWatch Logs.
const LOGGER = console;

interface CallbackContext extends Record<string, any> {}

class Resource extends BaseResource<ResourceModel> {

/**
Expand All @@ -23,19 +25,17 @@ class Resource extends BaseResource<ResourceModel> {
*
* @param session Current AWS session passed through from caller
* @param request The request object for the provisioning request passed to the implementor
* @param callbackContext Custom context object to enable handlers to process re-invocation
* @param callbackContext Custom context object to allow the passing through of additional
* state or metadata between subsequent retries
*/
@handlerEvent(Action.Create)
public async create(
session: Optional<SessionProxy>,
request: ResourceHandlerRequest<ResourceModel>,
callbackContext: Map<string, any>,
callbackContext: CallbackContext,
): Promise<ProgressEvent> {
const model: ResourceModel = request.desiredResourceState;
const progress: ProgressEvent<ResourceModel> = ProgressEvent.builder()
.status(OperationStatus.InProgress)
.resourceModel(model)
.build() as ProgressEvent<ResourceModel>;
const progress = ProgressEvent.progress<ProgressEvent<ResourceModel, CallbackContext>>(model);
// TODO: put code here

// Example:
Expand All @@ -61,19 +61,17 @@ class Resource extends BaseResource<ResourceModel> {
*
* @param session Current AWS session passed through from caller
* @param request The request object for the provisioning request passed to the implementor
* @param callbackContext Custom context object to enable handlers to process re-invocation
* @param callbackContext Custom context object to allow the passing through of additional
* state or metadata between subsequent retries
*/
@handlerEvent(Action.Update)
public async update(
session: Optional<SessionProxy>,
request: ResourceHandlerRequest<ResourceModel>,
callbackContext: Map<string, any>,
callbackContext: CallbackContext,
): Promise<ProgressEvent> {
const model: ResourceModel = request.desiredResourceState;
const progress: ProgressEvent<ResourceModel> = ProgressEvent.builder()
.status(OperationStatus.InProgress)
.resourceModel(model)
.build() as ProgressEvent<ResourceModel>;
const progress = ProgressEvent.progress<ProgressEvent<ResourceModel, CallbackContext>>(model);
// TODO: put code here
progress.status = OperationStatus.Success;
return progress;
Expand All @@ -86,18 +84,17 @@ class Resource extends BaseResource<ResourceModel> {
*
* @param session Current AWS session passed through from caller
* @param request The request object for the provisioning request passed to the implementor
* @param callbackContext Custom context object to enable handlers to process re-invocation
* @param callbackContext Custom context object to allow the passing through of additional
* state or metadata between subsequent retries
*/
@handlerEvent(Action.Delete)
public async delete(
session: Optional<SessionProxy>,
request: ResourceHandlerRequest<ResourceModel>,
callbackContext: Map<string, any>,
callbackContext: CallbackContext,
): Promise<ProgressEvent> {
const model: ResourceModel = request.desiredResourceState;
const progress: ProgressEvent<ResourceModel> = ProgressEvent.builder()
.status(OperationStatus.InProgress)
.build() as ProgressEvent<ResourceModel>;
const progress = ProgressEvent.progress<ProgressEvent<ResourceModel, CallbackContext>>();
// TODO: put code here
progress.status = OperationStatus.Success;
return progress;
Expand All @@ -109,20 +106,18 @@ class Resource extends BaseResource<ResourceModel> {
*
* @param session Current AWS session passed through from caller
* @param request The request object for the provisioning request passed to the implementor
* @param callbackContext Custom context object to enable handlers to process re-invocation
* @param callbackContext Custom context object to allow the passing through of additional
* state or metadata between subsequent retries
*/
@handlerEvent(Action.Read)
public async read(
session: Optional<SessionProxy>,
request: ResourceHandlerRequest<ResourceModel>,
callbackContext: Map<string, any>,
callbackContext: CallbackContext,
): Promise<ProgressEvent> {
const model: ResourceModel = request.desiredResourceState;
// TODO: put code here
const progress: ProgressEvent<ResourceModel> = ProgressEvent.builder()
.status(OperationStatus.Success)
.resourceModel(model)
.build() as ProgressEvent<ResourceModel>;
const progress = ProgressEvent.success<ProgressEvent<ResourceModel, CallbackContext>>(model);
return progress;
}

Expand All @@ -132,19 +127,21 @@ class Resource extends BaseResource<ResourceModel> {
*
* @param session Current AWS session passed through from caller
* @param request The request object for the provisioning request passed to the implementor
* @param callbackContext Custom context object to enable handlers to process re-invocation
* @param callbackContext Custom context object to allow the passing through of additional
* state or metadata between subsequent retries
*/
@handlerEvent(Action.List)
public async list(
session: Optional<SessionProxy>,
request: ResourceHandlerRequest<ResourceModel>,
callbackContext: Map<string, any>,
callbackContext: CallbackContext,
): Promise<ProgressEvent> {
const model: ResourceModel = request.desiredResourceState;
// TODO: put code here
const progress: ProgressEvent<ResourceModel> = ProgressEvent.builder()
const progress = ProgressEvent.builder<ProgressEvent<ResourceModel, CallbackContext>>()
.status(OperationStatus.Success)
.resourceModels([])
.build() as ProgressEvent<ResourceModel>;
.resourceModels([model])
.build();
return progress;
}
}
Expand Down
95 changes: 92 additions & 3 deletions python/rpdk/typescript/templates/models.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,103 @@
// This is a generated file. Modifications will be overwritten.
import { BaseModel, Optional } from '{{lib_name}}';
import { BaseModel, Dict, integer, Integer, Optional, transformValue } from '{{lib_name}}';
import { Exclude, Expose, Type, Transform } from 'class-transformer';

{% for model, properties in models.items() %}
export class {{ model|uppercase_first_letter }}{% if model == "ResourceModel" %} extends BaseModel{% endif %} {
export class {{ model|uppercase_first_letter }} extends BaseModel {
['constructor']: typeof {{ model|uppercase_first_letter }};

{% if model == "ResourceModel" %}
@Exclude()
public static readonly TYPE_NAME: string = '{{ type_name }}';

{% for identifier in primaryIdentifier %}
{% set components = identifier.split("/") %}
@Exclude()
protected readonly IDENTIFIER_KEY_{{ components[2:]|join('_')|upper }}: string = '{{ identifier }}';
{% endfor -%}

{% for identifiers in additionalIdentifiers %}
{% for identifier in identifiers %}
{% set components = identifier.split("/") %}
@Exclude()
protected readonly IDENTIFIER_KEY_{{ components[2:]|join('_')|upper }}: string = '{{ identifier }}';
{% endfor %}
{% endfor %}
{% endif %}

{% for name, type in properties.items() %}
{{ name|safe_reserved }}: Optional<{{ type|translate_type }}>;
{% set translated_type = type|translate_type %}
{% set inner_type = type|get_inner_type %}
@Expose({ name: '{{ name }}' })
{% if type|contains_model %}
@Type(() => {{ inner_type.type }})
{% else %}
@Transform(
(value: any, obj: any) =>
transformValue({{ inner_type.wrapper_type }}, '{{ name|lowercase_first_letter|safe_reserved }}', value, obj, [{{ inner_type.classes|join(', ') }}]),
{
toClassOnly: true,
}
)
{% endif %}
{{ name|lowercase_first_letter|safe_reserved }}?: Optional<{{ translated_type }}>;
{% endfor %}

{% if model == "ResourceModel" %}
@Exclude()
public getPrimaryIdentifier(): Dict {
const identifier: Dict = {};
{% for identifier in primaryIdentifier %}
{% set components = identifier.split("/") %}
if (this.{{components[2]|lowercase_first_letter}} != null
{%- for i in range(4, components|length + 1) -%}
{#- #} && this
{%- for component in components[2:i] -%} .{{component|lowercase_first_letter}} {%- endfor -%}
{#- #} != null
{%- endfor -%}
) {
identifier[this.IDENTIFIER_KEY_{{ components[2:]|join('_')|upper }}] = this{% for component in components[2:] %}.{{component|lowercase_first_letter}}{% endfor %};
}

{% endfor %}
// only return the identifier if it can be used, i.e. if all components are present
return Object.keys(identifier).length === {{ primaryIdentifier|length }} ? identifier : null;
}

@Exclude()
public getAdditionalIdentifiers(): Array<Dict> {
const identifiers: Array<Dict> = new Array<Dict>();
{% for identifiers in additionalIdentifiers %}
if (this.getIdentifier {%- for identifier in identifiers -%} _{{identifier.split("/")[-1]|uppercase_first_letter}} {%- endfor -%} () != null) {
identifiers.push(this.getIdentifier{% for identifier in identifiers %}_{{identifier.split("/")[-1]|uppercase_first_letter}}{% endfor %}());
}
{% endfor %}
// only return the identifiers if any can be used
return identifiers.length === 0 ? null : identifiers;
}
{% for identifiers in additionalIdentifiers %}

@Exclude()
public getIdentifier {%- for identifier in identifiers -%} _{{identifier.split("/")[-1]|uppercase_first_letter}} {%- endfor -%} (): Dict {
const identifier: Dict = {};
{% for identifier in identifiers %}
{% set components = identifier.split("/") %}
if ((this as any).{{components[2]|lowercase_first_letter}} != null
{%- for i in range(4, components|length + 1) -%}
{#- #} && (this as any)
{%- for component in components[2:i] -%} .{{component|lowercase_first_letter}} {%- endfor -%}
{#- #} != null
{%- endfor -%}
) {
identifier[this.IDENTIFIER_KEY_{{ components[2:]|join('_')|upper }}] = (this as any){% for component in components[2:] %}.{{component|lowercase_first_letter}}{% endfor %};
}

{% endfor %}
// only return the identifier if it can be used, i.e. if all components are present
return Object.keys(identifier).length === {{ identifiers|length }} ? identifier : null;
}
{% endfor %}
{% endif %}
}

{% endfor -%}
Loading