robb-j / gruber

(WIP) A standards-based JavaScript server library & patterns
0 stars 0 forks source link

Rework `Configuration.spec` #27

Open robb-j opened 2 months ago

robb-j commented 2 months ago

My initial thought is to store a single function at Configuration.describe, this is more like the other JavaScript protocols/interfaces and the interface is only a method now

robb-j commented 2 months ago

Another alternative could be to inferred from the JSON schema instead, maybe there could be Configuration.schema which is the key to store a JSON schema on a value to get automatic documentation?

This would be more standards-inline and different JSON schema features could be implemented as needed.

robb-j commented 2 months ago

An example of storing meta information in the spec:

/**
 * What if the meta information was stored in the JSON schema under $gruber ?
 *
 * @param {Record<string,unknown>} schema
 * @returns {import("./configuration.js").ConfigurationDescription | null}
 */
function _specify(schema, path = []) {
    if (typeof schema !== "object") return null;

    // Object
    if (schema.type === "object") {
        const fallback = {};
        const fields = [];
        for (const [field, subSchema] of Object.entries(schema.properties)) {
            const subSpec = _specify(
                subSchema,
                path.length ? path.concat("." + field) : [field],
            );
            if (!subSpec) continue;
            fallback[field] = subSpec.fallback;
            fields.push(...subSpec.fields);
        }
        return { fallback, fields };
    }

    // Array
    if (schema.type === "array") {
        const subSpec = _specify(schema.items, path.concat("[]"));
        return {
            fallback: [],
            fields: subSpec ? subSpec.fields : [],
        };
    }

    // URL
    if (schema.type === "string" && schema.format === "uri") {
        return {
            fallback: schema.default,
            fields: [_degruber(path.join(""), "url", schema)],
        };
    }

    // string
    if (schema.type === "string" && schema.format !== "uri") {
        return {
            fallback: schema.default,
            fields: [_degruber(path.join(""), "string", schema)],
        };
    }

    // number
    if (schema.type === "number") {
        return {
            fallback: schema.default,
            fields: [_degruber(path.join(""), "number", schema)],
        };
    }

    // boolean
    if (schema.type === "boolean") {
        return {
            fallback: schema.default,
            fields: [_degruber(path.join(""), "boolean", schema)],
        };
    }

    return null;
}

function _degruber(name, type, schema) {
    return {
        name,
        type,
        ...(schema.$gruber ?? {}),
        fallback: schema.default?.toString(),
    };
}

console.log(
    _specify({
        type: "object",
        properties: {
            name: {
                type: "string",
                default: "Geoff Testington",
                $gruber: { flag: "--name", variable: "FULL_NAME" },
            },
            age: {
                type: "number",
                default: 42,
                $gruber: { flag: "--age", variable: "AGE" },
            },
            website: {
                type: "string",
                format: "uri",
                default: "https://example.com",
                $gruber: { flag: "--website", variable: "WEBSITE_URL" },
            },
            pets: {
                type: "array",
                default: [],
                items: {
                    type: "string",
                },
            },
        },
    }),
);

outputs:

{
  fallback: {
    name: 'Geoff Testington',
    age: 42,
    website: 'https://example.com',
    pets: []
  },
  fields: [
    {
      name: 'name',
      type: 'string',
      flag: '--name',
      variable: 'FULL_NAME',
      fallback: 'Geoff Testington'
    },
    {
      name: 'age',
      type: 'number',
      flag: '--age',
      variable: 'AGE',
      fallback: '42'
    },
    {
      name: 'website',
      type: 'url',
      flag: '--website',
      variable: 'WEBSITE_URL',
      fallback: 'https://example.com'
    },
    { name: 'pets[]', type: 'string', fallback: undefined }
  ]
}