Skip to content

Commit

Permalink
feat: add support for yaml.unmarshal builtin
Browse files Browse the repository at this point in the history
This uses the "yaml" npm package to provide support for unmarshalling
YAML strings within rego via the yaml.unmarshal() function.

Test functionality has been included to verify parsing and handling of
syntax, metadata, reference and warnings to match the current behavior
of the opa tool.
  • Loading branch information
aron committed Dec 9, 2021
1 parent 978ae4c commit 04e1e5b
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 2 deletions.
16 changes: 15 additions & 1 deletion 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 @@ -34,6 +34,7 @@
"typescript": "^4.4.3"
},
"dependencies": {
"sprintf-js": "^1.1.2"
"sprintf-js": "^1.1.2",
"yaml": "^1.10.2"
}
}
2 changes: 2 additions & 0 deletions src/builtins/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const strings = require("./strings");
const regex = require("./regex");
const yaml = require("./yaml");

module.exports = {
...strings,
...regex,
...yaml,
};
28 changes: 28 additions & 0 deletions src/builtins/yaml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const yaml = require("yaml");

// see: https://eemeli.org/yaml/v1/#errors
const errors = new Set([
"YAMLReferenceError",
"YAMLSemanticError",
"YAMLSyntaxError",
"YAMLWarning",
]);

const unmarshal = function (str) {
const YAML_SILENCE_WARNINGS_CACHED = global.YAML_SILENCE_WARNINGS;
try {
// see: https://eemeli.org/yaml/v1/#silencing-warnings
global.YAML_SILENCE_WARNINGS = true;
return yaml.parse(str);
} catch (err) {
// Ignore parser errors.
if (err && errors.has(err.name)) {
return false;
}
throw err;
} finally {
global.YAML_SILENCE_WARNINGS = YAML_SILENCE_WARNINGS_CACHED;
}
};

module.exports = { "yaml.unmarshal": unmarshal };
2 changes: 2 additions & 0 deletions test/fixtures/yaml-support/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bundle.tar.gz
policy.wasm
50 changes: 50 additions & 0 deletions test/fixtures/yaml-support/yaml-support-policy.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package yaml.support

fixture := `
---
openapi: "3.0.1"
info:
title: test
paths:
/path1:
get:
x-amazon-apigateway-integration:
type: "mock"
httpMethod: "GET"
x-amazon-apigateway-policy:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: "*"
Action:
- 'execute-api:Invoke'
Resource: '*'
`

default canParseYaml = false

canParseYAML {
resource := yaml.unmarshal(fixture)
resource.info.title == "test"
}

hasSemanticError {
# see: https://github.com/eemeli/yaml/blob/395f892ec9a26b9038c8db388b675c3281ab8cd3/tests/doc/errors.js#L22
yaml.unmarshal("a:\n\t1\nb:\n\t2\n")
}

hasSyntaxError {
# see: https://github.com/eemeli/yaml/blob/395f892ec9a26b9038c8db388b675c3281ab8cd3/tests/doc/errors.js#L49
yaml.unmarshal("{ , }\n---\n{ 123,,, }\n")
}

hasReferenceError {
# see: https://github.com/eemeli/yaml/blob/395f892ec9a26b9038c8db388b675c3281ab8cd3/tests/doc/errors.js#L245
yaml.unmarshal("{ , }\n---\n{ 123,,, }\n")
}

hasYAMLWarning {
# see: https://github.com/eemeli/yaml/blob/395f892ec9a26b9038c8db388b675c3281ab8cd3/tests/doc/errors.js#L224
yaml.unmarshal("%FOO\n---bar\n")
}
78 changes: 78 additions & 0 deletions test/opa-yaml-support.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const { readFileSync } = require("fs");
const { execFileSync } = require("child_process");
const { loadPolicy } = require("../src/opa.js");

describe("yaml.unmarshal() support", () => {
const fixturesFolder = "test/fixtures/yaml-support";

let policy;

beforeAll(async () => {
const bundlePath = `${fixturesFolder}/bundle.tar.gz`;

execFileSync("opa", [
"build",
fixturesFolder,
"-o",
bundlePath,
"-t",
"wasm",
"-e",
"yaml/support/canParseYAML",
"-e",
"yaml/support/hasSyntaxError",
"-e",
"yaml/support/hasSemanticError",
"-e",
"yaml/support/hasReferenceError",
"-e",
"yaml/support/hasYAMLWarning",
]);

execFileSync("tar", [
"-xzf",
bundlePath,
"-C",
`${fixturesFolder}/`,
"/policy.wasm",
]);

const policyWasm = readFileSync(`${fixturesFolder}/policy.wasm`);
const opts = { initial: 5, maximum: 10 };
policy = await loadPolicy(policyWasm, opts);
});

it("should unmarshall YAML strings", () => {
const result = policy.evaluate({}, "yaml/support/canParseYAML");
expect(result.length).not.toBe(0);
expect(result[0]).toMatchObject({ result: true });
});

it("should ignore YAML syntax errors", () => {
expect(() => policy.evaluate({}, "yaml/support/hasSyntaxError")).not
.toThrow();
const result = policy.evaluate({}, "yaml/support/hasSyntaxError");
expect(result.length).toBe(0);
});

it("should ignore YAML semantic errors", () => {
expect(() => policy.evaluate({}, "yaml/support/hasSemanticError")).not
.toThrow();
const result = policy.evaluate({}, "yaml/support/hasSemanticError");
expect(result.length).toBe(0);
});

it("should ignore YAML reference errors", () => {
expect(() => policy.evaluate({}, "yaml/support/hasReferenceError")).not
.toThrow();
const result = policy.evaluate({}, "yaml/support/hasReferenceError");
expect(result.length).toBe(0);
});

it("should ignore YAML warnings", () => {
expect(() => policy.evaluate({}, "yaml/support/hasYAMLWarning")).not
.toThrow();
const result = policy.evaluate({}, "yaml/support/hasYAMLWarning");
expect(result.length).toBe(0);
});
});

0 comments on commit 04e1e5b

Please sign in to comment.