joi icon indicating copy to clipboard operation
joi copied to clipboard

Wrapping of nested schema properties

Open matthieusieben opened this issue 3 years ago • 6 comments

Support plan

  • is this issue currently blocking your project? (yes/no): no
  • is this issue affecting a production system? (yes/no): no

Context

  • node version: n.a
  • module version: 17.4.0
  • environment (e.g. node, browser, native): node
  • used with (e.g. hapi application, another framework, standalone, ...): hapi
  • any other relevant information:

How can we help?

Let's say I am using a library that performs Joi Schema validation:

 const configSchema = Joi.object().keys({
  foo: Joi.object().keys({
    bar: Joi.number().min(0)
  })
})

Now let's pretend that I want to be able to write a "dynamic" (interpreted) configuration using environement variables:

const config = {
  foo: {
   bar: "{ env.FOO_BAR }"
  }
}

How should I write a "schema transform" function that will transform the initial configSchema into something like:

 const configSchemaWithEnv = Joi.object().keys({
  foo: Joi.object().keys({
    bar:  Joi.alternatives(
      Joi.number().min(0), // Initial value
      Joi.string().regexp(ENV_VAR_REXEXP)
    )
  })
})

(of course any input would need to be interpreted before being actually used, but that's not the point)

In other words, how can I write a addEnvVarSupportToJoiSchema that transforms a basic schema by wrapping every/some parts of it ?

const addEnvVarSupportToJoiSchema = (schema) => {
  // 
  return alteredSchema
}

const configSchemaWithEnv = addEnvVarSupportToJoiSchema(configSchema)

Alternatively, if needed, I could also use something that allows passing a custom Joi object:

const configSchemaCreator = (joi) => joi.object().keys({
  foo: joi.object().keys({
    bar: joi.number().min(0)
  })
})

const alteredSchema = addEnvVarSupportToJoiSchemaUsingCustomJoi(configSchemaCreator)

Could you point me how to do this.

matthieusieben avatar Mar 07 '21 19:03 matthieusieben

My current workaround requires me to write schemas in a quite ugly way:

const withCustomWrapper = (fn) => {
  const joi = Joi.extend(withEnvString)
  // and more
  return fn(joi, (s) => joi.alternaties().try(s, joi.envString()))
}

const createSchema = (joi = Joi, propWrapper = x => x) => {
  return joi.object().keys({
    foo: joi.object().keys({
      bar: propWrapper(joi.number())
    })
  })
}

const schemaWithoutEnvSupport = createSchema()
const schemaWithEnvSupport = withCustomWrapper(createSchema)

Which is quite fine from a usability point of view but I'd like a better separation of concerns when writing the schema (avoid having to "know" that properties might need to be wrapped).

matthieusieben avatar Mar 07 '21 21:03 matthieusieben

I personally don't see a proper way to do this, an alternative to what you're doing could be describe -> alter -> compile but that feels weird. Or add some kind of api to concat to allow overrides under specific conditions.

Marsup avatar Mar 09 '21 10:03 Marsup

For the record npm did a joi extension to deal with environment but that probably won't help you.

Marsup avatar Mar 09 '21 10:03 Marsup

Thanks for your answer. My actual use case isn't really related to env variables, this was more of an example on what I was trying to achieve.

I did fear this wouldn't be possible out of the box. But since I have the ability to have full control over the joi instance used, would it be possible to "sub instance" (sub-class the live instance) to achieve my goal? If so, how would I do that? Where should I look (in addition to the concat method you already mentioned)

matthieusieben avatar Mar 09 '21 17:03 matthieusieben

I'll also look into the describe/compile which I didn't think about, thanks.

matthieusieben avatar Mar 09 '21 18:03 matthieusieben

I'm not exactly sure what you're trying to achieve but wouldn't extensions work for your case ?

Marsup avatar Mar 09 '21 18:03 Marsup