Repository: softwaremill/sttp-openai (scala)

Issue:
Retrieve model
Given uri:
`https://api.openai.com/v1/models/{model}`

Create an sttp GET endpoint + tests for serialization


Docs reference 
https://platform.openai.com/docs/api-reference/models/retrieve

Pull request description:
Closes #3 

tldr tests for serializator has been done in previous PR (get all models)

There is no test for serialization in this PR, because single model that we receive from a response is a part of all the models we get from previous request that was merged.

Patch:
diff --git a/core/src/main/scala/sttp/openai/Main.scala b/core/src/main/scala/sttp/openai/Main.scala
index 905bbc68..3a84c619 100644
--- a/core/src/main/scala/sttp/openai/Main.scala
+++ b/core/src/main/scala/sttp/openai/Main.scala
@@ -1,15 +1,13 @@
 package sttp.openai
 
 import sttp.client4._
-import sttp.openai.requests.models.ModelsGetResponseData.ModelsResponse
 
 object Main extends App {
   val backend: SyncBackend = DefaultSyncBackend()
 
   val openAi: OpenAi = new OpenAi("test")
-  val response: Response[Either[ResponseException[String, Exception], ModelsResponse]] =
-    openAi.getModels
-      .send(backend)
+
+  val response = openAi.retrieveModel("text-davinci-003").send(backend)
 
   println(response.code)
   println(response.body)
diff --git a/core/src/main/scala/sttp/openai/OpenAi.scala b/core/src/main/scala/sttp/openai/OpenAi.scala
index c1ab3f31..9e79e1a3 100644
--- a/core/src/main/scala/sttp/openai/OpenAi.scala
+++ b/core/src/main/scala/sttp/openai/OpenAi.scala
@@ -2,7 +2,7 @@ package sttp.openai
 
 import sttp.client4._
 import sttp.model.Uri
-import sttp.openai.requests.models.ModelsGetResponseData.ModelsResponse
+import sttp.openai.requests.models.ModelsResponseData.{ModelData, ModelsResponse}
 import sttp.openai.json.SttpUpickleApiExtension.asJsonSnake
 import sttp.openai.requests.files.FilesResponseData._
 
@@ -11,9 +11,19 @@ class OpenAi(authToken: String) {
   /** Fetches all available models from [[https://platform.openai.com/docs/api-reference/models]] */
   def getModels: Request[Either[ResponseException[String, Exception], ModelsResponse]] =
     openApiAuthRequest
-      .get(OpenAIEndpoints.ModelEndpoint)
+      .get(OpenAIEndpoints.modelEndpoint)
       .response(asJsonSnake[ModelsResponse])
 
+  /** @param modelId
+    *   a Model's Id as String
+    *
+    * Fetches an available model for given modelId from [[https://platform.openai.com/docs/api-reference/models/{modelId}]]
+    */
+  def retrieveModel(modelId: String): Request[Either[ResponseException[String, Exception], ModelData]] =
+    openApiAuthRequest
+      .get(OpenAIEndpoints.RetrieveModelEndpoint(modelId))
+      .response(asJsonSnake[ModelData])
+
   /** Fetches all files that belong to the user's organization from [[https://platform.openai.com/docs/api-reference/files]] */
   def getFiles: Request[Either[ResponseException[String, Exception], FilesResponse]] =
     openApiAuthRequest
@@ -25,6 +35,7 @@ class OpenAi(authToken: String) {
 }
 
 private object OpenAIEndpoints {
-  val ModelEndpoint: Uri = uri"https://api.openai.com/v1/models"
   val FilesEndpoint: Uri = uri"https://api.openai.com/v1/files"
+  val modelEndpoint: Uri = uri"https://api.openai.com/v1/models"
+  def RetrieveModelEndpoint(modelId: String): Uri = modelEndpoint.addPath(modelId)
 }
diff --git a/core/src/main/scala/sttp/openai/requests/models/ModelsGetResponseData.scala b/core/src/main/scala/sttp/openai/requests/models/ModelsResponseData.scala
similarity index 61%
rename from core/src/main/scala/sttp/openai/requests/models/ModelsGetResponseData.scala
rename to core/src/main/scala/sttp/openai/requests/models/ModelsResponseData.scala
index b5f441da..fda6b724 100644
--- a/core/src/main/scala/sttp/openai/requests/models/ModelsGetResponseData.scala
+++ b/core/src/main/scala/sttp/openai/requests/models/ModelsResponseData.scala
@@ -2,23 +2,23 @@ package sttp.openai.requests.models
 
 import sttp.openai.json.SnakePickle
 
-object ModelsGetResponseData {
+object ModelsResponseData {
 
-  case class Data(
+  case class ModelData(
       id: String,
       `object`: String,
       created: Int,
       ownedBy: String,
-      permission: Seq[Permission],
+      permission: Seq[ModelPermission],
       root: String,
       parent: Option[String]
   )
 
-  object Data {
-    implicit def dataReadWriter: SnakePickle.ReadWriter[Data] = SnakePickle.macroRW[Data]
+  object ModelData {
+    implicit def dataReadWriter: SnakePickle.ReadWriter[ModelData] = SnakePickle.macroRW[ModelData]
   }
 
-  case class Permission(
+  case class ModelPermission(
       id: String,
       `object`: String,
       created: Int,
@@ -33,11 +33,11 @@ object ModelsGetResponseData {
       isBlocking: Boolean
   )
 
-  object Permission {
-    implicit def permissionReadWriter: SnakePickle.ReadWriter[Permission] = SnakePickle.macroRW[Permission]
+  object ModelPermission {
+    implicit def permissionReadWriter: SnakePickle.ReadWriter[ModelPermission] = SnakePickle.macroRW[ModelPermission]
   }
 
-  case class ModelsResponse(`object`: String, data: Seq[Data])
+  case class ModelsResponse(`object`: String, data: Seq[ModelData])
 
   object ModelsResponse {
     implicit def modelsResponseReadWriter: SnakePickle.ReadWriter[ModelsResponse] = SnakePickle.macroRW[ModelsResponse]


Test patch:
diff --git a/core/src/test/scala/sttp/openai/requests/models/ModelsGetResponseDataSpec.scala b/core/src/test/scala/sttp/openai/requests/models/ModelsGetResponseDataSpec.scala
index 3f6a4e70..36cbe4b3 100644
--- a/core/src/test/scala/sttp/openai/requests/models/ModelsGetResponseDataSpec.scala
+++ b/core/src/test/scala/sttp/openai/requests/models/ModelsGetResponseDataSpec.scala
@@ -3,8 +3,8 @@ package sttp.openai.requests.models
 import org.scalatest.EitherValues
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
-import ModelsGetResponseData.{Data, ModelsResponse, Permission}
-import ModelsGetResponseData.ModelsResponse._
+import ModelsResponseData.{ModelData, ModelPermission, ModelsResponse}
+import ModelsResponseData.ModelsResponse._
 import sttp.openai.fixtures
 import sttp.openai.json.SttpUpickleApiExtension
 
@@ -15,8 +15,8 @@ class ModelsGetResponseDataSpec extends AnyFlatSpec with Matchers with EitherVal
     // given
     val response: String = fixtures.ModelsGetResponse.responseJson
 
-    val babbagePermission: Seq[Permission] = Seq(
-      Permission(
+    val babbagePermission: Seq[ModelPermission] = Seq(
+      ModelPermission(
         id = "modelperm-49FUp5v084tBB49tC4z8LPH5",
         `object` = "model_permission",
         created = 1669085501,
@@ -32,9 +32,9 @@ class ModelsGetResponseDataSpec extends AnyFlatSpec with Matchers with EitherVal
       )
     )
 
-    val davinciPermission: Seq[Permission] =
+    val davinciPermission: Seq[ModelPermission] =
       Seq(
-        Permission(
+        ModelPermission(
           id = "modelperm-U6ZwlyAd0LyMk4rcMdz33Yc3",
           `object` = "model_permission",
           created = 1669066355,
@@ -50,8 +50,8 @@ class ModelsGetResponseDataSpec extends AnyFlatSpec with Matchers with EitherVal
         )
       )
 
-    val serializedData: Seq[Data] = Seq(
-      Data(
+    val serializedData: Seq[ModelData] = Seq(
+      ModelData(
         id = "babbage",
         `object` = "model",
         created = 1649358449,
@@ -60,7 +60,7 @@ class ModelsGetResponseDataSpec extends AnyFlatSpec with Matchers with EitherVal
         root = "babbage",
         parent = None
       ),
-      Data(
+      ModelData(
         id = "davinci",
         `object` = "model",
         created = 1649359874,