Skip to content

Commit

Permalink
feat!: app.parameters (#5915)
Browse files Browse the repository at this point in the history
## Summary
BREAKING CHANGE: `app.platformParameters` has been changed to `app.parameters`

This PR now allows Wing authors to access the application's parameters through `app.parameters` for example:
```js
let parameters = nodeof(this).app.parameters;
```
This also means that now wing structs can easily be added to platform parameters through the use of `parameters.addSchema(Struct.schema())` as well structs can be read from the parameter list through `Struct.fromJson(parameters.read())`


## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
hasanaburayyan authored Mar 25, 2024
1 parent dcd47d9 commit effc7ac
Show file tree
Hide file tree
Showing 41 changed files with 675 additions and 86 deletions.
13 changes: 13 additions & 0 deletions docs/docs/04-standard-library/std/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ prepended to the unique identifier.
| <code><a href="#@winglang/sdk.std.IApp.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
| <code><a href="#@winglang/sdk.std.IApp.property.entrypointDir">entrypointDir</a></code> | <code>str</code> | The directory of the entrypoint of the current program. |
| <code><a href="#@winglang/sdk.std.IApp.property.isTestEnvironment">isTestEnvironment</a></code> | <code>bool</code> | `true` if this is a testing environment. |
| <code><a href="#@winglang/sdk.std.IApp.property.parameters">parameters</a></code> | <code><a href="#@winglang/sdk.platform.ParameterRegistrar">ParameterRegistrar</a></code> | The application's parameter registrar. |
| <code><a href="#@winglang/sdk.std.IApp.property.workdir">workdir</a></code> | <code>str</code> | The `.wing` directory into which you can emit artifacts during preflight. |

---
Expand Down Expand Up @@ -680,6 +681,18 @@ isTestEnvironment: bool;

---

##### `parameters`<sup>Required</sup> <a name="parameters" id="@winglang/sdk.std.IApp.property.parameters"></a>

```wing
parameters: ParameterRegistrar;
```

- *Type:* <a href="#@winglang/sdk.platform.ParameterRegistrar">ParameterRegistrar</a>

The application's parameter registrar.

---

##### `workdir`<sup>Required</sup> <a name="workdir" id="@winglang/sdk.std.IApp.property.workdir"></a>

```wing
Expand Down
12 changes: 12 additions & 0 deletions examples/tests/invalid/parameters.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
let app = nodeof(this).app;

struct MyParams {
foo: str;
}

app.parameters.addSchema(MyParams.schema());

// Error: Parameter validation errors:
// - must have required property 'foo'

// (hint: make sure to use --values to provide the required parameters file)
18 changes: 18 additions & 0 deletions examples/tests/invalid/struct_from_parameter.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
struct MyParams {
foo: str;
bar: bool;
baz: num;
}


let app = nodeof(this).app;

// Never added the schema to the parameters, so
// technically we never enforced the parameters must have been
// provided

MyParams.fromJson(app.parameters.read());
//Error: unable to parse MyParams:
// - instance requires property "bar"
// - instance requires property "baz"
// - instance requires property "foo"
21 changes: 21 additions & 0 deletions examples/tests/valid/parameters/nested/parameters.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
struct Person {
name: str;
age: num;
}

struct House {
address: str;
residents: Array<Person>;
}

struct MyParams {
houses: Array<House>;
}

let app = nodeof(this).app;

let myParams = MyParams.fromJson(app.parameters.read(schema: MyParams.schema()));

assert(myParams.houses.length == 2);
assert(myParams.houses.at(0).address == "123 Main St");
assert(myParams.houses.at(0).residents.length == 2);
13 changes: 13 additions & 0 deletions examples/tests/valid/parameters/nested/wing.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[houses]]
address = "123 Main St"
residents = [
{name = "John Doe", age = 30},
{name = "Jane Doe", age = 24}
]

[[houses]]
address = "456 Elm St"
residents = [
{name = "Tom Smith", age = 45},
{name = "Sue Smith", age = 40}
]
18 changes: 18 additions & 0 deletions examples/tests/valid/parameters/simple/parameters.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
let app = nodeof(this).app;

struct MyParams {
foo: str?;
meaningOfLife: num;
}

let myParams = MyParams.fromJson(app.parameters.read(schema: MyParams.schema()));

if let foo = myParams.foo {
assert(false); // shouldnt happen
} else {
assert(true);
}

let meaningOfLife = myParams.meaningOfLife;

assert(meaningOfLife == 42);
1 change: 1 addition & 0 deletions examples/tests/valid/parameters/simple/wing.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
meaningOfLife = 42
2 changes: 1 addition & 1 deletion examples/tests/valid/struct_from_json.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ assert(myStruct.m2.val == "10");
let schema = MyStruct.schema();
schema.validate(jMyStruct); // Should not throw exception

let expectedSchema = {"id":"/MyStruct","type":"object","properties":{"m1":{"type":"object","properties":{"val":{"type":"number"}},"required":["val"]},"m2":{"type":"object","properties":{"val":{"type":"string"}},"required":["val"]}},"required":["m1","m2"]};
let expectedSchema = {"$id":"/MyStruct","type":"object","properties":{"m1":{"type":"object","properties":{"val":{"type":"number"}},"required":["val"]},"m2":{"type":"object","properties":{"val":{"type":"string"}},"required":["val"]}},"required":["m1","m2"]};

assert(schema.asStr() == Json.stringify(expectedSchema));

Expand Down
2 changes: 1 addition & 1 deletion libs/wingc/src/json_schema_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl JsonSchemaGenerator {
let mut code = CodeMaker::default();

code.open("{");
code.line(format!("id: \"/{}\",", struct_.name));
code.line(format!("$id: \"/{}\",", struct_.name));
code.line("type: \"object\",".to_string());

code.open("properties: {");
Expand Down
13 changes: 5 additions & 8 deletions libs/wingsdk/src/core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export abstract class App extends Construct implements IApp {
* Parameter registrar of composed platforms
* @internal
*/
protected _platformParameters?: ParameterRegistrar;
protected _parameters?: ParameterRegistrar;

constructor(scope: Construct, id: string, props: AppProps) {
super(scope, id);
Expand Down Expand Up @@ -187,14 +187,11 @@ export abstract class App extends Construct implements IApp {
* The parameter registrar for the app, can be used to find and register
* parameter values that were provided to the wing application.
*/
public get platformParameters() {
if (!this._platformParameters) {
this._platformParameters = new ParameterRegistrar(
this,
"ParameterRegistrar"
);
public get parameters() {
if (!this._parameters) {
this._parameters = new ParameterRegistrar(this, "ParameterRegistrar");
}
return this._platformParameters!;
return this._parameters!;
}

/**
Expand Down
55 changes: 42 additions & 13 deletions libs/wingsdk/src/platform/parameter-registrar.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import Ajv from "ajv";
import { Construct } from "constructs";
import { loadPlatformSpecificValues } from "./util";
import { Node } from "../std";
import {
loadPlatformSpecificValues,
extractFieldsFromSchema,
filterParametersBySchema,
} from "./util";
import { Json, Node } from "../std";

/**
* Options for reading parameters
*/
export interface ReadParameterOptions {
/** Schema to limit the read to */
readonly schema?: any;
}

/**
* Parameter Registrar
Expand Down Expand Up @@ -29,7 +41,7 @@ export class ParameterRegistrar extends Construct {
* @param path the path of the parameter
* @returns the value of the parameter
*/
public getParameterValue(path: string): any {
public value(path: string): any {
if (this.parameterValueByPath[path] === undefined) {
// attempt to read the value from the raw parameters, then cache it
this.parameterValueByPath[path] = resolveValueFromPath(
Expand All @@ -41,13 +53,36 @@ export class ParameterRegistrar extends Construct {
return this.parameterValueByPath[path];
}

/**
* Read parameters
*
* @param options options for reading parameters
* @returns the schema as a string
*/
public read(options?: ReadParameterOptions): Json {
if (options?.schema) {
this.addSchema(options.schema);
const fields = extractFieldsFromSchema(
options.schema._rawSchema // If a JsonSchema object is passed in, extract raw schema from it
? options.schema._rawSchema
: options.schema
);
return filterParametersBySchema(fields, this._rawParameters);
}
return this._rawParameters as Json;
}

/**
* Add parameter schema to registrar
*
* @param schema schema to add to the registrar
*/
public addParameterSchema(schema: any) {
this.parameterSchemas.push(schema);
public addSchema(schema: any) {
// If a JsonSchema object is passed in, extract the raw schema from it
const schemaToAdd = schema._rawSchema ? schema._rawSchema : schema;
if (!this.parameterSchemas.includes(schemaToAdd)) {
this.parameterSchemas.push(schemaToAdd);
}
}

/**
Expand All @@ -58,14 +93,8 @@ export class ParameterRegistrar extends Construct {
* @param path the path to nest the schema under
* @param recursiveRequire whether or not to require all the nested properties
*/
public addParameterSchemaAtPath(
schema: any,
path: string,
recursiveRequire = false
) {
this.addParameterSchema(
this._nestSchemaUnderPath(schema, path, recursiveRequire)
);
public addSchemaAtPath(schema: any, path: string, recursiveRequire = false) {
this.addSchema(this._nestSchemaUnderPath(schema, path, recursiveRequire));
}

/**
Expand Down
4 changes: 2 additions & 2 deletions libs/wingsdk/src/platform/platform-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ export class PlatformManager {
newInstanceOverrides,
}) as App;

let registrar = app.platformParameters;
let registrar = app.parameters;

parameterSchemas.forEach((schema) => {
registrar.addParameterSchema(schema);
registrar.addSchema(schema);
});

return app;
Expand Down
33 changes: 33 additions & 0 deletions libs/wingsdk/src/platform/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,39 @@ export function parseValuesObjectFromString(values: string) {
return result;
}

/**
* Extracts all fields from a JSON schema.
*
* @param schema the schema to extract fields from
* @returns a set of all fields in the schema
*/
export function extractFieldsFromSchema(schema: any): Set<string> {
const fields = new Set<string>();

if (schema.properties) {
for (const key of Object.keys(schema.properties)) {
fields.add(key);
}
}

return fields;
}

export function filterParametersBySchema(
fields: Set<string>,
parameters: any
): any {
const filtered: any = {};

for (const field of fields) {
if (parameters.hasOwnProperty(field)) {
filtered[field] = parameters[field];
}
}

return filtered;
}

/**
* Loads platform-specific values that were passed in via CLI arguments and
* from a values file. CLI arguments take precedence over values file.
Expand Down
14 changes: 5 additions & 9 deletions libs/wingsdk/src/shared-aws/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class Domain extends cloud.Domain {
constructor(scope: Construct, id: string, props: cloud.DomainProps) {
super(scope, id, props);

const parameters = App.of(scope).platformParameters;
const parameters = App.of(scope).parameters;

// Domain requires parameters from the user, so we need to add the parameter schemas to the registrar
let schema = {
Expand All @@ -44,17 +44,13 @@ export class Domain extends cloud.Domain {
},
};

parameters.addParameterSchemaAtPath(schema, this.node.path, true);
parameters.addSchemaAtPath(schema, this.node.path, true);

const iamCertificate = parameters.getParameterValue(
`${this.node.path}/iamCertificate`
);
const acmCertificateArn = parameters.getParameterValue(
const iamCertificate = parameters.value(`${this.node.path}/iamCertificate`);
const acmCertificateArn = parameters.value(
`${this.node.path}/acmCertificateArn`
);
const hostedZoneId = parameters.getParameterValue(
`${this.node.path}/hostedZoneId`
);
const hostedZoneId = parameters.value(`${this.node.path}/hostedZoneId`);

this._iamCertificate = iamCertificate;
this._hostedZoneId = hostedZoneId;
Expand Down
2 changes: 1 addition & 1 deletion libs/wingsdk/src/std/bool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Boolean {
*/
public static fromJson(json: Json, options?: JsonValidationOptions): boolean {
const schema = JsonSchema._createJsonSchema({
id: "bool",
$id: "bool",
type: "boolean",
} as any);
schema.validate(json, options);
Expand Down
Loading

0 comments on commit effc7ac

Please sign in to comment.