Skip to content

Commit

Permalink
fix(env-options)!: env-options harmony
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Sep 26, 2023
1 parent aebbea2 commit 7799061
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 31 deletions.
16 changes: 10 additions & 6 deletions packages/env-options/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,28 @@ optionally holding a property with the same name as the option,
whose value is the configuration setting of that option.

```js
import { makeEnvironmentCaptor } from '@endo/env-options';
const { getEnvironmentOption } = makeEnvironmentCaptor(globalThis);
import { getEnvironmentOption } from '@endo/env-options';
const FooBarOption = getEnvironmentOption('FOO_BAR', 'absent');
```

The first argument to `getEnvironmentOption` is the name of the option.
The value of `FooBarOption` would then be the value of
`globalThis.process.env.FOO_BAR`, if present.
If setting is either absent or `undefined`, the default `'absent'`
would be used instead.
If value is either absent or `undefined`, the second argument,
such as `'absent'`, would be used instead.

In either case, reflecting Unix environment variable expectations,
the resulting setting must be a string.
This restriction also helps ensure that this channel is used only to pass data,
not authority beyond the ability to read this global state.

The `makeEnvironmentCaptor` function also returns a
`getCapturedEnvironmentOptionNames` function for use to give feedback about
The `'@endo/env-options'` module also exports a lower-level
`makeEnvironmentCaptor` that you can apply to whatever object you wish to treat
as a global, such as the global of another compartment. It returns an entagled
pair of a `getEnvironmentOption` function as above, and a
`getCapturedEnvironmentOptionNames` function for that returns an array of
the option names used by that `getEnvironmentOption` function. This is
useful to give feedback about
which environment variables were actually read, for diagnostic purposes.
For example, the
ses-shim `lockdown` once contained code such as the following, to explain which
Expand Down
52 changes: 47 additions & 5 deletions packages/env-options/src/env-options.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global globalThis */
// @ts-check

// `@endo/env-options` needs to be imported quite early, and so should
Expand All @@ -17,6 +18,8 @@ const uncurryThis =
(receiver, ...args) =>
apply(fn, receiver, args);
const arrayPush = uncurryThis(Array.prototype.push);
const arrayIncludes = uncurryThis(Array.prototype.includes);
const stringSplit = uncurryThis(String.prototype.split);

const q = JSON.stringify;

