-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a component to extract values from a remote file (#2554)
* 🔨 Adding a `<RemoteValue>` component to extract values from a remote file * 📝 refs #2554 Improving `<RemoteValue>` docs
- Loading branch information
1 parent
700911e
commit cbe4b02
Showing
5 changed files
with
213 additions
and
14 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
--- | ||
import parse, {Parser, SelectorFunction} from './parsers'; | ||
import Code from 'astro/components/Code.astro'; | ||
export {Parser} from './parsers'; | ||
/** | ||
* Component to fetch values from remote files, optionally passing a selector to extract a value from the file | ||
* (e.g. a JSON object). | ||
* | ||
* Import: | ||
* import RemoteValue from 'path/to/components/RemoteValue/RemoteValue.astro'; | ||
* | ||
* Usage syntax: | ||
* <RemoteValue url="https://remote.url.to.file" selector="selector.path" parser={one of Parser values} default="default value" /> | ||
* | ||
* Props: | ||
* url: remote URL to fetch the file from | ||
* parser: value from the Enum exported by RemoteValue.astro. If not defined, the component will use the file extension | ||
* from the URL. | ||
* selector: parsed-dependent syntax to extract the value from the remote file. It can also be a callback function that | ||
* will be called with the entire file as an argument and should return the value to extract. | ||
* E.g. for JSON, we use jsonpath-plus to provide an XPath-based syntax. | ||
* defaultValue: default value if we cannot retrieve the element (otherwise, we'll render "not available"). | ||
* This is recommended in case you ever change the remote file and the selector doesn't work anymore. | ||
* | ||
* These three examples return the same "clientSecret" element: | ||
* <RemoteValue url="https://some.github.repo/kickstart/kickstart.json" | ||
* selector="$.requests.4.body.application.oauthConfiguration.clientSecret" /> | ||
* <RemoteValue url="https://some.github.repo/kickstart/kickstart.json" | ||
* selector="$.requests.[?(@.url === '/api/application/#{applicationId}')].body.application.oauthConfiguration.clientSecret" /> | ||
* <RemoteValue url="https://some.github.repo/kickstart/kickstart.json" | ||
* selector={(element) => element.requests.find(e => e.url === '/api/application/#{applicationId}').body.application.oauthConfiguration.clientSecret} /> | ||
* | ||
* If you need to render the value inside a <Code> component, you need to pass a `codeRenderer` callback function: | ||
* <RemoteValue | ||
* url="https://some.github.repo/kickstart/kickstart.json" | ||
* selector="$.requests.4.body.application.oauthConfiguration.clientSecret" | ||
* codeRenderer={(value) => `OP_SECRET_KEY="${value}" bundle exec rails s`} /> | ||
* You can also pass a `codeLang` prop in case the <Code> element doesn't detect it automatically. | ||
*/ | ||
/** | ||
* Callback to be used to render <Code> elements | ||
*/ | ||
type CodeRendererFunc = (element: any) => string; | ||
/** | ||
* Available props. | ||
*/ | ||
export interface Props { | ||
/** | ||
* Url to fetch code from. | ||
*/ | ||
url: string; | ||
/** | ||
* Selector to extract the value from the content. | ||
* This is parser-specific but is normally some kind of XPath. | ||
* E.g. JSONPath syntax for JSON objects (https://www.npmjs.com/package/jsonpath-plus) | ||
*/ | ||
selector: string | SelectorFunction; | ||
/** | ||
* Optional parser name. If no one was informed, we'll use the file extension. | ||
*/ | ||
parser?: Parser; | ||
/** | ||
* Default value if there's an error retrieving file. | ||
*/ | ||
default?: string; | ||
/** | ||
* Callback to be used to render <Code> elements. | ||
*/ | ||
codeRenderer: CodeRendererFunc; | ||
/** | ||
* Optional `lang` attribute that will be passed to the <Code> component. | ||
*/ | ||
codeLang: string; | ||
} | ||
// Extracting props | ||
const {url, selector, parser, default: defaultValue, codeRenderer, codeLang} = Astro.props as Props; | ||
// Value that will be rendered | ||
let value; | ||
try { | ||
const response = await fetch(url); | ||
const content = await response.text(); | ||
// Using the selector to look up the value | ||
if ((content) && (selector)) { | ||
value = parse(url, content, selector, parser); | ||
} | ||
} catch (e) { | ||
console.error(`Error retrieving remote value from ${selector} at ${url}`, e); | ||
} | ||
// Default value | ||
if (!value) { | ||
value = defaultValue || "not available"; | ||
} | ||
--- | ||
{(codeRenderer) ? <Code code={codeRenderer(value)} lang={codeLang}/> : value} |
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,77 @@ | ||
import {JSONPath} from 'jsonpath-plus'; | ||
|
||
/** | ||
* Common cache object used by parsers. | ||
*/ | ||
const CACHE: { [key: string]: any } = {}; | ||
|
||
/** | ||
* Available values for the optional syntax prop. | ||
*/ | ||
export enum Parser { | ||
JSON = 'json', | ||
} | ||
|
||
/** | ||
* Custom function to look up the value. | ||
*/ | ||
export type SelectorFunction = (element: object) => string; | ||
|
||
|
||
/** | ||
* JSON Parser | ||
* | ||
* @param {String} url URL used as a key for the caching system | ||
* @param {String} code Actual code to be parsed | ||
* @param {String|SelectorFunction} selectorOrFunction A JSONPath selector or a custom function to look up the value. | ||
*/ | ||
function jsonParser(url: string, code: string, selectorOrFunction: string | SelectorFunction): string | null { | ||
// Caching JSON objects | ||
if (typeof CACHE[url] === 'undefined') { | ||
try { | ||
CACHE[url] = JSON.parse(code); | ||
} catch (err: any) { | ||
return null; | ||
} | ||
} | ||
|
||
// Strings are treated as JSONPath selector | ||
if (typeof selectorOrFunction === 'string') { | ||
const result = JSONPath({ | ||
path: selectorOrFunction, | ||
json: CACHE[url], | ||
}); | ||
return ((result) && (result[0])) ? result[0] : null; | ||
} | ||
|
||
// If this is a function, we call it passing the JSON object as argument | ||
return selectorOrFunction(CACHE[url]); | ||
} | ||
|
||
/** | ||
* Parsing content, optionally looking up the value via a selector | ||
* | ||
* @param {String} url URL used as a key for the caching system | ||
* @param {String} content Actual code to be parsed | ||
* @param {String|SelectorFunction} selector String or a custom function | ||
* @param {String|undefined} parser Optional parser (will detect from the filename extension) | ||
* @return {String|null} | ||
*/ | ||
export default function parse( | ||
url: string, | ||
content: string, | ||
selector: string | SelectorFunction, | ||
parser?: string | ||
): string | null { | ||
// If no parser was informed, we use the file extension | ||
if (!parser) { | ||
parser = url.substring(url.lastIndexOf('.') + 1); | ||
} | ||
|
||
switch (parser.toLowerCase()) { | ||
case Parser.JSON: | ||
return jsonParser(url, content, selector); | ||
} | ||
|
||
return null; | ||
} |
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