Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using AutoField with schema containing patternProperties #1290

Open
harleyharl opened this issue Oct 24, 2023 · 1 comment
Open

Using AutoField with schema containing patternProperties #1290

harleyharl opened this issue Oct 24, 2023 · 1 comment
Assignees
Labels
Area: Bridge Affects some of the bridge packages Bridge: JSON Schema Affects the uniforms-bridge-json-schema package Type: Feature New features and feature requests
Milestone

Comments

@harleyharl
Copy link

harleyharl commented Oct 24, 2023

I've encountered an issue while attempting to use AutoField with a schema that contains patternProperties. Here's the relevant part of my schema:

"discrete_components": {
  "type": "object",
  "patternProperties": {
    ".+": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "maxLength": 255,
          "minLength": 1
        },
        "components": {
          "type": "array",
          "maxItems": 512,
          "minItems": 1,
          "items": {
            "type": "object",
            "required": ["type", "range_start", "range_end"],
            "properties": {
              "type": {
                "type": "string",
                "enum": ["OPEN", "CLOSED"]
              },
              "range_start": {
                "type": "integer",
                "maximum": 255
              },
              "range_end": {
                "type": "integer",
                "maximum": 255
              }
            }
          }
        }
      }
    }
  }
}

I'm trying to use AutoField like this:

<AutoField name="discrete_components.someId" />

However, this doesn't work because AutoField uses the getField function from the JSONSchemaBridge class, and that function doesn't handle patternProperties. Would it be possible to consider adding support for patternProperties to the JSONSchemaBridge class?

I've attempted to extend the class to address this issue, but it's a bit messy. Here's an example of what I've tried:

class CustomJSONSchemaBridge extends JSONSchemaBridge {

  fieldInvariant = (name: string, condition: boolean) => {
    invariant(condition, "Field not found in schema: \"%s\"", name)
  }

  partialNames = ["allOf", "anyOf", "oneOf"]

  resolveRefIfNeeded (
    partial: UnknownObject,
    schema: UnknownObject,
  ): UnknownObject {
    if (!("$ref" in partial)) {
      return partial
    }
  
    const { $ref, ...partialWithoutRef } = partial
    return this.resolveRefIfNeeded(
      // @ts-expect-error The `partial` and `schema` should be typed more precisely.
      Object.assign({}, partialWithoutRef, resolveRef($ref, schema)),
      schema,
    )
  }

  getField (name: string) {
    return joinName(null, name).reduce((definition, next, index, array) => {
      const prevName = joinName(array.slice(0, index))
      const nextName = joinName(prevName, next)
      const definitionCache = (this._compiledSchema[nextName] ??= {})
      definitionCache.isRequired = !!(
        definition.required?.includes(next) ||
        this._compiledSchema[prevName].required?.includes(next)
      )
  
      if (next === "$" || next === "" + parseInt(next, 10)) {
        this.fieldInvariant(name, definition.type === "array")
        definition = Array.isArray(definition.items)
          ? definition.items[parseInt(next, 10)]
          : definition.items
        this.fieldInvariant(name, !!definition)
      } else if (definition.type === "object" && definition.patternProperties) {
        // NOTE: This block has been added to handle patternProperties which aren't handled already. 
        let nextFound = false
        for (const pattern of Object.keys(definition.patternProperties)) {
          const regex = new RegExp(pattern)
          if (regex.test(next)) {
            definition = definition.patternProperties[pattern]
            nextFound = true
            break
          }
        }
        this.fieldInvariant(name, !!definition.properties)
      } else if (definition.type === "object") {
        this.fieldInvariant(name, !!definition.properties)
        definition = definition.properties[joinName.unescape(next)]
        this.fieldInvariant(name, !!definition)
      } else {
        let nextFound = false
        this.partialNames.forEach(partialName => {
          definition[partialName]?.forEach((partialElement: any) => {
            if (!nextFound) {
              partialElement = this.resolveRefIfNeeded(partialElement, this.schema)
              if (next in partialElement.properties) {
                definition = partialElement.properties[next]
                nextFound = true
              }
            }
          })
        })
  
        this.fieldInvariant(name, nextFound)
      }
  
      definition = this.resolveRefIfNeeded(definition, this.schema)
  
      // Naive computation of combined type, properties, and required.
      const required = definition.required ? definition.required.slice() : []
      const properties = definition.properties
        ? Object.assign({}, definition.properties)
        : {}
  
      this.partialNames.forEach(partialName => {
        definition[partialName]?.forEach((partial: any) => {
          partial = this.resolveRefIfNeeded(partial, this.schema)
  
          if (partial.required) {
            required.push(...partial.required)
          }
  
          Object.assign(properties, partial.properties)
  
          if (!definitionCache.type && partial.type) {
            definitionCache.type = partial.type
          }
        })
      })
  
      if (required.length > 0) {
        definitionCache.required = required
      }
  
      if (!isEmpty(properties)) {
        definitionCache.properties = properties
      }
  
      return definition
    }, this.schema)
  }
}

Is there a better approach that I may have overlooked? Your assistance is greatly appreciated! Thank you.

@kestarumper kestarumper added Type: Feature New features and feature requests Area: Bridge Affects some of the bridge packages Bridge: JSON Schema Affects the uniforms-bridge-json-schema package labels Nov 3, 2023
@kestarumper kestarumper self-assigned this Nov 3, 2023
@kestarumper
Copy link
Member

Hi @harleyharl,

Would it be possible to consider adding support for patternProperties to the JSONSchemaBridge class?

We had a small internal discussion about it, and we came to the conclusion that we should always try to handle as many features as JSONSchema provides. Given that, we appreciate your effort and would happily accept a working solution as a PR. However, it still needs to pass the tests and possibly add new ones that cover the new functionality. If you don't have time to do it, then eventually, we'll provide this ourselves somewhere in the future.

TL;DR: Yes, it is possible and welcome to submit a PR with the support for patternProperties.

PS: Thanks for sharing your implementation.

@kestarumper kestarumper added this to the v4.x milestone Nov 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Bridge Affects some of the bridge packages Bridge: JSON Schema Affects the uniforms-bridge-json-schema package Type: Feature New features and feature requests
Projects
Status: Concept
Development

No branches or pull requests

2 participants