All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gql.client.codegen.GeneratorCli.scala Maven / Gradle / Ivy

There is a newer version: 0.4.1-4-348043d
Show newest version
/*
 * Copyright 2023 Valdemar Grange
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package gql.client.codegen

import cats.effect._
import cats.implicits._
import com.monovore.decline._
import com.monovore.decline.effect._
import fs2.io.file._
import cats.data._
import io.circe._

object GeneratorCli
    extends CommandIOApp(
      name = "gql-client-codegen",
      header = "GraphQL client code generator for GQL"
    ) {
  final case class Query(
      query: Path,
      output: Option[Path]
  )
  final case class Input(
      schema: Path,
      shared: Path,
      queries: NonEmptyList[Query]
  )
  val kvPairs =
    Opts.options[Input](
      "input",
      help = """|Path to the GraphQL schemas and query files, given as json value.
                |For instance: {"schema": "/my/schema", "shared": "/mp/shared", "queries": [{ "query": "/my/query", "output": "/my/Query.scala" }] }.
                |"schema" should be the path to your schema
                |"shared" should be the output file for shared types (graphql input and enum types), note that only inputs and enums used in your queries are generated
                |"queries" should be the collection of queries that you want to generate code for
                |Omitting "output" will generate the file beside the input query""".stripMargin
    ) {
      Argument.from[Input]("json-input") { str =>
        implicit val decoderForPath: Decoder[Path] = Decoder.decodeString.emap { str =>
          Either.catchNonFatal(Path(str)).leftMap(_.getMessage)
        }

        implicit val queryDec: Decoder[Query] = Decoder.instance[Query] { c =>
          (
            c.downField("query").as[Path],
            c.downField("output").as[Option[Path]]
          ).mapN(Query.apply)
        }

        implicit val inputDec: Decoder[Input] = Decoder.instance[Input] { c =>
          (
            c.get[Path]("schema"),
            c.get[Path]("shared"),
            c.get[NonEmptyList[Query]]("queries")
          ).mapN(Input.apply)
        }

        io.circe.parser.decode[Input](str).leftMap(_.show).toValidatedNel
      }
    }

  val validateOpt = Opts.flag("validate", "validate the schema and queries").orFalse

  override def main: Opts[IO[ExitCode]] =
    (validateOpt, kvPairs).mapN { case (validate, kvs) =>
      fs2.Stream
        .emits(kvs.toList)
        .lift[IO]
        .evalMap { i =>
          Generator.mainGenerate[IO](i.schema, i.shared, validate) {
            i.queries.toList.map { q =>
              Generator.Input(
                q.query,
                q.output.getOrElse(q.query.parent.get / (q.query.fileName.toString + ".scala"))
              )
            }
          }
        }
        .compile
        .foldMonoid
        .flatMap {
          case Nil => IO.unit
          case xs  => IO.raiseError(new Exception(s"Failed to generate code with error: ${xs.mkString("\n")}"))
        }
        .as(ExitCode.Success)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy