andyglow / scala-jsonschema

Scala JSON Schema
Other
122 stars 38 forks source link

PureConfig Sealed Families #292

Open gabrieljones opened 1 year ago

gabrieljones commented 1 year ago

Describe a type or a family of types

Is your feature request related to a problem? Please describe. For Sealed Families, PureConfig adds an additional synthetic field called type. https://pureconfig.github.io/docs/overriding-behavior-for-sealed-families.html This would then allow creating a json schema for valid json that pureconfig could turn back into an ADT.

Describe the solution you'd like A config option to add a const field called type similar to how PureConfig does it.

Describe alternatives you've considered Maybe this configuration is already possible, and I just need help figuring out how to do it.

andyglow commented 1 year ago

hello, @gabrieljones Probably what you are looking for is @discriminator and @discriminatorKey annotations.

Here is an example:


  @discriminator sealed trait Storage
  object Storage {
    // format: off
    @definition("diskDevice") @discriminatorKey("disk")  final case class DiskDevice(device: String Refined MatchesRegex[W.`"^/dev/[^/]+(/[^/]+)*$"`.T]) extends Storage
    @definition("diskUUID")   @discriminatorKey("disk")  final case class DiskUUID(label: java.util.UUID) extends Storage
    @definition("nfs")        @discriminatorKey("nfs")   final case class NFS(remotePath: String Refined MatchesRegex[W.`"^(/[^/]+)+$"`.T], server: String) extends Storage
    @definition("tmpfs")      @discriminatorKey("tmpfs") final case class TmpFS(sizeInMB: Refined[Int, GreaterEqual[W.`16`.T] And LessEqual[W.`512`.T]]) extends Storage
    // format: on
  }

There is also several examples in tests. Check out this spec: DiscriminatorSpec

gabrieljones commented 1 year ago

I will try that.

Also I just realized PureConfig converts field names from camel case to kebab case.

By default, PureConfig:

  • expects config keys to be written in kebab case (such as my-field) and the associated field names are written in camel case (such as myField);

https://pureconfig.github.io/docs/overriding-behavior-for-case-classes.html

gabrieljones commented 1 year ago

I don't suppose it's possible to add a discriminator via configuration instead of with an annotation?

I see json.Schema#withDiscriminationKey, but I don't think that is enough. Hmmm digging...

andyglow commented 1 year ago

Also I just realized PureConfig converts field names from camel case to kebab case.

btw: you can configure it

I don't suppose it's possible to add a discriminator via configuration instead of with an annotation?

what's wrong with annotations approach?

gabrieljones commented 1 year ago

what's wrong with annotations approach?

I am creating a schema from an ADT graph in a library I do not own.

Also I want to prevent scala-jsonschema from becoming a transitive dependency of my library.

andyglow commented 1 year ago

I see, so .. for the first issue: would configuring pureconfig to use corresponding ConfigFieldMapping help? for the second one: I'm afraid there is no other way of defining discriminator machinery except @annotations. If you are up to, please feel free to send me a PR. This can be a cool feature

gabrieljones commented 1 year ago

would configuring pureconfig to use corresponding ConfigFieldMapping help?

I want the resulting schema.json to reflect the kebab case in the conf file not the field name in the ADT case classes. For now I want to stick with the PureConfig convention of kebab case in the conf file and camel case in the ADT case classes.

andyglow commented 1 year ago

got it. original idea was to express schema as it is defined in scala.. so.. you could use Schema.object.withFieldsUpdated method, though.. may seem not so cool, but still an option

import json.Schema, json.Json
import com.github.andyglow.jsonschema._

object PlayJsonBindings {

  case class User(
      id: String,
      firstName: String,
      lastName: String
  )

  def main(args: Array[String]): Unit = {
    val schema = Json.objectSchema[User]().withFieldsUpdated { case f =>
      f.copy(name = camelToKebab(f.name))
    }

    println(schema.draft07("foo"))
  }

  def camelToKebab(name: String) = "[A-Z\\d]".r.replaceAllIn(
    name,
    { m =>
      "-" + m.group(0).toLowerCase()
    }
  )

}

Scastie: https://scastie.scala-lang.org/ZXWdW1xKQParONn2BicfXQ