تولید سوگرها در زمان کامپایل

در محل کار ، ما در حال تولید کد از Swaggers و انتشارات گفته شده کد شده است. افسوس ، این امر ما را ملزم می کند که به یاد داشته باشیم که Swagger (ها) را تولید کنیم ، و همچنین نسخه های کتابخانه ها را اصلاح کنیم (این بدان معنی است که شما باید نسخه های موجود در سرور را به روز کنید ، کد تولید شده به روز شده را منتشر کنید ، سپس مشتری خود را به روز کنید) ، این مثالها را از Eikek/SBT-Openapi-Schema مشاهده کنید:
import com.github.eikek.sbt.openapi._
val CirceVersion = "0.14.1"
libraryDependencies ++= Seq(
"io.circe" %% "circe-generic" % CirceVersion,
"io.circe" %% "circe-parser" % CirceVersion
)
openapiSpec := (Compile/resourceDirectory).value/"test.yml"
openapiTargetLanguage := Language.Scala
Compile/openapiScalaConfig := ScalaConfig()
.withJson(ScalaJson.circeSemiauto)
.addMapping(CustomMapping.forType({ case TypeDef("LocalDateTime", _) =>
TypeDef("Timestamp", Imports("com.mypackage.Timestamp"))
}))
.addMapping(CustomMapping.forName({ case s => s + "Dto" }))
enablePlugins(OpenApiSchema)
مشکل
هنگام تعریف نقاط پایانی ، این غیر معمول نیست که نقطه پایانی و اجرای آن را با یک .serverLogic
، به معنای ایجاد نقاط پایانی ما به اجرای در دسترس است (نیاز به فوری API ، مخازن برای اتصال به پایگاه داده ها ، مشتریان HTTP …).
این کار را سخت تر از نیاز به نوشتن Swaggers ما روی دیسک کرد:
برای اینکه swagger تولید شود و روی دیسک نوشته شود. در غیر این صورت ، کد تولید شده به روز نخواهد بود ، که این یک مشکل است. یک روش بسیار طولانی ، نه خیلی راحت ، که قبلاً یک بار در آن انجام می دادیم.
راه حل ساده
ما می توانیم با استفاده از کتابخانه هایی مانند TAPIR ، سوگرها را تولید کنیم:
val swagger = OpenAPIDocsInterpreter()
.toOpenAPI(
tapirEndpoints,
"my app",
"v0.0.1"
)
.openapi("3.0.3")
.toYaml
writeSwaggerToFile(swagger, pathToSwaggerFile)
سپس ما فقط باید خود را جدا کنیم Endpoint
S و اجرای آنها (که آنها را ساخته است ServerEndpoint
اتفاقاً S ، اما شما فقط به آن احتیاج دارید Endpoint
برای تولید مشخصات OpenAPI).
// MyApiEndpoints.scala
class Endpoints {
protected val booksListingEndpoint: PublicEndpoint[(BooksQuery, Limit, AuthToken), String, List[Book], Any] =
endpoint
.get
.in(("books" / path[String]("genre") / path[Int]("year")).mapTo[BooksQuery])
.in(query[Limit]("limit").description("Maximum number of books to retrieve"))
.in(header[AuthToken]("X-Auth-Token"))
.errorOut(stringBody)
.out(jsonBody[List[Book]])
val plainEndpoints: List[AnyEndpoint] = List(booksListingEndpoint)
}
// MyApiRoutes.scala
class Routes(api: MyApiService) extends Endpoints {
private val booksListing =
booksListingEndpoint
.serverLogic { case (booksQuery, limit, authToken) =>
api.listBooks(authToken, booksQuery, limit)
}
val endpoints: List[ServerEndpoint[Fs2Streams[IO], IO]] = List(
booksListing
)
val routes: HttpRoutes = Http4sServerInterpreter()
.toRoutes(endpoints)
}
و سپس راهی برای کامپایل و اجرای تماس با کد پیدا کنید OpenAPIDocsInterpreter
با new Endpoints().plainEndpoints
بشر آسان است؟
بیایید آن را مهندسی کنیم
تقسیم نقاط پایانی و اجرای نقاط پایانی سرور کار درست و آسان است ، اما می توانیم فراتر برویم. چه می شود اگر ما بتوانیم از سیستم ساخت خود استفاده کنیم تا هر بار که کامپایل می کنیم برای ما ایجاد کند؟
ساخت یک افزونه SBT
SBT است در واقع ایجاد ابزار در Scala ، از این رو من یک افزونه درست کردم تا تولید را برای ما انجام دهم. یک افزونه می تواند یک کار را تعریف کند ، که می توانید در داخل پوسته SBT به عنوان یک دستور تماس بگیرید.
import sbt.Keys.*
import sbt.*
object SpecGen extends AutoPlugin {
object autoImport {
val Spec = config("spec").extend(Runtime)
val specGenMain = settingKey[String]("Main class (FQDN) to run")
val specGenArgs = settingKey[Seq[String]]("Arguments to pass to runner")
val specGenMake = taskKey[Unit]("run code/resource generation from config")
}
import autoImport.*
override def projectSettings: Seq[Def.Settings[?]] = inConfig(Spec)(Defaults.configSettings ++ Seq(
specGenMain := "" ,
specGenArgs := Seq.empty,
specGenMake := {
val logger = streams.value.log
val classPath = Attributed.data((Spec / fullClasspath).value)
(Spec / runner).value.run(specGenMain.value, classPath, specGenArgs.value, logger).get
}
)) :+ (ivyConfigurations := overrideConfigs(Spec)(ivyConfigurations.value))
}
💡 نوک
این اجرای کوچک حداقل لخت برای ثبت یک کلاس ، فوری آن و اجرای آن از طریق SBT است.
تا وظیفه ما را بعد از
compile
ما باید آن را برگردانیمFile
و یک ژنراتور منبع اضافه کنید:Compile / resourceGenerators += (Spec / specGenMake).taskValue
بشر
این افزونه ساده را می توان در پروژه ها به شرح زیر فعال کرد:
val `my-api` = (project in file("application/my-api"))
.settings(...)
.enablePlugins(SpecGen)
.settings(
Spec / specGenMain := "org.myapi.GenerateSwagger"
)
// we need a project to be able to `publish` the swagger.
// GenerateSwagger would write to the /src/resources/swagger.yaml
val `my-api-swagger` = project in file("modules/my-api-swagger")
و ما فقط باید بنویسیم GenerateSwagger
کلاس که Endpoints
و تماس بگیرید OpenAPIDocsInterpreter
روی آنها ، برای نوشتن YAML حاصل در یک پرونده!
📝 توجه داشته باشید
در حال حاضر ، این اجرای کوچک به کاربر نیاز دارد تا اجرا شود
Spec / specGenMake;compile
تا زمانی که مجدداً آن را مجدداً مورد استفاده قرار دهم ، تولید و کدگذاری شده است. برای نیازهای ما این مسئله بزرگی نیست ، زیرا ما در محیط CI/CD خود Swaggers را تولید و منتشر می کنیم.
تولید کد Scala از یک swagger منتشر شده
چگونه می توانید کد را از یک swagger در داخل یک شیشه بارگیری شده توسط SBT تولید کنید؟
با استفاده از SBT-SWGAGETER ، بارگیری کننده Swagger و Unpacker ، بسیار آسان است! ماژول Swagger خود را به عنوان وابستگی اضافه کنید و به افزونه اجازه دهید کار را انجام دهد:
// build.sbt
object Dependencies {
lazy val swagger = "com.example" % "my-api-swagger_2.13" % "1.0.0"
lazy val circe = Seq(
"io.circe" % "circe-core",
"io.circe" % "circe-generics",
"io.circe" % "circe-generic-extras"
).map(_ %% "0.14.1")
}
lazy val `my-api-generated` = (project in file("modules/my-api-generated"))
.enablePlugins(OpenApiSchema)
.enablePlugins(SwaggerinatorSbt)
.settings(swaggerinatorDependency := Dependencies.swagger)
.settings(swaggerinatorPackage := Pkg("com.example.my-api.generated"))
.settings(libraryDependencies ++= Dependencies.circe)
lazy val infrastructure = (project in file("modules/infrastructure"))
// ...
.dependsOn(`my-api-generated` % "compile->compile")
عبور از وابستگی به Swaggerinator به آن اجازه می دهد تا با جدا کردن شیشه به آن دسترسی داشته باشد (از این گذشته ، این فقط یک فایل زیپ با یک پسوند دیگر است). سپس افزونه با تماس با آن ، کد را از swagger تولید می کند OpenApiSchema
افزونه برای ما
در اصل از lexp.lt