diff --git a/angular.json b/angular.json index 56a153c..6014380 100644 --- a/angular.json +++ b/angular.json @@ -153,7 +153,7 @@ "demos/people-service/src/assets" ], "styles": [ - "demos/people-service/src/styles.css" + "demos/people-service/src/styles.css", "node_modules/primeng/resources/primeng.css", "node_modules/primeicons/primeicons.css", "node_modules/primeflex/primeflex.css", diff --git a/libs/oarng/src/lib/config/config.service.spec.ts b/libs/oarng/src/lib/config/config.service.spec.ts index a59506f..33bd277 100644 --- a/libs/oarng/src/lib/config/config.service.spec.ts +++ b/libs/oarng/src/lib/config/config.service.spec.ts @@ -14,6 +14,9 @@ describe('ConfigurationService', () => { // Mock configuration object const mockConfig: Configuration = { PDRDMP: 'https://oardev.nist.gov/od/ds/rpa', + daptool: { + editEnabled: true + } }; beforeEach(() => { @@ -68,6 +71,14 @@ describe('ConfigurationService', () => { expect(actualConfig['PDRDMP']).toEqual(mockConfig['PDRDMP']); }); + it('should return configuration parameter', () => { + service.config = mockConfig; + expect(service.get('PDRDMP', 'unconfigured')).toEqual(mockConfig['PDRDMP']); + expect(service.get('daptool', 'unconfigured')).toEqual(mockConfig['daptool']); + expect(service.get('daptool.editEnabled')).toBe(true); + expect(service.get('daptool.endpoint', 'unconfigured')).toBe('unconfigured'); + }); + }) diff --git a/libs/oarng/src/lib/config/config.service.ts b/libs/oarng/src/lib/config/config.service.ts index 12e28bf..77b26d4 100644 --- a/libs/oarng/src/lib/config/config.service.ts +++ b/libs/oarng/src/lib/config/config.service.ts @@ -5,6 +5,7 @@ import { Observable, Subject, throwError } from "rxjs"; import { catchError, tap } from "rxjs/operators"; import { Configuration, ReleaseInfo, RELEASE_INFO, CONFIG_URL } from "./config.model"; +import { NamedParameters, AnyObj } from "../data/namedparams"; import { environment } from "../../environments/environment"; /** @@ -36,7 +37,7 @@ export class ConfigurationService { this.config.componentRelease = this.relInfo let r: ReleaseInfo|undefined = this.config.componentRelease; - let msg = "app configuration loaded for "; + let msg = "app configuration loaded"; if (r) msg += " for "+r.component+", version "+r.version console.log(msg); } @@ -71,6 +72,26 @@ export class ConfigurationService { return (this.config ?? { }) as T; } + /** + * extract a named parameter from the (already loaded) configuration data using its + * hierarchical (dot-delimited) name. + * + * This function accomplishes two things: first, it provides a bit of syntactic + * sugar for getting at deep values in the parameter hierarchy. That is, + * `cfg.get("links.orgHome")` is equivalent to `cfg.getConfig()["links"]["orgHome"]`. + * + * The second bit of functionality is the optional parameter that allows the caller + * to set the default value to return if the value is not set. If the stored value + * is null or undefined, the default value is returned. + * + * @param param the name of the desired parameter + * @param default the default value to return if a parameter with the given name + * is not set or is null. + */ + get(param: string, defval?: T | null): T | null | undefined { + return (new NamedParameters(this.getConfig())).get(param, defval); + } + /** * Handle the HTTP errors. * @param error The error object. diff --git a/libs/oarng/src/lib/data/data.module.ts b/libs/oarng/src/lib/data/data.module.ts new file mode 100644 index 0000000..667a647 --- /dev/null +++ b/libs/oarng/src/lib/data/data.module.ts @@ -0,0 +1,12 @@ +/** + * Utilities for managing various data structures + */ +import { NgModule } from '@angular/core'; + +import { NamedParameters } from './namedparams'; +import { AnyObj } from './types'; + +@NgModule({}) +export class DataModule { } + +export { NamedParameters, AnyObj } diff --git a/libs/oarng/src/lib/data/namedparams.spec.ts b/libs/oarng/src/lib/data/namedparams.spec.ts new file mode 100644 index 0000000..96fda28 --- /dev/null +++ b/libs/oarng/src/lib/data/namedparams.spec.ts @@ -0,0 +1,45 @@ +import { NamedParameters, AnyObj } from "./namedparams"; + +describe("NamedParameters", function() { + let data : AnyObj = { + "links": { + orgHome: "https://data.nist.gov/", + rabbitHoles: { + endless: "/endless", + deadend: "/endless/deadend" + } + } + }; + + it("get()", function() { + let p: NamedParameters = new NamedParameters(data); + debugger; + expect(p.data).toBe(data); + expect(p.get("links", "unconfigured")).toBe(data["links"]); + expect(p.get("apis", "unconfigured")).toBe("unconfigured"); + expect(p.get("apis")).toBe(undefined); + expect(p.get("links.orgHome", "unconfigured")).toBe("https://data.nist.gov/"); + expect(p.get("links.rabbitHoles.deadend", "unconfigured")).toBe("/endless/deadend"); + expect(p.get("links.rabbitHoles.conspiracy", "unconfigured")).toBe("unconfigured"); + }); + + it("construction-time defaults", function() { + let defs = { + links: { + orgHome: "https://google.com", + portalHome: "/sdp", + rabbitHoles: { } + }, + apis: false + } + let p: NamedParameters = new NamedParameters(data, defs); + expect(p.data).toBe(data); + expect(p.get("links", "unconfigured")).toBe(data["links"]); + expect(p.get("apis", "unconfigured")).toBe("unconfigured"); + expect(p.get("apis")).toBe(false); + expect(p.get("links.orgHome", "unconfigured")).toBe("https://data.nist.gov/"); + expect(p.get("links.portalHome")).toBe("/sdp"); + expect(p.get("links.rabbitHoles.deadend", "unconfigured")).toBe("/endless/deadend"); + expect(p.get("links.rabbitHoles.conspiracy", "unconfigured")).toBe("unconfigured"); + }); +}); diff --git a/libs/oarng/src/lib/data/namedparams.ts b/libs/oarng/src/lib/data/namedparams.ts new file mode 100644 index 0000000..3125a5d --- /dev/null +++ b/libs/oarng/src/lib/data/namedparams.ts @@ -0,0 +1,63 @@ +import { AnyObj } from './types'; + +/** + * A wrapper class around data stored in a hierarchical object that eases access. It provides two + * key features: + * * a get() function for accessing items deep in the hierarcy via dot-delimited name + * * the ability to specify a default if the parameter is not set. + */ +export class NamedParameters { + + data: AnyObj; + _defs: NamedParameters|null = null; + + /** + * wrap a data object + * @param params the data object to wrap + * @param defParams a data object that provides default values to return if a parameter + * is not set in params _and_ a default is not provided to the get() + * function. + */ + constructor(params: AnyObj, defParams?: AnyObj) { + this.data = params; + if (defParams) + this._defs = new NamedParameters(defParams); + } + + /** + * get hierarchical values by name with an option to request a default value. + * + * This function accomplishes two things: first, it provides a bit of syntactic + * sugar for getting at deep values in the parameter hierarchy. That is, + * `params.get("links.orgHome")` is equivalent to `params.data["links"]["orgHome"]`. + * + * The second bit of functionality is the optional parameter that allows the caller + * to set the default value to return if the value is not set. If the stored value + * is null or undefined, the default value is returned. + * + * @param param the name of the desired parameter + * @param default the default value to return if a parameter with the given name + * is not set or is null. This overrides any default set at + * construction time. Do not set or set to undefined to explicity + * defer to a construction-time default. + */ + get(param: string, defval?: T | null): T | null | undefined { + let names: string[] = param.split("."); + let val: any = this.data; + for (var i = 0; i < names.length; i++) { + if (typeof val != "object") { + val = null; + break; + } + val = val[names[i]]; + } + if (val == null || val == undefined) { + if (defval == undefined && this._defs) + defval = this._defs.get(param); + val = defval; + } + return val; + } +} + +export { AnyObj } diff --git a/libs/oarng/src/lib/data/types.ts b/libs/oarng/src/lib/data/types.ts new file mode 100644 index 0000000..d043a5a --- /dev/null +++ b/libs/oarng/src/lib/data/types.ts @@ -0,0 +1,9 @@ +/** + * exportable type definitions + */ + +/** + * an object that can be empty or contain parameters of any names and types + */ +export type AnyObj = { [paramName: string]: any } + diff --git a/libs/oarng/src/public-api.ts b/libs/oarng/src/public-api.ts index fb0e5a1..dd84c43 100644 --- a/libs/oarng/src/public-api.ts +++ b/libs/oarng/src/public-api.ts @@ -18,3 +18,4 @@ export * from './lib/auth/auth'; export * from './lib/config/config.module'; export * from './lib/auth/auth.module'; export * from './lib/staffdir/staffdir.module'; +export * from './lib/data/data.module';