-
Notifications
You must be signed in to change notification settings - Fork 168
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🪚 OmniGraph™ Reusable config loading (#127)
- Loading branch information
1 parent
f202724
commit 4d32e7b
Showing
7 changed files
with
148 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './loading' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { importDefault, isFile, isReadable } from '@/filesystem/filesystem' | ||
import { createModuleLogger } from '@/stdio/logger' | ||
import { printZodErrors } from '@/stdio/printer' | ||
import { z } from 'zod' | ||
|
||
export const createConfigLoader = | ||
<TConfig>(schema: z.ZodSchema<TConfig>, logger = createModuleLogger('config loader')) => | ||
async (path: string): Promise<TConfig> => { | ||
// First we check that the config file is indeed there and we can read it | ||
logger.verbose(`Checking config file '${path}' for existence & readability`) | ||
const isConfigReadable = isFile(path) && isReadable(path) | ||
if (!isConfigReadable) { | ||
throw new Error( | ||
`Unable to read config file '${path}'. Check that the file exists and is readable to your terminal user` | ||
) | ||
} | ||
|
||
// Keep talking to the user | ||
logger.verbose(`Config file '${path}' exists & is readable`) | ||
|
||
// Now let's see if we can load the config file | ||
let rawConfig: unknown | ||
try { | ||
logger.verbose(`Loading config file '${path}'`) | ||
|
||
rawConfig = await importDefault(path) | ||
} catch (error) { | ||
throw new Error(`Unable to read config file '${path}': ${error}`) | ||
} | ||
|
||
logger.verbose(`Loaded config file '${path}'`) | ||
|
||
// It's time to make sure that the config is not malformed | ||
// | ||
// At this stage we are only interested in the shape of the data, | ||
// we are not checking whether the information makes sense (e.g. | ||
// whether there are no missing nodes etc) | ||
logger.verbose(`Validating the structure of config file '${path}'`) | ||
const configParseResult = schema.safeParse(rawConfig) | ||
if (configParseResult.success === false) { | ||
const userFriendlyErrors = printZodErrors(configParseResult.error) | ||
|
||
throw new Error( | ||
`Config from file '${path}' is malformed. Please fix the following errors:\n\n${userFriendlyErrors}` | ||
) | ||
} | ||
|
||
return configParseResult.data | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './config' | ||
export * from './filesystem' | ||
export * from './language' | ||
export * from './stdio' |
19 changes: 19 additions & 0 deletions
19
packages/io-utils/test/config/__snapshots__/loading.test.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`config/loading createConfigLoader should reject if the file cannot be imported 1`] = `[Error: Unable to read config file './myconfig.ts': No way]`; | ||
|
||
exports[`config/loading createConfigLoader should reject if the file contents do not match the schema 1`] = ` | ||
[Error: Config from file './myconfig.ts' is malformed. Please fix the following errors: | ||
Property 'good': Required] | ||
`; | ||
|
||
exports[`config/loading createConfigLoader should reject if the path is not a file 1`] = `[Error: Unable to read config file './myconfig.ts'. Check that the file exists and is readable to your terminal user]`; | ||
|
||
exports[`config/loading createConfigLoader should reject if the path is not readable 1`] = `[Error: Unable to read config file './myconfig.ts'. Check that the file exists and is readable to your terminal user]`; | ||
|
||
exports[`config/loading createConfigLoader should resolve if the file contents match the schema 1`] = ` | ||
{ | ||
"good": "config", | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { createConfigLoader } from '@/config' | ||
import { importDefault, isFile, isReadable } from '@/filesystem/filesystem' | ||
import { z } from 'zod' | ||
|
||
jest.mock('@/filesystem/filesystem', () => ({ | ||
isFile: jest.fn().mockReturnValue(false), | ||
isReadable: jest.fn().mockReturnValue(false), | ||
importDefault: jest.fn().mockRejectedValue('Not mocked'), | ||
})) | ||
|
||
const isFileMock = isFile as jest.Mock | ||
const importDefaultMock = importDefault as jest.Mock | ||
const isReadableMock = isReadable as jest.Mock | ||
|
||
describe('config/loading', () => { | ||
beforeEach(() => { | ||
isFileMock.mockReset() | ||
importDefaultMock.mockReset() | ||
isReadableMock.mockReset() | ||
}) | ||
|
||
describe('createConfigLoader', () => { | ||
it('should reject if the path is not a file', async () => { | ||
isFileMock.mockReturnValue(false) | ||
|
||
const configLoader = createConfigLoader(z.unknown()) | ||
|
||
await expect(configLoader('./myconfig.ts')).rejects.toMatchSnapshot() | ||
}) | ||
|
||
it('should reject if the path is not readable', async () => { | ||
isFileMock.mockReturnValue(true) | ||
isReadableMock.mockReturnValue(false) | ||
|
||
const configLoader = createConfigLoader(z.unknown()) | ||
|
||
await expect(configLoader('./myconfig.ts')).rejects.toMatchSnapshot() | ||
}) | ||
|
||
it('should reject if the file cannot be imported', async () => { | ||
isFileMock.mockReturnValue(true) | ||
isReadableMock.mockReturnValue(true) | ||
importDefaultMock.mockRejectedValue('No way') | ||
|
||
const configLoader = createConfigLoader(z.unknown()) | ||
|
||
await expect(configLoader('./myconfig.ts')).rejects.toMatchSnapshot() | ||
}) | ||
|
||
it('should reject if the file contents do not match the schema', async () => { | ||
isFileMock.mockReturnValue(true) | ||
isReadableMock.mockReturnValue(true) | ||
importDefaultMock.mockResolvedValue({ bad: 'config' }) | ||
|
||
const schema = z.object({ good: z.string() }) | ||
const configLoader = createConfigLoader(schema) | ||
|
||
await expect(configLoader('./myconfig.ts')).rejects.toMatchSnapshot() | ||
}) | ||
|
||
it('should resolve if the file contents match the schema', async () => { | ||
isFileMock.mockReturnValue(true) | ||
isReadableMock.mockReturnValue(true) | ||
importDefaultMock.mockResolvedValue({ good: 'config' }) | ||
|
||
const schema = z.object({ good: z.string() }) | ||
const configLoader = createConfigLoader(schema) | ||
|
||
await expect(configLoader('./myconfig.ts')).resolves.toMatchSnapshot() | ||
}) | ||
}) | ||
}) |
2 changes: 1 addition & 1 deletion
2
packages/ua-utils-evm-hardhat-test/test/task/oapp/__snapshots__/wire.test.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters