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

Add elm list encoders/decoders #686

Merged
merged 3 commits into from
Oct 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 77 additions & 52 deletions elm-generator/src/main/scala/generator/elm/ElmModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,51 @@ case class ElmModel(args: GenArgs) {
s"type alias ${Names.pascalCase(model.name)} =",
" {",
model.fields.map { f =>
val opt = if (f.required) {
""
} else {
"Maybe "
def maybe(contents: String): String = {
if (f.required) {
contents
} else {
maybeWrapInParens("Maybe", contents)
}
}
s"${Names.camelCase(f.name)}: $opt${elmType.lookup(f.`type`)}"

s"${Names.camelCase(f.name)}: ${maybe(elmType.lookup(f.`type`))}"
}.mkString("\n, ").indent(4).stripTrailing(),
" }"
).mkString("\n")
}

private[this] def maybeWrapInParens(function: String, contents: String): String = {
val i = contents.indexOf(" ")
if (i > 0) {
s"$function ($contents)"
} else {
s"$function $contents"
}
}

private[this] def genEncoderForDatatype(m: Model, f: Field, v: Datatype): String = {
v match {
case p: Datatype.Primitive => primitiveEncoder(p)
case u: UserDefined => {
Names.camelCase(args.imports, u.name) + "Encoder"
}
case m: Generated.Model => {
Names.camelCase(args.imports, m.name) + "Encoder"
}
case u: Container.List => {
args.imports.addAs("Json.Encode", "Encode")
s"(Encode.list ${genEncoderForDatatype(m, f, u.inner)})"
}
case u: Container.Option => {
todo(s"model ${m.name} Field ${f.name} has type option: ${u.name}")
}
case u: Container.Map => {
todo(s"model ${m.name} Field ${f.name} has type map: ${u.name}")
}
}
}


private[this] def genEncoder(m: Model): ElmFunction = {
args.imports.addAs("Json.Encode", "Encode")
Expand All @@ -60,22 +94,12 @@ case class ElmModel(args: GenArgs) {
"[",
m.fields.zipWithIndex.map { case (f, i) =>
val encoder = args.datatypeResolver.parse(f.`type`) match {
case Success(v) => v match {
case p: Datatype.Primitive => fieldEncoder(f, primitiveEncoder(p))
case u: UserDefined => {
fieldEncoder(f, Names.camelCase(args.imports, u.name) + "Encoder")
}
case m: Generated.Model => {
fieldEncoder(f, Names.camelCase(args.imports, m.name) + "Encoder")
}
case u: Container.List => {
todo(s"model ${m.name} Field ${f.name} has type list: ${u.name}")
}
case u: Container.Option => {
todo(s"model ${m.name} Field ${f.name} has type option: ${u.name}")
}
case u: Container.Map => {
todo(s"model ${m.name} Field ${f.name} has type map: ${u.name}")
case Success(v) => {
val encoder = genEncoderForDatatype(m, f, v)
if (f.required) {
s"""( ${Names.wrapInQuotes(f.name)}, $encoder instance.${Names.camelCase(f.name)} )"""
} else {
s"""( ${Names.wrapInQuotes(f.name)}, encodeOptional $encoder instance.${Names.camelCase(f.name)} )"""
}
}
case Failure(ex) => throw ex
Expand All @@ -95,7 +119,7 @@ case class ElmModel(args: GenArgs) {
args.imports.addAs("Json.Encode", "Encode")
import Datatype.Primitive._
p match {
case Boolean => "Encode.boolean"
case Boolean => "Encode.bool"
case Double => "Encode.float"
case Integer => "Encode.int"
case Long => "Encode.int"
Expand All @@ -110,58 +134,59 @@ case class ElmModel(args: GenArgs) {
}
}

private[this] def fieldEncoder(f: Field, encoder: String): String = {
if (f.required) {
s"""( ${Names.wrapInQuotes(f.name)}, $encoder instance.${Names.camelCase(f.name)} )"""
} else {
s"""( ${Names.wrapInQuotes(f.name)}, encodeOptional $encoder instance.${Names.camelCase(f.name)} )"""
}
}

private[this] def genDecoder(m: Model): ElmFunction = {
elmJson.decoder(m.name) {
Seq(
s"Decode.succeed ${Names.pascalCase(m.name)}",
m.fields.map { f =>
modelFieldDecoder(m, f)
val decoder = modelFieldDecoder(m, f)
pipelineDecoder(f, decoder)
}.mkString("\n").indent(4)
).mkString("\n").indent(4)
}
}

private[this] def fieldDecoder(f: Field, decoder: String): String = {
private[this] def pipelineDecoder(f: Field, decoder: String): String = {
args.imports.addAs("Json.Decode.Pipeline", "Pipeline")

if (f.required) {
s"""|> Pipeline.required ${Names.wrapInQuotes(f.name)} $decoder"""
} else {
args.imports.addAs("Json.Decode", "Decode")
s"""|> Pipeline.optional ${Names.wrapInQuotes(f.name)} (Decode.nullable $decoder) Nothing"""
val nullDecoder = maybeWrapInParens("Decode.nullable", decoder)
s"""|> Pipeline.optional ${Names.wrapInQuotes(f.name)} ($nullDecoder) Nothing"""
}
}

private[this] def modelFieldDecoder(m: Model, f: Field): String = {
args.datatypeResolver.parse(f.`type`) match {
case Success(v) => v match {
case p: Datatype.Primitive => fieldDecoder(f, primitiveDecoder(p))
case u: UserDefined => {
fieldDecoder(f, Names.camelCase(args.imports, u.name) + "Decoder")
}
case m: Generated.Model => {
fieldDecoder(f, Names.camelCase(args.imports, m.name) + "Decoder")
}
case u: Container.List => {
todo(s"model ${m.name} Field ${f.name} has type list: ${u.name}")
}
case u: Container.Option => {
todo(s"model ${m.name} Field ${f.name} has type option: ${u.name}")
}
case u: Container.Map => {
todo(s"model ${m.name} Field ${f.name} has type map: ${u.name}")
}
}
case Success(v) => genDecoderForDatatype(m, f, v)
case Failure(ex) => throw ex
}
}

private[this] def genDecoderForDatatype(m: Model, f: Field, v: Datatype): String = {
v match {
case p: Datatype.Primitive => primitiveDecoder(p)
case u: UserDefined => {
Names.camelCase(args.imports, u.name) + "Decoder"
}
case m: Generated.Model => {
Names.camelCase(args.imports, m.name) + "Decoder"
}
case u: Container.List => {
args.imports.addAs("Json.Decode", "Decode")
"(" + maybeWrapInParens("Decode.list", genDecoderForDatatype(m, f, u.inner)) + ")"
}
case u: Container.Option => {
todo(s"model ${m.name} Field ${f.name} has type option: ${u.name}")
}
case u: Container.Map => {
todo(s"model ${m.name} Field ${f.name} has type map: ${u.name}")
}
}
}

private[this] def todo(msg: String): Nothing = {
sys.error(s"The elm generator does not yet support this type: $msg")
}
Expand All @@ -170,7 +195,7 @@ case class ElmModel(args: GenArgs) {
args.imports.addAs("Json.Decode", "Decode")
import Datatype.Primitive._
p match {
case Boolean => "Decode.boolean"
case Boolean => "Decode.bool"
case Double => "Decode.float"
case Integer => "Decode.int"
case Long => "Decode.int"
Expand Down
Loading