Expand Down Expand Up @@ -47,13 +50,16 @@ export const makeEnvironmentCaptor = aGlobal => {
*
* @param {string} optionName
* @param {string} defaultSetting
* @param {string[]} [optOtherNames]
* @returns {string}
*/
const getEnvironmentOption = (optionName, defaultSetting) => {
// eslint-disable-next-line @endo/no-polymorphic-call
const getEnvironmentOption = (
optionName,
defaultSetting,
optOtherNames = undefined,
) => {
typeof optionName === 'string' ||
Fail`Environment option name ${q(optionName)} must be a string.`;
// eslint-disable-next-line @endo/no-polymorphic-call
typeof defaultSetting === 'string' ||
Fail`Environment option default setting ${q(
defaultSetting,
Expand All @@ -68,7 +74,6 @@ export const makeEnvironmentCaptor = aGlobal => {
if (optionName in globalEnv) {
arrayPush(capturedEnvironmentOptionNames, optionName);
const optionValue = globalEnv[optionName];
// eslint-disable-next-line @endo/no-polymorphic-call
typeof optionValue === 'string' ||
Fail`Environment option named ${q(
optionName,
Expand All @@ -79,15 +84,52 @@ export const makeEnvironmentCaptor = aGlobal => {
}
}
}
optOtherNames === undefined ||
setting === defaultSetting ||
arrayIncludes(optOtherNames, setting) ||
Fail`Unrecognized ${q(optionName)} value ${q(
setting,
)}. Expected one of ${q([defaultSetting, ...optOtherNames])}`;
return setting;
};
freeze(getEnvironmentOption);

/**
* @param {string} optionName
* @returns {string[]}
*/
const getEnvironmentOptionsList = optionName => {
const option = getEnvironmentOption(optionName, '');
return option === '' ? [] : stringSplit(option, ',');
};
freeze(getEnvironmentOptionsList);

const environmentOptionsListHas = (optionName, element) =>
arrayIncludes(getEnvironmentOptionsList(optionName), element);

const getCapturedEnvironmentOptionNames = () => {
return freeze([...capturedEnvironmentOptionNames]);
};
freeze(getCapturedEnvironmentOptionNames);

return freeze({ getEnvironmentOption, getCapturedEnvironmentOptionNames });
return freeze({
getEnvironmentOption,
getEnvironmentOptionsList,
environmentOptionsListHas,
getCapturedEnvironmentOptionNames,
});
};
freeze(makeEnvironmentCaptor);

/**
* For the simple case, where the global in question is `globalThis` and no
* reporting of option names is desired.
*
* TODO For this simple case, we should not track the
* `capturedEnvironmentOptionNames` since no one can observe them.
*/
export const {
getEnvironmentOption,
getEnvironmentOptionsList,
environmentOptionsListHas,
} = makeEnvironmentCaptor(globalThis);
18 changes: 7 additions & 11 deletions packages/eventual-send/src/track-turns.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* global globalThis */
import { makeEnvironmentCaptor } from '@endo/env-options';

const { getEnvironmentOption } = makeEnvironmentCaptor(globalThis);
import {
environmentOptionsListHas,
getEnvironmentOption,
} from '@endo/env-options';

// NOTE: We can't import these because they're not in scope before lockdown.
// import { assert, details as X, Fail } from '@agoric/assert';
Expand All @@ -17,18 +18,13 @@ let hiddenPriorError;
let hiddenCurrentTurn = 0;
let hiddenCurrentEvent = 0;

const DEBUG = getEnvironmentOption('DEBUG', '');

// Turn on if you seem to be losing error logging at the top of the event loop
const VERBOSE = DEBUG.split(':').includes('track-turns');
const VERBOSE = environmentOptionsListHas('DEBUG', 'track-turns');

// Track-turns is disabled by default and can be enabled by an environment
// option.
const TRACK_TURNS = getEnvironmentOption('TRACK_TURNS', 'disabled');
if (TRACK_TURNS !== 'enabled' && TRACK_TURNS !== 'disabled') {
throw TypeError(`unrecognized TRACK_TURNS ${JSON.stringify(TRACK_TURNS)}`);
}
const ENABLED = (TRACK_TURNS || 'disabled') === 'enabled';
const ENABLED =
getEnvironmentOption('TRACK_TURNS', 'disabled', ['enabled']) === 'enabled';

// We hoist the following functions out of trackTurns() to discourage the
// closures from holding onto 'args' or 'func' longer than necessary,
Expand Down
8 changes: 2 additions & 6 deletions packages/exo/src/exo-makers.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
/* global globalThis */
/// <reference types="ses"/>
import { makeEnvironmentCaptor } from '@endo/env-options';
import { environmentOptionsListHas } from '@endo/env-options';
import { objectMap } from '@endo/patterns';

import { defendPrototype, defendPrototypeKit } from './exo-tools.js';

const { create, seal, freeze, defineProperty, values } = Object;

const { getEnvironmentOption } = makeEnvironmentCaptor(globalThis);
const DEBUG = getEnvironmentOption('DEBUG', '');

// Turn on to give each exo instance its own toStringTag value.
const LABEL_INSTANCES = DEBUG.split(',').includes('label-instances');
const LABEL_INSTANCES = environmentOptionsListHas('DEBUG', 'label-instances');

const makeSelf = (proto, instanceCount) => {
const self = create(proto);
Expand Down
4 changes: 1 addition & 3 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// @ts-check

import { makeEnvironmentCaptor } from '@endo/env-options';
import { getEnvironmentOption as getenv } from '@endo/env-options';
import {
FERAL_FUNCTION,
FERAL_EVAL,
Expand Down Expand Up @@ -154,8 +154,6 @@ export const repairIntrinsics = (options = {}) => {
// [`stackFiltering` options](https://github.com/Agoric/SES-shim/blob/master/packages/ses/docs/lockdown.md#stackfiltering-options)
// for an explanation.

const { getEnvironmentOption: getenv } = makeEnvironmentCaptor(globalThis);

const {
errorTaming = getenv('LOCKDOWN_ERROR_TAMING', 'safe'),
errorTrapping = getenv('LOCKDOWN_ERROR_TRAPPING', 'platform'),
Expand Down

0 comments on commit 7799061

Please sign in to comment.