org.http4s.client.Client Scala Examples

The following examples show how to use org.http4s.client.Client. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.
Example 1
Source File: DeploymentsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.apps.v1.{Deployment, DeploymentList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class DeploymentsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[DeploymentList],
    encoder: Encoder[Deployment],
    decoder: Decoder[Deployment]
) extends Listable[F, DeploymentList] {
  val resourceUri: Uri = uri"/apis" / "apps" / "v1" / "deployments"

  def namespace(namespace: String): NamespacedDeploymentsApi[F] =
    NamespacedDeploymentsApi(httpClient, config, namespace)
}

private[client] case class NamespacedDeploymentsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[Deployment],
    val resourceDecoder: Decoder[Deployment],
    val listDecoder: Decoder[DeploymentList]
) extends Creatable[F, Deployment]
    with Replaceable[F, Deployment]
    with Gettable[F, Deployment]
    with Listable[F, DeploymentList]
    with Deletable[F]
    with DeletableTerminated[F]
    with GroupDeletable[F]
    with Watchable[F, Deployment] {
  val resourceUri: Uri = uri"/apis" / "apps" / "v1" / "namespaces" / namespace / "deployments"
} 
Example 2
Source File: ReplicaSetsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.apps.v1.{ReplicaSet, ReplicaSetList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class ReplicaSetsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[ReplicaSetList],
    encoder: Encoder[ReplicaSet],
    decoder: Decoder[ReplicaSet]
) extends Listable[F, ReplicaSetList] {
  val resourceUri: Uri = uri"/apis" / "apps" / "v1" / "replicasets"

  def namespace(namespace: String): NamespacedReplicaSetsApi[F] =
    NamespacedReplicaSetsApi(httpClient, config, namespace)
}

private[client] case class NamespacedReplicaSetsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[ReplicaSet],
    val resourceDecoder: Decoder[ReplicaSet],
    val listDecoder: Decoder[ReplicaSetList]
) extends Creatable[F, ReplicaSet]
    with Replaceable[F, ReplicaSet]
    with Gettable[F, ReplicaSet]
    with Listable[F, ReplicaSetList]
    with Deletable[F]
    with DeletableTerminated[F]
    with GroupDeletable[F]
    with Watchable[F, ReplicaSet] {
  val resourceUri = uri"/apis" / "apps" / "v1" / "namespaces" / namespace / "replicasets"
} 
Example 3
Source File: StatefulSetsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.apps.v1.{StatefulSet, StatefulSetList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class StatefulSetsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[StatefulSetList],
    encoder: Encoder[StatefulSet],
    decoder: Decoder[StatefulSet]
) extends Listable[F, StatefulSetList] {
  val resourceUri: Uri = uri"/apis" / "apps" / "v1" / "statefulsets"

  def namespace(namespace: String): NamespacedStatefulSetsApi[F] =
    NamespacedStatefulSetsApi(httpClient, config, namespace)
}

private[client] case class NamespacedStatefulSetsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[StatefulSet],
    val resourceDecoder: Decoder[StatefulSet],
    val listDecoder: Decoder[StatefulSetList]
) extends Creatable[F, StatefulSet]
    with Replaceable[F, StatefulSet]
    with Gettable[F, StatefulSet]
    with Listable[F, StatefulSetList]
    with Deletable[F]
    with DeletableTerminated[F]
    with GroupDeletable[F]
    with Watchable[F, StatefulSet] {
  val resourceUri: Uri = uri"/apis" / "apps" / "v1" / "namespaces" / namespace / "statefulsets"
} 
Example 4
Source File: SecretsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import java.util.Base64
import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.core.v1.{Secret, SecretList}
import org.http4s.{Status, Uri}
import org.http4s.client.Client
import org.http4s.implicits._
import scala.collection.compat._

private[client] case class SecretsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[SecretList],
    encoder: Encoder[Secret],
    decoder: Decoder[Secret]
) extends Listable[F, SecretList] {
  val resourceUri = uri"/api" / "v1" / "secrets"

  def namespace(namespace: String) = NamespacedSecretsApi(httpClient, config, namespace)
}

private[client] case class NamespacedSecretsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[Secret],
    val resourceDecoder: Decoder[Secret],
    val listDecoder: Decoder[SecretList]
) extends Creatable[F, Secret]
    with Replaceable[F, Secret]
    with Gettable[F, Secret]
    with Listable[F, SecretList]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, Secret] {
  val resourceUri: Uri = uri"/api" / "v1" / "namespaces" / namespace / "secrets"

  def createEncode(resource: Secret): F[Status] = create(encode(resource))

  def createOrUpdateEncode(resource: Secret): F[Status] =
    createOrUpdate(encode(resource))

  private def encode(resource: Secret) =
    resource.copy(data = resource.data.map(_.view.mapValues(v => Base64.getEncoder.encodeToString(v.getBytes)).toMap))
} 
Example 5
Source File: ConfigMapsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.core.v1.{ConfigMap, ConfigMapList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class ConfigMapsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[ConfigMapList],
    encoder: Encoder[ConfigMap],
    decoder: Decoder[ConfigMap]
) extends Listable[F, ConfigMapList] {
  val resourceUri: Uri = uri"/api" / "v1" / "configmaps"

  def namespace(namespace: String): NamespacedConfigMapsApi[F] = NamespacedConfigMapsApi(httpClient, config, namespace)
}

private[client] case class NamespacedConfigMapsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[ConfigMap],
    val resourceDecoder: Decoder[ConfigMap],
    val listDecoder: Decoder[ConfigMapList]
) extends Creatable[F, ConfigMap]
    with Replaceable[F, ConfigMap]
    with Gettable[F, ConfigMap]
    with Listable[F, ConfigMapList]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, ConfigMap] {
  val resourceUri: Uri = uri"/api" / "v1" / "namespaces" / namespace / "configmaps"
} 
Example 6
Source File: NamespacesApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe.{Decoder, Encoder}
import io.k8s.api.core.v1.{Namespace, NamespaceList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class NamespacesApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[NamespaceList],
    val resourceEncoder: Encoder[Namespace],
    val resourceDecoder: Decoder[Namespace]
) extends Creatable[F, Namespace]
    with Replaceable[F, Namespace]
    with Gettable[F, Namespace]
    with Listable[F, NamespaceList]
    with Deletable[F]
    with DeletableTerminated[F]
    with Watchable[F, Namespace] {
  protected val resourceUri: Uri = uri"/api" / "v1" / "namespaces"
} 
Example 7
Source File: PodDisruptionBudgetsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.policy.v1beta1.{PodDisruptionBudget, PodDisruptionBudgetList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class PodDisruptionBudgetsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[PodDisruptionBudgetList],
    encoder: Encoder[PodDisruptionBudget],
    decoder: Decoder[PodDisruptionBudget]
) extends Listable[F, PodDisruptionBudgetList] {
  val resourceUri: Uri = uri"/apis" / "policy" / "v1beta1" / "poddisruptionbudgets"

  def namespace(namespace: String): NamespacedPodDisruptionBudgetApi[F] =
    NamespacedPodDisruptionBudgetApi(httpClient, config, namespace)
}

private[client] case class NamespacedPodDisruptionBudgetApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[PodDisruptionBudget],
    val resourceDecoder: Decoder[PodDisruptionBudget],
    val listDecoder: Decoder[PodDisruptionBudgetList]
) extends Creatable[F, PodDisruptionBudget]
    with Replaceable[F, PodDisruptionBudget]
    with Gettable[F, PodDisruptionBudget]
    with Listable[F, PodDisruptionBudgetList]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, PodDisruptionBudget] {
  val resourceUri: Uri = uri"/apis" / "policy" / "v1beta1" / "namespaces" / namespace / "poddisruptionbudgets"
} 
Example 8
Source File: CustomResourceDefinitionsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe.{Decoder, Encoder}
import io.k8s.apiextensionsapiserver.pkg.apis.apiextensions.v1.{CustomResourceDefinition, CustomResourceDefinitionList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class CustomResourceDefinitionsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[CustomResourceDefinitionList],
    val resourceEncoder: Encoder[CustomResourceDefinition],
    val resourceDecoder: Decoder[CustomResourceDefinition]
) extends Creatable[F, CustomResourceDefinition]
    with Replaceable[F, CustomResourceDefinition]
    with Gettable[F, CustomResourceDefinition]
    with Listable[F, CustomResourceDefinitionList]
    with Deletable[F]
    with DeletableTerminated[F]
    with GroupDeletable[F]
    with Watchable[F, CustomResourceDefinition] { self =>
  val resourceUri: Uri = uri"/apis" / "apiextensions.k8s.io" / "v1" / "customresourcedefinitions"
  override val watchResourceUri: Uri =
    uri"/apis" / "apiextensions.k8s.io" / "v1" / "watch" / "customresourcedefinitions"
} 
Example 9
Source File: ServiceAccountsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.core.v1.{ServiceAccount, ServiceAccountList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class ServiceAccountsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[ServiceAccountList],
    encoder: Encoder[ServiceAccount],
    decoder: Decoder[ServiceAccount]
) extends Listable[F, ServiceAccountList] {
  val resourceUri: Uri = uri"/api" / "v1" / "serviceaccounts"

  def namespace(namespace: String): NamespacedServiceAccountsApi[F] =
    NamespacedServiceAccountsApi(httpClient, config, namespace)
}

private[client] case class NamespacedServiceAccountsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[ServiceAccount],
    val resourceDecoder: Decoder[ServiceAccount],
    val listDecoder: Decoder[ServiceAccountList]
) extends Creatable[F, ServiceAccount]
    with Replaceable[F, ServiceAccount]
    with Gettable[F, ServiceAccount]
    with Listable[F, ServiceAccountList]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, ServiceAccount] {
  val resourceUri: Uri = uri"/api" / "v1" / "namespaces" / namespace / "serviceaccounts"
} 
Example 10
Source File: HorizontalPodAutoscalersApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.autoscaling.v1.{HorizontalPodAutoscaler, HorizontalPodAutoscalerList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class HorizontalPodAutoscalersApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[HorizontalPodAutoscalerList],
    encoder: Encoder[HorizontalPodAutoscaler],
    decoder: Decoder[HorizontalPodAutoscaler]
) extends Listable[F, HorizontalPodAutoscalerList] {
  val resourceUri: Uri = uri"/apis" / "autoscaling" / "v1" / "horizontalpodautoscalers"

  def namespace(namespace: String): NamespacedHorizontalPodAutoscalersApi[F] =
    NamespacedHorizontalPodAutoscalersApi(httpClient, config, namespace)
}

private[client] case class NamespacedHorizontalPodAutoscalersApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[HorizontalPodAutoscaler],
    val resourceDecoder: Decoder[HorizontalPodAutoscaler],
    val listDecoder: Decoder[HorizontalPodAutoscalerList]
) extends Creatable[F, HorizontalPodAutoscaler]
    with Replaceable[F, HorizontalPodAutoscaler]
    with Gettable[F, HorizontalPodAutoscaler]
    with Listable[F, HorizontalPodAutoscalerList]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, HorizontalPodAutoscaler] {
  val resourceUri: Uri = uri"/apis" / "autoscaling" / "v1" / "namespaces" / namespace / "horizontalpodautoscalers"
} 
Example 11
Source File: ServicesApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.core.v1.{Service, ServiceList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class ServicesApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[ServiceList],
    encoder: Encoder[Service],
    decoder: Decoder[Service]
) extends Listable[F, ServiceList] {
  val resourceUri: Uri = uri"/api" / "v1" / "services"

  def namespace(namespace: String): NamespacedServicesApi[F] = NamespacedServicesApi(httpClient, config, namespace)
}

private[client] case class NamespacedServicesApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[Service],
    val resourceDecoder: Decoder[Service],
    val listDecoder: Decoder[ServiceList]
) extends Creatable[F, Service]
    with Replaceable[F, Service]
    with Gettable[F, Service]
    with Listable[F, ServiceList]
    with Proxy[F]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, Service] {
  val resourceUri: Uri = uri"/api" / "v1" / "namespaces" / namespace / "services"
} 
Example 12
Source File: JobsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.batch.v1.{Job, JobList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class JobsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[JobList],
    encoder: Encoder[Job],
    decoder: Decoder[Job]
) extends Listable[F, JobList] {
  val resourceUri: Uri = uri"/apis" / "batch" / "v1" / "jobs"

  def namespace(namespace: String): NamespacedJobsApi[F] = NamespacedJobsApi(httpClient, config, namespace)
}

private[client] case class NamespacedJobsApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[Job],
    val resourceDecoder: Decoder[Job],
    val listDecoder: Decoder[JobList]
) extends Creatable[F, Job]
    with Replaceable[F, Job]
    with Gettable[F, Job]
    with Listable[F, JobList]
    with Deletable[F]
    with DeletableTerminated[F]
    with GroupDeletable[F]
    with Watchable[F, Job] {
  val resourceUri: Uri = uri"/apis" / "batch" / "v1" / "namespaces" / namespace / "jobs"
} 
Example 13
Source File: IngressesApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._
import io.k8s.api.networking.v1beta1.{Ingress, IngressList}

private[client] case class IngressessApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[IngressList],
    encoder: Encoder[Ingress],
    decoder: Decoder[Ingress]
) extends Listable[F, IngressList] {
  val resourceUri: Uri = uri"/apis" / "extensions" / "v1beta1" / "ingresses"

  def namespace(namespace: String): NamespacedIngressesApi[F] = NamespacedIngressesApi(httpClient, config, namespace)
}

private[client] case class NamespacedIngressesApi[F[_]](
    httpClient: Client[F],
    config: KubeConfig,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[Ingress],
    val resourceDecoder: Decoder[Ingress],
    val listDecoder: Decoder[IngressList]
) extends Creatable[F, Ingress]
    with Replaceable[F, Ingress]
    with Gettable[F, Ingress]
    with Listable[F, IngressList]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, Ingress] {
  val resourceUri: Uri = uri"/apis" / "extensions" / "v1beta1" / "namespaces" / namespace / "ingresses"
} 
Example 14
Source File: KubernetesClient.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client

import java.net.http.HttpClient

import cats.effect._
import com.goyeau.kubernetes.client.api._
import com.goyeau.kubernetes.client.crd.{CrdContext, CustomResource, CustomResourceList}
import com.goyeau.kubernetes.client.util.SslContexts
import io.circe.{Decoder, Encoder}
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.client.jdkhttpclient.JdkWSClient

import scala.concurrent.ExecutionContext

case class KubernetesClient[F[_]: ConcurrentEffect: ContextShift](httpClient: Client[F], config: KubeConfig) {
  lazy val namespaces = NamespacesApi(httpClient, config)
  lazy val pods = PodsApi(
    httpClient,
    JdkWSClient[F](HttpClient.newBuilder().sslContext(SslContexts.fromConfig(config)).build()),
    config
  )
  lazy val jobs                      = JobsApi(httpClient, config)
  lazy val cronJobs                  = CronJobsApi(httpClient, config)
  lazy val deployments               = DeploymentsApi(httpClient, config)
  lazy val statefulSets              = StatefulSetsApi(httpClient, config)
  lazy val replicaSets               = ReplicaSetsApi(httpClient, config)
  lazy val services                  = ServicesApi(httpClient, config)
  lazy val serviceAccounts           = ServiceAccountsApi(httpClient, config)
  lazy val configMaps                = ConfigMapsApi(httpClient, config)
  lazy val secrets                   = SecretsApi(httpClient, config)
  lazy val horizontalPodAutoscalers  = HorizontalPodAutoscalersApi(httpClient, config)
  lazy val podDisruptionBudgets      = PodDisruptionBudgetsApi(httpClient, config)
  lazy val customResourceDefinitions = CustomResourceDefinitionsApi(httpClient, config)
  lazy val ingresses                 = IngressessApi(httpClient, config)

  def customResources[A: Encoder: Decoder, B: Encoder: Decoder](context: CrdContext)(implicit
      listDecoder: Decoder[CustomResourceList[A, B]],
      encoder: Encoder[CustomResource[A, B]],
      decoder: Decoder[CustomResource[A, B]]
  ) = CustomResourcesApi[F, A, B](httpClient, config, context)
}

object KubernetesClient {
  def apply[F[_]: ConcurrentEffect: ContextShift](config: KubeConfig): Resource[F, KubernetesClient[F]] =
    BlazeClientBuilder[F](ExecutionContext.global, Option(SslContexts.fromConfig(config))).resource
      .map(httpClient => apply(httpClient, config))

  def apply[F[_]: ConcurrentEffect: ContextShift](config: F[KubeConfig]): Resource[F, KubernetesClient[F]] =
    Resource.liftF(config).flatMap(apply(_))
} 
Example 15
Source File: Creatable.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import scala.language.reflectiveCalls
import cats.implicits._
import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.util.CirceEntityCodec._
import com.goyeau.kubernetes.client.util.EnrichedStatus
import io.circe._
import io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
import org.http4s._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.headers.`Content-Type`
import org.http4s.Method._

private[client] trait Creatable[F[_], Resource <: { def metadata: Option[ObjectMeta] }] extends Http4sClientDsl[F] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri
  implicit protected def resourceEncoder: Encoder[Resource]

  def create(resource: Resource): F[Status] =
    httpClient.fetch(POST(resource, config.server.resolve(resourceUri), config.authorization.toSeq: _*))(
      EnrichedStatus[F]
    )

  def createOrUpdate(resource: Resource): F[Status] = {
    val fullResourceUri = config.server.resolve(resourceUri) / resource.metadata.get.name.get
    def update =
      httpClient.fetch(
        PATCH(
          resource,
          fullResourceUri,
          `Content-Type`(MediaType.application.`merge-patch+json`) +: config.authorization.toSeq: _*
        )
      )(EnrichedStatus[F])

    httpClient
      .fetch(GET(fullResourceUri, config.authorization.toSeq: _*))(EnrichedStatus.apply[F])
      .flatMap {
        case status if status.isSuccess => update
        case Status.NotFound =>
          create(resource).recoverWith {
            case Status.Conflict => update
          }
      }
  }
} 
Example 16
Source File: Deletable.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.util.CirceEntityCodec._
import com.goyeau.kubernetes.client.util.EnrichedStatus
import io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions
import org.http4s._
import org.http4s.Method._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl

private[client] trait Deletable[F[_]] extends Http4sClientDsl[F] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri

  def delete(name: String, deleteOptions: Option[DeleteOptions] = None): F[Status] =
    httpClient.fetch(
      Request(
        DELETE,
        config.server.resolve(resourceUri) / name,
        headers = Headers(config.authorization.toList),
        body =
          deleteOptions.fold[EntityBody[F]](EmptyBody)(implicitly[EntityEncoder[F, DeleteOptions]].toEntity(_).body)
      )
    )(EnrichedStatus[F])
} 
Example 17
Source File: Proxy.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import org.http4s._
import org.http4s.dsl.impl.Path
import org.http4s.client.Client
import org.http4s.EntityDecoder
import org.http4s.headers.`Content-Type`

private[client] trait Proxy[F[_]] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri

  def proxy(
      name: String,
      method: Method,
      path: Path,
      contentType: `Content-Type` = `Content-Type`(MediaType.text.plain),
      data: Option[String] = None
  ): F[String] =
    httpClient.expect[String](
      Request(
        method,
        config.server.resolve(resourceUri) / name / s"proxy$path",
        headers = Headers(config.authorization.toList),
        body = data.fold[EntityBody[F]](EmptyBody)(
          implicitly[EntityEncoder[F, String]].withContentType(contentType).toEntity(_).body
        )
      )
    )(EntityDecoder.text)
} 
Example 18
Source File: Replaceable.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import scala.language.reflectiveCalls
import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.util.CirceEntityCodec._
import com.goyeau.kubernetes.client.util.EnrichedStatus
import io.circe._
import io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
import org.http4s._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.Method._

private[client] trait Replaceable[F[_], Resource <: { def metadata: Option[ObjectMeta] }] extends Http4sClientDsl[F] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri
  implicit protected def resourceEncoder: Encoder[Resource]

  def replace(resource: Resource): F[Status] =
    httpClient.fetch(
      PUT(
        resource,
        config.server.resolve(resourceUri) / resource.metadata.get.name.get,
        config.authorization.toSeq: _*
      )
    )(EnrichedStatus[F])
} 
Example 19
Source File: GroupDeletable.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.util.EnrichedStatus
import com.goyeau.kubernetes.client.util.Uris.addLabels
import org.http4s._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.Method._

private[client] trait GroupDeletable[F[_]] extends Http4sClientDsl[F] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri

  @deprecated("Use deleteAll() instead", "0.4.0")
  lazy val delete: F[Status] = deleteAll()

  def deleteAll(labels: Map[String, String] = Map.empty): F[Status] = {
    val uri = addLabels(labels, config.server.resolve(resourceUri))
    httpClient.fetch(DELETE(uri, config.authorization.toSeq: _*))(EnrichedStatus[F])
  }
} 
Example 20
Source File: Listable.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.util.CirceEntityCodec._
import com.goyeau.kubernetes.client.util.Uris.addLabels
import io.circe._
import org.http4s._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.Method._

private[client] trait Listable[F[_], Resource] extends Http4sClientDsl[F] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri
  implicit protected def listDecoder: Decoder[Resource]

  @deprecated("Use list() instead", "0.4.0")
  def list: F[Resource] = list()

  def list(labels: Map[String, String] = Map.empty): F[Resource] = {
    val uri = addLabels(labels, config.server.resolve(resourceUri))
    httpClient.expect[Resource](GET(uri, config.authorization.toSeq: _*))
  }
} 
Example 21
Source File: Watchable.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.operation

import cats.effect.Sync
import cats.syntax.either._
import com.goyeau.kubernetes.client.util.Uris.addLabels
import com.goyeau.kubernetes.client.{KubeConfig, WatchEvent}
import fs2.Stream
import io.circe.jawn.CirceSupportParser
import io.circe.{Decoder, Json}
import jawnfs2._
import org.http4s.Method._
import org.http4s._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl
import org.typelevel.jawn.Facade

private[client] trait Watchable[F[_], Resource] extends Http4sClientDsl[F] {
  protected def httpClient: Client[F]
  implicit protected val F: Sync[F]
  protected def config: KubeConfig
  protected def resourceUri: Uri
  protected def watchResourceUri: Uri = resourceUri
  implicit protected def resourceDecoder: Decoder[Resource]

  implicit val parserFacade: Facade[Json] = new CirceSupportParser(None, false).facade

  def watch(labels: Map[String, String] = Map.empty): Stream[F, Either[String, WatchEvent[Resource]]] = {
    val uri = addLabels(labels, config.server.resolve(watchResourceUri))
    val req = GET(uri.+?("watch", "1"), config.authorization.toSeq: _*)
    jsonStream(req).map(_.as[WatchEvent[Resource]].leftMap(_.getMessage))
  }

  private def jsonStream(req: F[Request[F]]): Stream[F, Json] =
    for {
      request <- Stream.eval(req)
      json    <- httpClient.stream(request).flatMap(_.body.chunks.parseJsonStream)
    } yield json
} 
Example 22
Source File: Content.scala    From github   with MIT License 5 votes vote down vote up
package io.chrisdavenport.github.endpoints.repositories

import cats.implicits._
import cats.data._
import cats.effect._
import io.chrisdavenport.github.data.Content._
import org.http4s._
import org.http4s.implicits._
import org.http4s.client.Client

import io.chrisdavenport.github.Auth
import io.chrisdavenport.github.internals.GithubMedia._
import io.chrisdavenport.github.internals.RequestConstructor

object Content {
  def contentsFor[F[_]: Sync](
    owner: String,
    repo: String,
    path: String,
    ref: Option[String],
    auth: Option[Auth]
  ): Kleisli[F, Client[F], Content] =
    RequestConstructor.runRequestWithNoBody[F, Content](
      auth,
      Method.GET,
      (uri"repos" / owner / repo / "contents" / path)
        .withOptionQueryParam("ref", ref)
    )

  def readmeFor[F[_]: Sync](
    owner: String,
    repo: String,
    auth: Option[Auth]
  ) = 
    RequestConstructor.runRequestWithNoBody[F, Content](
      auth,
      Method.GET,
      uri"repos" / owner / repo / "readme"
    )

  def createFile[F[_]: Sync](
    owner: String,
    repo: String,
    createFile: CreateFile,
    auth: Auth
  ) = RequestConstructor.runRequestWithBody[F, CreateFile, ContentResult](
    auth.some,
    Method.PUT,
    uri"repos" / owner /  repo / "contents" / createFile.path,
    createFile
  )

  def updateFile[F[_]: Sync](
    owner: String,
    repo: String,
    updateFile: UpdateFile,
    auth: Auth
  ) = RequestConstructor.runRequestWithBody[F, UpdateFile, ContentResult](
    auth.some,
    Method.PUT,
    uri"repos" / owner /  repo / "contents" / updateFile.path,
    updateFile
  )

  def deleteFile[F[_]: Sync](
    owner: String,
    repo: String,
    deleteFile: DeleteFile,
    auth: Auth
  ) = RequestConstructor.runRequestWithBody[F, DeleteFile, Unit](
    auth.some,
    Method.DELETE,
    uri"repos" / owner /  repo / "contents" / deleteFile.path,
    deleteFile
  )
  
  
} 
Example 23
Source File: S3.scala    From aws4s   with MIT License 5 votes vote down vote up
package org.aws4s.s3

import cats.effect.Effect
import org.aws4s._
import org.http4s.client.Client
import fs2.Stream
import org.aws4s.core.ExtraEntityDecoderInstances._
import org.aws4s.core.Service

case class S3[F[_]: Effect](client: F[Client[F]], region: Region, credentials: () => Credentials) extends Service[F, Nothing] {

  val listBuckets: F[ListBucketsSuccess] = run {
    ListBuckets(region)
  }

  def putObject(
      bucket:         BucketName,
      objectPath:     ObjectPath,
      obj:            F[Stream[F, Byte]],
      payloadSigning: PayloadSigning = PayloadSigning.Unsigned,
  ): F[Unit] = run {
    PutObject(region, bucket, objectPath, obj, payloadSigning)
  }

  def deleteObject(
      bucket:     BucketName,
      objectPath: ObjectPath
  ): F[Unit] = run {
    DeleteObject(region, bucket, objectPath)
  }

  def getObject(
      bucket:     BucketName,
      objectPath: ObjectPath
  ): F[Stream[F, Byte]] = run {
    GetObject(region, bucket, objectPath)
  }
} 
Example 24
Source File: DynamoDb.scala    From aws4s   with MIT License 5 votes vote down vote up
package org.aws4s.dynamodb

import cats.data.NonEmptyList
import cats.effect.Effect
import io.circe.Json
import org.aws4s.core.Service
import org.aws4s.{Credentials, Region}
import org.http4s.client.Client
import org.aws4s.core.ExtraEntityDecoderInstances._

case class DynamoDb[F[_]: Effect](client: F[Client[F]], region: Region, credentials: () => Credentials) extends Service[F, Json] {

  def createTable(tableName: TableName, indices: NonEmptyList[Index], provisionedThroughput: ProvisionedThroughput): F[CreateTableSuccess] =
    run {
      CreateTable(region, indices, tableName, provisionedThroughput)
    }

  def deleteTable(tableName: TableName): F[DeleteTableSuccess] =
    run {
      DeleteTable(region, tableName)
    }
} 
Example 25
Source File: Sqs.scala    From aws4s   with MIT License 5 votes vote down vote up
package org.aws4s.sqs

import cats.effect.Effect
import org.aws4s.core.Service
import org.aws4s.Credentials
import org.http4s.client.Client

case class Sqs[F[_]: Effect](client: F[Client[F]], credentials: () => Credentials) extends Service[F, String] {

  def sendMessage(
      q:                      Queue,
      messageBody:            MessageBody,
      delaySeconds:           Option[DelaySeconds] = None,
      messageDeduplicationId: Option[MessageDeduplicationId] = None
  ): F[SendMessageSuccess] = run {
    SendMessage(
      q,
      messageBody,
      delaySeconds,
      messageDeduplicationId
    )
  }

  def receiveMessage(
      q:                       Queue,
      maxNumberOfMessages:     Option[MaxNumberOfMessages] = None,
      visibilityTimeout:       Option[VisibilityTimeout] = None,
      waitTimeSeconds:         Option[WaitTimeSeconds] = None,
      receiveRequestAttemptId: Option[ReceiveRequestAttemptId] = None,
  ): F[ReceiveMessageSuccess] = run {
    ReceiveMessage(
      q,
      maxNumberOfMessages,
      visibilityTimeout,
      waitTimeSeconds,
      receiveRequestAttemptId
    )
  }

  def deleteMessage(
      q:             Queue,
      receiptHandle: ReceiptHandle,
  ): F[Unit] = run {
    DeleteMessage(
      q,
      receiptHandle
    )
  }
} 
Example 26
Source File: Kms.scala    From aws4s   with MIT License 5 votes vote down vote up
package org.aws4s.kms

import cats.effect.Effect
import io.circe.Json
import org.aws4s.core.ExtraEntityDecoderInstances._
import org.aws4s.core.Service
import org.aws4s.{Credentials, Region}
import org.http4s.client.Client

case class Kms[F[_]: Effect](client: F[Client[F]], region: Region, credentials: () => Credentials) extends Service[F, Json] {

  def encrypt(
      keyId:       KeyId,
      plaintext:   Plaintext,
      context:     Option[EncryptionContext] = None,
      grantTokens: Option[GrantTokens] = None,
  ): F[EncryptSuccess] = run {
    Encrypt(
      region,
      keyId,
      plaintext,
      context,
      grantTokens
    )
  }

  def decrypt(
      ciphertext:  Ciphertext,
      context:     Option[EncryptionContext] = None,
      grantTokens: Option[GrantTokens] = None,
  ): F[DecryptSuccess] = run {
    Decrypt(region, ciphertext, context, grantTokens)
  }

  def createKey(description: Option[KeyDescription] = None): F[CreateKeySuccess] = run {
    CreateKey(region, description)
  }

  def scheduleKeyDeletion(keyId: KeyId, pendingWindowInDays: Option[PendingWindowInDays] = None): F[ScheduleKeyDeletionSuccess] = run {
    ScheduleKeyDeletion(region, keyId, pendingWindowInDays)
  }
} 
Example 27
Source File: Http4sBlazeClientModule.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.client

import cats.effect.{ConcurrentEffect, Resource}
import javax.net.ssl.SSLContext
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.ExecutionContext
object Http4sBlazeClientModule {

  
  def make[F[_]: ConcurrentEffect](
      config: Http4sBlazeClientConfig,
      executionContext: ExecutionContext,
      sslContext: Option[SSLContext] = None
  ): Resource[F, Client[F]] = {
    val builder = BlazeClientBuilder[F](executionContext)
      .withResponseHeaderTimeout(config.responseHeaderTimeout)
      .withIdleTimeout(config.idleTimeout)
      .withRequestTimeout(config.requestTimeout)
      .withConnectTimeout(config.connectTimeout)
      .withUserAgent(config.userAgent)
      .withMaxTotalConnections(config.maxTotalConnections)
      .withMaxWaitQueueLimit(config.maxWaitQueueLimit)
      .withMaxConnectionsPerRequestKey(Function.const(config.maxConnectionsPerRequestkey))
      .withCheckEndpointAuthentication(config.checkEndpointIdentification)
      .withMaxResponseLineSize(config.maxResponseLineSize)
      .withMaxHeaderLength(config.maxHeaderLength)
      .withMaxChunkSize(config.maxChunkSize)
      .withChunkBufferMaxSize(config.chunkBufferMaxSize)
      .withParserMode(config.parserMode)
      .withBufferSize(config.bufferSize)

    sslContext.map(builder.withSslContext).getOrElse(builder).resource
  }

} 
Example 28
Source File: Http4sClientCircuitBreakerModule.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.client.monix.catnap

import cats.effect.{Resource, Sync}
import cats.syntax.applicativeError._
import cats.syntax.flatMap._
import monix.catnap.CircuitBreaker
import org.http4s.Response
import org.http4s.client.Client

object Http4sClientCircuitBreakerModule {

  
  def make[F[_]: Sync](
      client: Client[F],
      circuitBreaker: CircuitBreaker[F],
      httpStatusClassifier: HttpStatusClassifier = HttpStatusClassifier.default
  ): Client[F] = {
    val F = Sync[F]

    class ServerFailure(val response: Response[F], val close: F[Unit]) extends Exception

    Client[F] { request =>
      val raisedInternal = client.run(request).allocated.flatMap {
        case tuple @ (response, _) if !httpStatusClassifier.isServerFailure(response.status) => F.pure(tuple)
        case (response, close)                                                               => F.raiseError[(Response[F], F[Unit])](new ServerFailure(response, close))
      }
      val lifted = circuitBreaker.protect(raisedInternal).recover {
        case serverFailure: ServerFailure => (serverFailure.response, serverFailure.close)
      }
      Resource(lifted)
    }
  }

} 
Example 29
Source File: Http4sRoutingModule.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.example.module

import cats.implicits._
import com.avast.sst.example.service.RandomService
import com.avast.sst.http4s.server.Http4sRouting
import com.avast.sst.http4s.server.micrometer.MicrometerHttp4sServerMetricsModule
import org.http4s.client.Client
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpApp, HttpRoutes}
import zio.Task
import zio.interop.catz._

class Http4sRoutingModule(
    randomService: RandomService,
    client: Client[Task],
    serverMetricsModule: MicrometerHttp4sServerMetricsModule[Task]
) extends Http4sDsl[Task] {

  import serverMetricsModule._

  private val helloWorldRoute = routeMetrics.wrap("hello")(Ok("Hello World!"))

  private val routes = HttpRoutes.of[Task] {
    case GET -> Root / "hello"           => helloWorldRoute
    case GET -> Root / "random"          => randomService.randomNumber.map(_.show).flatMap(Ok(_))
    case GET -> Root / "circuit-breaker" => client.expect[String]("https://httpbin.org/status/500").flatMap(Ok(_))
  }

  val router: HttpApp[Task] = Http4sRouting.make {
    serverMetrics {
      routes
    }
  }

} 
Example 30
Source File: Users.scala    From github   with MIT License 5 votes vote down vote up
package io.chrisdavenport.github.endpoints

import cats.implicits._
import cats.data._
import cats.effect._
import io.chrisdavenport.github.data.Users._
import org.http4s._
import org.http4s.implicits._
import org.http4s.client.Client
import fs2.Stream

import io.chrisdavenport.github.Auth
import io.chrisdavenport.github.internals.GithubMedia._
import io.chrisdavenport.github.internals.RequestConstructor

object Users {

  def userInfoFor[F[_]: Sync](username: String, auth: Option[Auth]): Kleisli[F, Client[F], User] = 
    RequestConstructor.runRequestWithNoBody[F, User](
      auth,
      Method.GET,
      uri"users" / username
    )

  def ownerInfoFor[F[_]: Sync](owner: String, auth: Option[Auth]): Kleisli[F, Client[F], Owner] =
    RequestConstructor.runRequestWithNoBody[F, Owner](
      auth,
      Method.GET,
      uri"users" / owner
    )

  def userInfoAuthenticatedUser[F[_]: Sync](auth: Auth): Kleisli[F, Client[F], User] = 
    RequestConstructor.runRequestWithNoBody[F, User](
      auth.some,
      Method.GET,
      uri"user"
    )

  // We expose this as the returned list for each request
  // that way users can monitor how many requests they make
  // and can know where they stand in regards to their cap.
  def getAllUsers[F[_]: Sync](
    since: Option[String],
    auth: Option[Auth]
  ): Kleisli[Stream[F, ?], Client[F], List[SimpleOwner]] = 
    RequestConstructor.runPaginatedRequest[F, List[SimpleOwner]](
      auth,
      uri"users".withOptionQueryParam("since", since)
    )

  // Patch so presently only updates. Unsure 
  // if null values are removed entirely, so
  // for the first draft dropNull values
  // possibly in the future rework
  def updateAuthenticatedUser[F[_]: Sync](
    auth: Auth,
    name: Option[String],
    email: Option[String],
    blog: Option[String],
    company: Option[String],
    location: Option[String],
    hireable: Option[Boolean],
    bio: Option[String]
  ): Kleisli[F, Client[F], User] = {
    import io.circe._
    def fromOptionJson[A](fa: Option[A])(f: A => Json): Json =
      fa.fold(Json.Null)(f)
    def fromOptionString(fa: Option[String]): Json =
      fromOptionJson(fa)(Json.fromString)
    val json = Json.obj(
      "name" -> fromOptionString(name),
      "email" -> fromOptionString(email),
      "blog" -> fromOptionString(blog),
      "company" -> fromOptionString(company),
      "location" -> fromOptionString(location),
      "hireable" -> fromOptionJson(hireable)(Json.fromBoolean),
      "bio" -> fromOptionString(bio)
    ).dropNullValues
    RequestConstructor.runRequestWithBody[F, Json, User](
      auth.some,
      Method.PATCH,
      uri"user",
      json
    )
  }
} 
Example 31
Source File: Search.scala    From github   with MIT License 5 votes vote down vote up
package io.chrisdavenport.github.endpoints

import cats.data._
import cats.effect._
import fs2.Stream
import io.chrisdavenport.github.data.Repositories._
import org.http4s.implicits._
import org.http4s.client.Client
import io.chrisdavenport.github.Auth
import io.chrisdavenport.github.data.{Order, SearchResult, Sort}
import io.chrisdavenport.github.data.Users.User
import io.chrisdavenport.github.internals.GithubMedia._
import io.chrisdavenport.github.internals.RequestConstructor

object Search {

  
  def users[F[_]: Sync](
    q: String,
    sort: Option[Sort.User],
    order: Option[Order],
    auth: Option[Auth]
  ): Kleisli[Stream[F, *], Client[F], SearchResult[User]] =
    RequestConstructor.runPaginatedRequest[F, SearchResult[User]](
      auth,
      (uri"search" / "users")
        .withQueryParam("q", q)
        .withOptionQueryParam("sort", sort.flatMap(Sort.toOptionalParam))
        .withOptionQueryParam("order", order.flatMap(Order.toOptionalParam))
    )

} 
Example 32
Source File: Http4sFullTracerTest.scala    From guardrail   with MIT License 5 votes vote down vote up
package core.Http4s

import _root_.tracer.client.{ http4s => cdefs }
import _root_.tracer.server.http4s.addresses.{ AddressesHandler, AddressesResource, GetAddressResponse, GetAddressesResponse }
import _root_.tracer.server.http4s.users.{ GetUserResponse, UsersHandler, UsersResource }
import _root_.tracer.server.{ http4s => sdefs }
import _root_.tracer.client.http4s.users.UsersClient
import _root_.tracer.client.http4s.addresses.AddressesClient
import _root_.tracer.server.http4s.Http4sImplicits.TraceBuilder
import cats.effect.IO
import org.http4s.{ Header, HttpRoutes, Request }
import org.http4s.client.Client
import org.http4s.implicits._
import org.http4s.syntax.StringSyntax
import org.scalatest.{ EitherValues, FunSuite, Matchers }

class Http4sFullTracerTest extends FunSuite with Matchers with EitherValues with StringSyntax {

  val traceHeaderKey          = "tracer-label"
  def log(line: String): Unit = ()

  def trace: String => Request[IO] => TraceBuilder[IO] = { name => request =>
    // In a real environment, this would be where you could establish a new
    // tracing context and inject that fresh header value.
    log(s"Expecting all requests to have ${traceHeaderKey} header.")
    traceBuilder(request.headers.get(traceHeaderKey.ci).get.value)
  }

  def traceBuilder(parentValue: String): TraceBuilder[IO] = { name => httpClient =>
    Client { req =>
      httpClient.run(req.putHeaders(Header(traceHeaderKey, parentValue)))
    }
  }

  test("full tracer: passing headers through multiple levels") {
    // Establish the "Address" server
    val server2: HttpRoutes[IO] =
      new AddressesResource(trace).routes(
        new AddressesHandler[IO] {
          def getAddress(respond: GetAddressResponse.type)(id: String)(traceBuilder: TraceBuilder[IO]) =
            IO.pure(if (id == "addressId") {
              respond.Ok(sdefs.definitions.Address(Some("line1"), Some("line2"), Some("line3")))
            } else sdefs.addresses.GetAddressResponse.NotFound)
          def getAddresses(respond: GetAddressesResponse.type)()(traceBuilder: TraceBuilder[IO]) =
            IO.pure(sdefs.addresses.GetAddressesResponse.NotFound)
        }
      )

    // Establish the "User" server
    val server1: HttpRoutes[IO] =
      new UsersResource(trace).routes(
        new UsersHandler[IO] {
          // ... using the "Address" server explicitly in the addressesClient
          val addressesClient = AddressesClient.httpClient(Client.fromHttpApp(server2.orNotFound))
          def getUser(respond: GetUserResponse.type)(id: String)(traceBuilder: TraceBuilder[IO]) =
            addressesClient
              .getAddress(traceBuilder, "addressId")
              .map {
                case cdefs.addresses.GetAddressResponse.Ok(address) =>
                  respond.Ok(sdefs.definitions.User("1234", sdefs.definitions.UserAddress(address.line1, address.line2, address.line3)))
                case cdefs.addresses.GetAddressResponse.NotFound => respond.NotFound
              }
        }
      )

    // Build a UsersClient using the User server
    val usersClient = UsersClient.httpClient(Client.fromHttpApp(server1.orNotFound))
    // As this is the entry point, we either have a tracing header from
    // somewhere else, or we generate one for top-level request.
    val testTrace = traceBuilder("top-level-request")

    // Make a request against the mock servers using a hard-coded user ID
    val retrieved: cdefs.users.GetUserResponse = usersClient.getUser(testTrace, "1234").attempt.unsafeRunSync().right.value

    retrieved shouldBe cdefs.users.GetUserResponse
      .Ok(cdefs.definitions.User("1234", cdefs.definitions.UserAddress(Some("line1"), Some("line2"), Some("line3"))))
  }
} 
Example 33
Source File: Forks.scala    From github   with MIT License 5 votes vote down vote up
package io.chrisdavenport.github.endpoints.repositories

import cats.implicits._
import cats.data._
import cats.effect._

import fs2.Stream

import io.chrisdavenport.github.Auth
import io.chrisdavenport.github.data.Repositories.Repo
import io.chrisdavenport.github.data.Sort
import io.chrisdavenport.github.internals.GithubMedia._
import io.chrisdavenport.github.internals.RequestConstructor

import org.http4s._
import org.http4s.implicits._
import org.http4s.client.Client

object Forks {
  
  def list[F[_]: Sync](
    owner: String,
    repo: String,
    sort: Option[Sort.Fork],
    auth: Auth
  ): Kleisli[Stream[F, *], Client[F], List[Repo]] =
  RequestConstructor.runPaginatedRequest[F, List[Repo]](
    auth.some,
    (uri"repos" / owner / repo / "forks")
      .withOptionQueryParam("sort", sort.flatMap(Sort.toOptionalParam))
  )
} 
Example 34
Source File: Repositories.scala    From github   with MIT License 5 votes vote down vote up
package io.chrisdavenport.github.endpoints

import cats.implicits._
import cats.data._
import cats.effect._
import io.chrisdavenport.github.data.Repositories._
import org.http4s._
import org.http4s.implicits._
import org.http4s.client.Client

import io.chrisdavenport.github.Auth
import io.chrisdavenport.github.internals.RequestConstructor


object Repositories {

  def repository[F[_]: Sync](
    owner: String,
    repo: String,
    auth: Option[Auth]
  ): Kleisli[F, Client[F], Repo] = {
    import io.chrisdavenport.github.internals.GithubMedia._
    RequestConstructor.runRequestWithNoBody[F, Repo](
      auth,
      Method.GET,
      uri"repos" / owner / repo
    )
  }

  def createRepo[F[_]: Sync](
    newRepo: NewRepo,
    auth: Auth
  ): Kleisli[F, Client[F], Repo] = {
    import io.chrisdavenport.github.internals.GithubMedia._
    RequestConstructor.runRequestWithBody[F, NewRepo, Repo](
      auth.some,
      Method.POST,
      uri"user/repos",
      newRepo
    )
  }

  def createOrganizationRepo[F[_]: Sync](
    org: String,
    newRepo: NewRepo,
    auth: Auth
  ): Kleisli[F, Client[F], Repo] = {
    import io.chrisdavenport.github.internals.GithubMedia._
    RequestConstructor.runRequestWithBody[F, NewRepo, Repo](
      auth.some,
      Method.POST,
      uri"orgs" / org / "repos",
      newRepo
    )
  }

  def edit[F[_]: Sync](
    owner: String,
    repo: String,
    editRepo: EditRepo,
    auth: Auth
  ): Kleisli[F, Client[F], Repo] = {
    import io.chrisdavenport.github.internals.GithubMedia._
    RequestConstructor.runRequestWithBody[F, EditRepo, Repo](
      auth.some,
      Method.PATCH,
      uri"repos" / owner / repo,
      editRepo
    )
  }

  
  def delete[F[_]: Sync](
    owner: String,
    repo: String,
    auth: Auth
  ): Kleisli[F, Client[F], Unit] =
    RequestConstructor.runRequestWithNoBody[F, Unit](
      auth.some,
      Method.DELETE,
      uri"repos" / owner / repo
    )

} 
Example 35
Source File: AdserverHttpClientBuilder.scala    From scala-openrtb   with Apache License 2.0 5 votes vote down vote up
package com.powerspace.openrtb.examples.rtb.http4s.adserver

import com.google.openrtb.{BidRequest, BidResponse}
import com.powerspace.openrtb.examples.rtb.http4s.common.ExampleSerdeModule
import com.powerspace.openrtb.json.SerdeModule
import io.circe.{Decoder, Encoder}
import monix.eval.Task
import org.http4s.Uri.{Authority, RegName, Scheme}
import org.http4s.client.Client
import org.http4s.{EntityDecoder, EntityEncoder, Method, Request, Uri}

object AdserverHttpClientBuilder {

  import org.http4s.circe._

  val serdeModule: SerdeModule = ExampleSerdeModule

  implicit val bidRequestEncoder: Encoder[BidRequest] = serdeModule.bidRequestEncoder
  implicit val bidRequestEntityEncoder: EntityEncoder[Task, BidRequest] = jsonEncoderOf[Task, BidRequest]

  implicit val bidResponseDecoder: Decoder[BidResponse] = serdeModule.bidResponseDecoder
  implicit val bidResponseEntityDecoder: EntityDecoder[Task, BidResponse] = jsonOf[Task, BidResponse]

  def bid(client: Client[Task], bidRequest: BidRequest): Task[Option[BidResponse]] = {
    val url = Uri(
      scheme = Some(Scheme.http),
      authority = Some(Authority(host = RegName("localhost"), port = Some(9000))),
      path = "/bid"
    )

    val httpRequest = Request[Task](
      method = Method.POST,
      uri = url
    ).withEntity[BidRequest](bidRequest)

    client.expectOption[BidResponse](httpRequest)
  }
} 
Example 36
Source File: AdserverApp.scala    From scala-openrtb   with Apache License 2.0 5 votes vote down vote up
package com.powerspace.openrtb.examples.rtb.http4s.adserver

import cats.effect.Resource
import com.google.openrtb.{BidRequest, BidResponse}
import com.powerspace.openrtb.examples.rtb.http4s.common.ExampleSerdeModule
import io.circe.{Encoder, Json}
import monix.eval.Task
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.duration.Duration

object AdserverApp extends App {

  import monix.execution.Scheduler.Implicits.global

  val httpClient: Resource[Task, Client[Task]] = buildHttpClient()
  val potentialBidResponse = httpBid(httpClient)
  private val bidRequest = Adserver.buildBidRequest()

  potentialBidResponse
    .map(bidResponse => {
      bidResponse.foreach(br => println(buildAuctionString(br)))
    })
    .runSyncUnsafe(Duration.Inf)

  private def buildHttpClient(): Resource[Task, Client[Task]] = {
    BlazeClientBuilder[Task](global).resource
  }

  private def httpBid(httpClient: Resource[Task, Client[Task]]) =
    httpClient.use(AdserverHttpClientBuilder.bid(_, bidRequest))

  private def buildAuctionString(bidResponse: BidResponse) = {
    case class Auction(bidRequest: BidRequest, bidResponse: BidResponse)

    val auctionEncoder = new Encoder[Auction] {
      override def apply(auction: Auction): Json = Json.obj(
        ("request", ExampleSerdeModule.bidRequestEncoder.apply(auction.bidRequest)),
        ("response", ExampleSerdeModule.bidResponseEncoder.apply(auction.bidResponse))
      )
    }

    auctionEncoder(Auction(bidRequest, bidResponse)).toString()
  }
} 
Example 37
Source File: resources.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop

import cats.effect._
import cats.implicits._
import config.data._
import dev.profunktor.redis4cats.{ Redis, RedisCommands }
import dev.profunktor.redis4cats.log4cats._
import eu.timepit.refined.auto._
import io.chrisdavenport.log4cats.Logger
import natchez.Trace.Implicits.noop // needed for skunk
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder
import scala.concurrent.ExecutionContext
import skunk._

final case class AppResources[F[_]](
    client: Client[F],
    psql: Resource[F, Session[F]],
    redis: RedisCommands[F, String, String]
)

object AppResources {

  def make[F[_]: ConcurrentEffect: ContextShift: Logger](
      cfg: AppConfig
  ): Resource[F, AppResources[F]] = {

    def mkPostgreSqlResource(c: PostgreSQLConfig): SessionPool[F] =
      Session
        .pooled[F](
          host = c.host.value,
          port = c.port.value,
          user = c.user.value,
          database = c.database.value,
          max = c.max.value
        )

    def mkRedisResource(c: RedisConfig): Resource[F, RedisCommands[F, String, String]] =
      Redis[F].utf8(c.uri.value)

    def mkHttpClient(c: HttpClientConfig): Resource[F, Client[F]] =
      BlazeClientBuilder[F](ExecutionContext.global)
        .withConnectTimeout(c.connectTimeout)
        .withRequestTimeout(c.requestTimeout)
        .resource

    (
      mkHttpClient(cfg.httpClientConfig),
      mkPostgreSqlResource(cfg.postgreSQL),
      mkRedisResource(cfg.redis)
    ).mapN(AppResources.apply[F])

  }

} 
Example 38
Source File: HttpClients.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.modules

import cats.effect._
import org.http4s.client.Client
import shop.config.data.PaymentConfig
import shop.http.clients._

object HttpClients {
  def make[F[_]: Sync](
      cfg: PaymentConfig,
      client: Client[F]
  ): F[HttpClients[F]] =
    Sync[F].delay(
      new HttpClients[F] {
        def payment: PaymentClient[F] = new LivePaymentClient[F](cfg, client)
      }
    )
}

trait HttpClients[F[_]] {
  def payment: PaymentClient[F]
} 
Example 39
Source File: ExchangeRateService.scala    From core   with Apache License 2.0 5 votes vote down vote up
package com.smartbackpackerapp.service

import java.net.ConnectException

import cats.MonadError
import cats.effect.Effect
import cats.syntax.all._
import com.smartbackpackerapp.common.Log
import com.smartbackpackerapp.config.SBConfiguration
import com.smartbackpackerapp.model.Currency
import io.circe.generic.auto._
import org.http4s.circe._
import org.http4s.client.{Client, UnexpectedStatus}

class ExchangeRateService[F[_] : Effect](client: Client[F],
                                         sbConfig: SBConfiguration[F])
                                        (implicit L: Log[F]) extends AbstractExchangeRateService[F](sbConfig) {

  override protected def retrieveExchangeRate(uri: String): F[CurrencyExchangeDTO] = {
    client.expect[CurrencyExchangeDTO](uri)(jsonOf[F, CurrencyExchangeDTO])
  }

}

abstract class AbstractExchangeRateService[F[_]](sbConfig: SBConfiguration[F])
                                                (implicit F: MonadError[F, Throwable], L: Log[F]) {

  protected val fixerUri: Currency => Currency => F[String] = baseCurrency => foreignCurrency => {
    val uri = sbConfig.fixerBaseUri.map(_.getOrElse("http://localhost:8081"))
    uri.map(x => s"$x/latest?base=${baseCurrency.value}&symbols=${foreignCurrency.value}")
  }

  protected def retrieveExchangeRate(uri: String): F[CurrencyExchangeDTO]

  // We don't want the whole destination service to fail if the exchange rate service is unavailable
  // so the `UnexpectedStatus` and `ConnectException` errors are treated as an empty exchange rate
  def exchangeRateFor(baseCurrency: Currency, foreignCurrency: Currency): F[CurrencyExchangeDTO] = {
    val ifEmpty = CurrencyExchangeDTO.empty(baseCurrency).pure[F]

    def performRequest(uri: String): F[CurrencyExchangeDTO] =
      retrieveExchangeRate(uri).recoverWith {
        case e: ConnectException => L.error(e).flatMap(_ => ifEmpty)
        case _: UnexpectedStatus => ifEmpty
      }

    validateCurrencies(baseCurrency, foreignCurrency).fold(ifEmpty) { _ =>
      for {
        uri <- fixerUri(baseCurrency)(foreignCurrency)
        _   <- L.info(s"Retrieving currency exchange from: $uri")
        er  <- performRequest(uri)
      } yield {
        if (er.rates.nonEmpty) er
        else er.copy(rates = Map(baseCurrency.value -> -1.0))
      }
    }
  }

  private def validateCurrencies(baseCurrency: Currency, foreignCurrency: Currency): Option[Currency] = {
    if (baseCurrency == foreignCurrency) none[Currency]
    else foreignCurrency.some
  }

} 
Example 40
Source File: CustomResourcesApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.crd.{CrdContext, CustomResource, CustomResourceList}
import com.goyeau.kubernetes.client.operation._
import com.goyeau.kubernetes.client.util.CirceEntityCodec._
import com.goyeau.kubernetes.client.util.EnrichedStatus
import io.circe._
import org.http4s.Method._
import org.http4s.client.Client
import org.http4s.implicits._
import org.http4s.{Status, Uri}

private[client] case class CustomResourcesApi[F[_], A, B](
    httpClient: Client[F],
    config: KubeConfig,
    context: CrdContext
)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[CustomResourceList[A, B]],
    encoder: Encoder[CustomResource[A, B]],
    decoder: Decoder[CustomResource[A, B]]
) extends Listable[F, CustomResourceList[A, B]] {

  val resourceUri: Uri = uri"/apis" / context.group / context.version / context.plural

  def namespace(namespace: String): NamespacedCustomResourcesApi[F, A, B] =
    NamespacedCustomResourcesApi(httpClient, config, context, namespace)
}

private[client] case class NamespacedCustomResourcesApi[F[_], A, B](
    httpClient: Client[F],
    config: KubeConfig,
    context: CrdContext,
    namespace: String
)(implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[CustomResource[A, B]],
    val resourceDecoder: Decoder[CustomResource[A, B]],
    val listDecoder: Decoder[CustomResourceList[A, B]]
) extends Creatable[F, CustomResource[A, B]]
    with Replaceable[F, CustomResource[A, B]]
    with Gettable[F, CustomResource[A, B]]
    with Listable[F, CustomResourceList[A, B]]
    with Deletable[F]
    with GroupDeletable[F]
    with Watchable[F, CustomResource[A, B]] {

  val resourceUri: Uri = uri"/apis" / context.group / context.version / "namespaces" / namespace / context.plural

  def updateStatus(name: String, resource: CustomResource[A, B]): F[Status] =
    httpClient.fetch(
      PUT(resource, config.server.resolve(resourceUri / name / "status"), config.authorization.toSeq: _*)
    )(
      EnrichedStatus[F]
    )
} 
Example 41
Source File: CronJobsApi.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.Sync
import com.goyeau.kubernetes.client.KubeConfig
import com.goyeau.kubernetes.client.operation._
import io.circe._
import io.k8s.api.batch.v1beta1.{CronJob, CronJobList}
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.implicits._

private[client] case class CronJobsApi[F[_]](httpClient: Client[F], config: KubeConfig)(implicit
    val F: Sync[F],
    val listDecoder: Decoder[CronJobList],
    encoder: Encoder[CronJob],
    decoder: Decoder[CronJob]
) extends Listable[F, CronJobList] {
  val resourceUri: Uri = uri"/apis" / "batch" / "v1beta1" / "cronjobs"

  def namespace(namespace: String): NamespacedCronJobsApi[F] = NamespacedCronJobsApi(httpClient, config, namespace)
}

private[client] case class NamespacedCronJobsApi[F[_]](httpClient: Client[F], config: KubeConfig, namespace: String)(
    implicit
    val F: Sync[F],
    val resourceEncoder: Encoder[CronJob],
    val resourceDecoder: Decoder[CronJob],
    val listDecoder: Decoder[CronJobList]
) extends Creatable[F, CronJob]
    with Replaceable[F, CronJob]
    with Gettable[F, CronJob]
    with Listable[F, CronJobList]
    with Deletable[F]
    with DeletableTerminated[F]
    with GroupDeletable[F]
    with Watchable[F, CronJob] {
  val resourceUri: Uri = uri"/apis" / "batch" / "v1beta1" / "namespaces" / namespace / "cronjobs"
} 
Example 42
Source File: HttpMetricsSpec.scala    From kamon-http4s   with Apache License 2.0 5 votes vote down vote up
package kamon.http4s

import cats.effect._
import kamon.testkit.InstrumentInspection
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder
import org.scalatest.concurrent.Eventually
import org.scalatest.time.SpanSugar
import org.scalatest.{Matchers, OptionValues, WordSpec}
import cats.implicits._
import kamon.http4s.middleware.server.KamonSupport
import kamon.instrumentation.http.HttpServerMetrics
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.client.Client

import scala.concurrent.ExecutionContext
import org.http4s.implicits._

class HttpMetricsSpec extends WordSpec
  with Matchers
  with Eventually
  with SpanSugar
  with InstrumentInspection.Syntax
  with OptionValues
 {

  implicit val contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  val srv =
    BlazeServerBuilder[IO]
      .bindLocal(43567)
      .withHttpApp(KamonSupport(HttpRoutes.of[IO] {
        case GET -> Root / "tracing" / "ok" =>  Ok("ok")
        case GET -> Root / "tracing" / "not-found"  => NotFound("not-found")
        case GET -> Root / "tracing" / "error"  => InternalServerError("This page will generate an error!")
      }, "/127.0.0.1", 43567).orNotFound)
      .resource

  val client =
    BlazeClientBuilder[IO](ExecutionContext.global).withMaxTotalConnections(10).resource

   val metrics =
    Resource.liftF(IO(HttpServerMetrics.of("http4s.server", "/127.0.0.1", 43567)))


  def withServerAndClient[A](f: (Server[IO], Client[IO], HttpServerMetrics.HttpServerInstruments) => IO[A]): A =
   (srv, client, metrics).tupled.use(f.tupled).unsafeRunSync()

  private def get[F[_]: ConcurrentEffect](path: String)(server: Server[F], client: Client[F]): F[String] = {
    client.expect[String](s"http://127.0.0.1:${server.address.getPort}$path")
  }

  "The HttpMetrics" should {

    "track the total of active requests" in withServerAndClient { (server, client, serverMetrics) =>

      val requests = List
        .fill(100) {
          get("/tracing/ok")(server, client)
        }.parSequence_

      val test = IO {
        serverMetrics.activeRequests.distribution().max should be > 1L
        serverMetrics.activeRequests.distribution().min shouldBe 0L
      }
      requests *> test
    }

    "track the response time with status code 2xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/ok")(server, client)).sequence_

      val test = IO(serverMetrics.requestsSuccessful.value should be >= 0L)

      requests *> test
    }

    "track the response time with status code 4xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/not-found")(server, client).attempt).sequence_

      val test = IO(serverMetrics.requestsClientError.value should be >= 0L)

      requests *> test
    }

    "track the response time with status code 5xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/error")(server, client).attempt).sequence_

      val test = IO(serverMetrics.requestsServerError.value should be >= 0L)

      requests *> test
    }
  }
} 
Example 43
Source File: SolrFtsClient.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.ftssolr

import cats.effect._
import cats.implicits._
import fs2.Stream

import docspell.common._
import docspell.ftsclient._

import org.http4s.client.Client
import org.http4s.client.middleware.Logger
import org.log4s.getLogger

final class SolrFtsClient[F[_]: Effect](
    solrUpdate: SolrUpdate[F],
    solrSetup: SolrSetup[F],
    solrQuery: SolrQuery[F]
) extends FtsClient[F] {

  def initialize: F[Unit] =
    solrSetup.setupSchema

  def search(q: FtsQuery): F[FtsResult] =
    solrQuery.query(q)

  def indexData(logger: Logger[F], data: Stream[F, TextData]): F[Unit] =
    modifyIndex(logger, data)(solrUpdate.add)

  def updateIndex(logger: Logger[F], data: Stream[F, TextData]): F[Unit] =
    modifyIndex(logger, data)(solrUpdate.update)

  def modifyIndex(logger: Logger[F], data: Stream[F, TextData])(
      f: List[TextData] => F[Unit]
  ): F[Unit] =
    (for {
      _      <- Stream.eval(logger.debug("Updating SOLR index"))
      chunks <- data.chunks
      res    <- Stream.eval(f(chunks.toList).attempt)
      _ <- res match {
        case Right(()) => Stream.emit(())
        case Left(ex) =>
          Stream.eval(logger.error(ex)("Error updating with chunk of data"))
      }
    } yield ()).compile.drain

  def removeItem(logger: Logger[F], itemId: Ident): F[Unit] =
    logger.debug(s"Remove item '${itemId.id}' from index") *>
      solrUpdate.delete(s"${Field.itemId.name}:${itemId.id}", None)

  def removeAttachment(logger: Logger[F], attachId: Ident): F[Unit] =
    logger.debug(s"Remove attachment '${attachId.id}' from index") *>
      solrUpdate.delete(s"${Field.attachmentId.name}:${attachId.id}", None)

  def clearAll(logger: Logger[F]): F[Unit] =
    logger.info("Deleting complete full-text index!") *>
      solrUpdate.delete("*:*", Option(0))

  def clear(logger: Logger[F], collective: Ident): F[Unit] =
    logger.info(s"Deleting full-text index for collective ${collective.id}") *>
      solrUpdate.delete(s"${Field.collectiveId.name}:${collective.id}", Option(0))
}

object SolrFtsClient {
  private[this] val logger = getLogger

  def apply[F[_]: ConcurrentEffect](
      cfg: SolrConfig,
      httpClient: Client[F]
  ): Resource[F, FtsClient[F]] = {
    val client = loggingMiddleware(cfg, httpClient)
    Resource.pure[F, FtsClient[F]](
      new SolrFtsClient(
        SolrUpdate(cfg, client),
        SolrSetup(cfg, client),
        SolrQuery(cfg, client)
      )
    )
  }

  private def loggingMiddleware[F[_]: Concurrent](
      cfg: SolrConfig,
      client: Client[F]
  ): Client[F] =
    Logger(
      logHeaders = true,
      logBody = cfg.logVerbose,
      logAction = Some((msg: String) => Sync[F].delay(logger.trace(msg)))
    )(client)

} 
Example 44
Source File: SolrQuery.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.ftssolr

import cats.effect._

import docspell.ftsclient._
import docspell.ftssolr.JsonCodec._

import _root_.io.circe.syntax._
import org.http4s._
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl

trait SolrQuery[F[_]] {

  def query(q: QueryData): F[FtsResult]

  def query(q: FtsQuery): F[FtsResult]
}

object SolrQuery {
  def apply[F[_]: ConcurrentEffect](cfg: SolrConfig, client: Client[F]): SolrQuery[F] = {
    val dsl = new Http4sClientDsl[F] {}
    import dsl._

    new SolrQuery[F] {
      val url = Uri.unsafeFromString(cfg.url.asString) / "query"

      def query(q: QueryData): F[FtsResult] = {
        val req = Method.POST(q.asJson, url)
        client.expect[FtsResult](req)
      }

      def query(q: FtsQuery): F[FtsResult] = {
        val fq = QueryData(
          cfg,
          List(
            Field.content,
            Field.content_de,
            Field.content_en,
            Field.itemName,
            Field.itemNotes,
            Field.attachmentName
          ),
          List(
            Field.id,
            Field.itemId,
            Field.collectiveId,
            Field("score"),
            Field.attachmentId,
            Field.attachmentName,
            Field.discriminator
          ),
          q
        )
        query(fq)
      }
    }
  }
} 
Example 45
Source File: VCSExtraAlgTest.scala    From scala-steward   with Apache License 2.0 5 votes vote down vote up
package org.scalasteward.core.vcs

import cats.effect.IO
import org.http4s.HttpRoutes
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.scalasteward.core.TestInstances.ioLogger
import org.scalasteward.core.TestSyntax._
import org.scalasteward.core.data.{ReleaseRelatedUrl, Update}
import org.scalasteward.core.mock.MockContext.config
import org.scalasteward.core.util.{HttpExistenceClient, Nel}
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

class VCSExtraAlgTest extends AnyFunSuite with Matchers {
  val routes: HttpRoutes[IO] =
    HttpRoutes.of[IO] {
      case HEAD -> Root / "foo" / "bar" / "compare" / "v0.1.0...v0.2.0" => Ok("exist")
      case HEAD -> Root / "foo" / "buz" / "compare" / "v0.1.0...v0.2.0" => PermanentRedirect()
      case _                                                            => NotFound()
    }

  implicit val client = Client.fromHttpApp[IO](routes.orNotFound)
  implicit val httpExistenceClient =
    HttpExistenceClient.create[IO].allocated.map(_._1).unsafeRunSync()

  val vcsExtraAlg = VCSExtraAlg.create[IO]
  val updateFoo = Update.Single("com.example" % "foo" % "0.1.0", Nel.of("0.2.0"))
  val updateBar = Update.Single("com.example" % "bar" % "0.1.0", Nel.of("0.2.0"))
  val updateBuz = Update.Single("com.example" % "buz" % "0.1.0", Nel.of("0.2.0"))

  test("getBranchCompareUrl") {
    vcsExtraAlg
      .getReleaseRelatedUrls(uri"https://github.com/foo/foo", updateFoo)
      .unsafeRunSync() shouldBe List.empty
    vcsExtraAlg
      .getReleaseRelatedUrls(uri"https://github.com/foo/bar", updateBar)
      .unsafeRunSync() shouldBe List(
      ReleaseRelatedUrl.VersionDiff(uri"https://github.com/foo/bar/compare/v0.1.0...v0.2.0")
    )
    vcsExtraAlg
      .getReleaseRelatedUrls(uri"https://github.com/foo/buz", updateBuz)
      .unsafeRunSync() shouldBe List.empty
  }
} 
Example 46
Source File: HttpJsonClient.scala    From scala-steward   with Apache License 2.0 5 votes vote down vote up
package org.scalasteward.core.util

import cats.effect.Sync
import cats.implicits._
import io.circe.{Decoder, Encoder}
import org.http4s.Method.{GET, POST}
import org.http4s.circe.{jsonEncoderOf, jsonOf}
import org.http4s.client.Client
import org.http4s._
import scala.util.control.NoStackTrace

final class HttpJsonClient[F[_]: Sync](implicit
    client: Client[F]
) {
  type ModReq = Request[F] => F[Request[F]]

  def get[A: Decoder](uri: Uri, modify: ModReq): F[A] =
    request[A](GET, uri, modify)

  def post[A: Decoder](uri: Uri, modify: ModReq): F[A] =
    request[A](POST, uri, modify)

  def postWithBody[A: Decoder, B: Encoder](uri: Uri, body: B, modify: ModReq): F[A] =
    post[A](uri, modify.compose(_.withEntity(body)(jsonEncoderOf[F, B])))

  private def request[A: Decoder](method: Method, uri: Uri, modify: ModReq): F[A] =
    client.expectOr[A](modify(Request[F](method, uri)))(resp =>
      toUnexpectedResponse(uri, method, resp)
    )(jsonOf[F, A].transform(_.leftMap(failure => JsonParseError(uri, method, failure))))

  private def toUnexpectedResponse(
      uri: Uri,
      method: Method,
      response: Response[F]
  ): F[Throwable] = {
    val body = response.body.through(fs2.text.utf8Decode).compile.string
    body.map(UnexpectedResponse(uri, method, response.headers, response.status, _))
  }
}

final case class JsonParseError(
    uri: Uri,
    method: Method,
    underlying: DecodeFailure
) extends DecodeFailure {
  val message = s"uri: $uri\nmethod: $method\nmessage: ${underlying.message}"
  override def cause: Option[Throwable] = underlying.some
  override def toHttpResponse[F[_]](httpVersion: HttpVersion): Response[F] =
    underlying.toHttpResponse(httpVersion)
}

final case class UnexpectedResponse(
    uri: Uri,
    method: Method,
    headers: Headers,
    status: Status,
    body: String
) extends RuntimeException
    with NoStackTrace {
  override def getMessage: String =
    s"uri: $uri\nmethod: $method\nstatus: $status\nheaders: $headers\nbody: $body"
} 
Example 47
Source File: HttpExistenceClient.scala    From scala-steward   with Apache License 2.0 5 votes vote down vote up
package org.scalasteward.core.util

import cats.effect.{Async, Resource}
import cats.implicits._
import com.github.benmanes.caffeine.cache.Caffeine
import io.chrisdavenport.log4cats.Logger
import org.http4s.client.Client
import org.http4s.{Method, Request, Status, Uri}
import org.scalasteward.core.application.Config
import scalacache.CatsEffect.modes._
import scalacache.caffeine.CaffeineCache
import scalacache.{Async => _, _}

final class HttpExistenceClient[F[_]](statusCache: Cache[Status])(implicit
    client: Client[F],
    logger: Logger[F],
    mode: Mode[F],
    F: MonadThrowable[F]
) {
  def exists(uri: Uri): F[Boolean] =
    status(uri).map(_ === Status.Ok).handleErrorWith { throwable =>
      logger.debug(throwable)(s"Failed to check if $uri exists").as(false)
    }

  private def status(uri: Uri): F[Status] =
    statusCache.cachingForMemoizeF(uri.renderString)(None) {
      client.status(Request[F](method = Method.HEAD, uri = uri))
    }
}

object HttpExistenceClient {
  def create[F[_]](implicit
      config: Config,
      client: Client[F],
      logger: Logger[F],
      F: Async[F]
  ): Resource[F, HttpExistenceClient[F]] = {
    val buildCache = F.delay {
      CaffeineCache(
        Caffeine
          .newBuilder()
          .maximumSize(16384L)
          .expireAfterWrite(config.cacheTtl.length, config.cacheTtl.unit)
          .build[String, Entry[Status]]()
      )
    }
    Resource.make(buildCache)(_.close().void).map(new HttpExistenceClient[F](_))
  }
} 
Example 48
Source File: Context.scala    From scala-steward   with Apache License 2.0 5 votes vote down vote up
package org.scalasteward.core.application

import cats.Parallel
import cats.effect._
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import org.http4s.client.Client
import org.http4s.client.asynchttpclient.AsyncHttpClient
import org.scalasteward.core.buildtool.BuildToolDispatcher
import org.scalasteward.core.buildtool.maven.MavenAlg
import org.scalasteward.core.buildtool.mill.MillAlg
import org.scalasteward.core.buildtool.sbt.SbtAlg
import org.scalasteward.core.coursier.{CoursierAlg, VersionsCache}
import org.scalasteward.core.edit.EditAlg
import org.scalasteward.core.git.GitAlg
import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg}
import org.scalasteward.core.nurture.{NurtureAlg, PullRequestRepository}
import org.scalasteward.core.persistence.JsonKeyValueStore
import org.scalasteward.core.repocache.{RefreshErrorAlg, RepoCacheAlg, RepoCacheRepository}
import org.scalasteward.core.repoconfig.RepoConfigAlg
import org.scalasteward.core.scalafix.MigrationAlg
import org.scalasteward.core.scalafmt.ScalafmtAlg
import org.scalasteward.core.update.{FilterAlg, GroupMigrations, PruningAlg, UpdateAlg}
import org.scalasteward.core.util._
import org.scalasteward.core.util.uri._
import org.scalasteward.core.vcs.data.AuthenticatedUser
import org.scalasteward.core.vcs.{VCSApiAlg, VCSExtraAlg, VCSRepoAlg, VCSSelection}

object Context {
  def create[F[_]: ConcurrentEffect: ContextShift: Parallel: Timer](
      args: List[String]
  ): Resource[F, StewardAlg[F]] =
    for {
      blocker <- Blocker[F]
      cliArgs_ <- Resource.liftF(new Cli[F].parseArgs(args))
      implicit0(config: Config) <- Resource.liftF(Config.create[F](cliArgs_))
      implicit0(client: Client[F]) <- AsyncHttpClient.resource[F]()
      implicit0(logger: Logger[F]) <- Resource.liftF(Slf4jLogger.create[F])
      implicit0(httpExistenceClient: HttpExistenceClient[F]) <- HttpExistenceClient.create[F]
      implicit0(user: AuthenticatedUser) <- Resource.liftF(config.vcsUser[F])
      implicit0(fileAlg: FileAlg[F]) = FileAlg.create[F]
      implicit0(migrationAlg: MigrationAlg) <- Resource.liftF(
        MigrationAlg.create[F](config.scalafixMigrations)
      )
      implicit0(groupMigration: GroupMigrations) <- Resource.liftF(GroupMigrations.create[F])
    } yield {
      val kvsPrefix = Some(config.vcsType.asString)
      implicit val dateTimeAlg: DateTimeAlg[F] = DateTimeAlg.create[F]
      implicit val processAlg: ProcessAlg[F] = ProcessAlg.create[F](blocker)
      implicit val workspaceAlg: WorkspaceAlg[F] = WorkspaceAlg.create[F]
      implicit val repoConfigAlg: RepoConfigAlg[F] = new RepoConfigAlg[F]
      implicit val filterAlg: FilterAlg[F] = new FilterAlg[F]
      implicit val gitAlg: GitAlg[F] = GitAlg.create[F]
      implicit val httpJsonClient: HttpJsonClient[F] = new HttpJsonClient[F]
      implicit val repoCacheRepository: RepoCacheRepository[F] =
        new RepoCacheRepository[F](new JsonKeyValueStore("repo_cache", "1", kvsPrefix))
      implicit val selfCheckAlg: SelfCheckAlg[F] = new SelfCheckAlg[F]
      val vcsSelection = new VCSSelection[F]
      implicit val vcsApiAlg: VCSApiAlg[F] = vcsSelection.getAlg(config)
      implicit val vcsRepoAlg: VCSRepoAlg[F] = VCSRepoAlg.create[F](config, gitAlg)
      implicit val vcsExtraAlg: VCSExtraAlg[F] = VCSExtraAlg.create[F]
      implicit val pullRequestRepository: PullRequestRepository[F] =
        new PullRequestRepository[F](new JsonKeyValueStore("pull_requests", "2", kvsPrefix))
      implicit val scalafmtAlg: ScalafmtAlg[F] = ScalafmtAlg.create[F]
      implicit val coursierAlg: CoursierAlg[F] = CoursierAlg.create[F]
      implicit val versionsCache: VersionsCache[F] =
        new VersionsCache[F](config.cacheTtl, new JsonKeyValueStore("versions", "2"))
      implicit val updateAlg: UpdateAlg[F] = new UpdateAlg[F]
      implicit val mavenAlg: MavenAlg[F] = MavenAlg.create[F]
      implicit val sbtAlg: SbtAlg[F] = SbtAlg.create[F]
      implicit val millAlg: MillAlg[F] = MillAlg.create[F]
      implicit val buildToolDispatcher: BuildToolDispatcher[F] = BuildToolDispatcher.create[F]
      implicit val refreshErrorAlg: RefreshErrorAlg[F] =
        new RefreshErrorAlg[F](new JsonKeyValueStore("refresh_error", "1", kvsPrefix))
      implicit val repoCacheAlg: RepoCacheAlg[F] = new RepoCacheAlg[F]
      implicit val editAlg: EditAlg[F] = new EditAlg[F]
      implicit val nurtureAlg: NurtureAlg[F] = new NurtureAlg[F]
      implicit val pruningAlg: PruningAlg[F] = new PruningAlg[F]
      new StewardAlg[F]
    }
} 
Example 49
Source File: Http4sClientEndpointsJsonSchemaTest.scala    From endpoints4s   with MIT License 5 votes vote down vote up
package endpoints4s.http4s.client

import endpoints4s.algebra
import endpoints4s.algebra.client

import cats.effect.Sync
import org.http4s.client.Client
import cats.effect.IO
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.global
import org.http4s.client.asynchttpclient.AsyncHttpClient
import cats.effect.ContextShift
import endpoints4s.algebra.circe
import org.http4s.Uri

class TestJsonSchemaClient[F[_]: Sync](host: Uri, client: Client[F])
    extends Endpoints[F](host, client)
    with BasicAuthentication
    with JsonEntitiesFromCodecs
    with algebra.BasicAuthenticationTestApi
    with algebra.EndpointsTestApi
    with algebra.JsonFromCodecTestApi
    with algebra.SumTypedEntitiesTestApi
    with circe.JsonFromCirceCodecTestApi
    with circe.JsonEntitiesFromCodecs

class Http4sClientEndpointsJsonSchemaTest
    extends client.EndpointsTestSuite[TestJsonSchemaClient[IO]]
    with client.BasicAuthTestSuite[TestJsonSchemaClient[IO]]
    with client.JsonFromCodecTestSuite[TestJsonSchemaClient[IO]]
    with client.SumTypedEntitiesTestSuite[TestJsonSchemaClient[IO]] {

  implicit val ctx: ContextShift[IO] = IO.contextShift(global)

  val (ahc, shutdown) =
    AsyncHttpClient.allocate[IO]().unsafeRunSync()

  val client = new TestJsonSchemaClient[IO](
    Uri.unsafeFromString(s"http://localhost:$wiremockPort"),
    ahc
  )

  def call[Req, Resp](
      endpoint: client.Endpoint[Req, Resp],
      args: Req
  ): Future[Resp] = {
    Thread.sleep(50)
    val eventualResponse = endpoint(args)
    Thread.sleep(50)
    eventualResponse.unsafeToFuture()
  }

  def encodeUrl[A](url: client.Url[A])(a: A): String =
    url.encodeUrl(a).toOption.get.renderString

  clientTestSuite()
  basicAuthSuite()
  jsonFromCodecTestSuite()

  override def afterAll(): Unit = {
    shutdown.unsafeRunSync()
    super.afterAll()
  }

} 
Example 50
Source File: Http4sClientSpec.scala    From canoe   with MIT License 5 votes vote down vote up
package canoe.api.clients

import canoe.api._
import canoe.methods.Method
import canoe.models.InputFile
import cats.effect.IO
import io.circe.{Decoder, Encoder}
import org.http4s.HttpApp
import org.http4s.client.Client
import org.http4s.dsl.io._
import io.circe.Json
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import org.scalatest.freespec.AnyFreeSpec

class Http4sClientSpec extends AnyFreeSpec {
  private case class TestMethod(name: String = "test",
                                encoder: Encoder[String] = Encoder.encodeString,
                                decoder: Decoder[String] = Decoder.decodeString,
                                files: List[InputFile] = Nil)
      extends Method[String, String] {
    def attachments(request: String): List[(String, InputFile)] = files.map("" -> _)
  }

  private implicit val testMethod = TestMethod()

  private def response(s: String) = s"""{"ok" : true, "result" : "$s"}"""

  private implicit val logger = Slf4jLogger.getLogger[IO]

  "Client" - {
    "sends" - {
      "to correct Telegram endpoint" in {
        val client: Client[IO] = Client.fromHttpApp(HttpApp(r => Ok(response(r.uri.toString))))
        val tgClient = new Http4sTelegramClient("token", client)

        assert(tgClient.execute("any").unsafeRunSync() == s"https://api.telegram.org/bottoken/${testMethod.name}")
      }

      val tgClient = new Http4sTelegramClient("", Client.fromHttpApp(HttpApp[IO] { r =>
        Ok(response(r.headers.get(org.http4s.headers.`Content-Type`).map(_.value.replace("\"", "''")).getOrElse("")))
      }))

      "json POST request if attachments contain file upload" in {
        assert(tgClient.execute("any").unsafeRunSync() == "application/json")
      }

      "multipart POST request if attachments contain file upload" in {
        val resp = tgClient.execute("any")(testMethod.copy(files = List(InputFile.Upload("", Array.emptyByteArray))))
        assert(resp.unsafeRunSync().startsWith("multipart/form-data"))
      }
    }

    "encodes/decodes" - {
      "request entity with method encoder" in {
        val tgClient = new Http4sTelegramClient(
          "",
          Client.fromHttpApp(HttpApp[IO](_.bodyAsText.compile.string.flatMap(s => Ok(response(s.replace("\"", "'"))))))
        )
        val res = tgClient.execute("")(testMethod.copy(encoder = Encoder.instance(_ => Json.fromString("encoded"))))

        assert(res.unsafeRunSync() == "'encoded'")
      }

      "result entity with method decoder" in {
        val tgClient = new Http4sTelegramClient("", Client.fromHttpApp(HttpApp[IO](_ => Ok(response("")))))
        val res = tgClient.execute("")(testMethod.copy(decoder = Decoder.const("decoded")))

        assert(res.unsafeRunSync() == "decoded")
      }
    }

    "handles" - {
      "decode failure as ResponseDecodingError" in {
        val tgClient = new Http4sTelegramClient("", Client.fromHttpApp(HttpApp[IO](_ => Ok("{}"))))

        assertThrows[ResponseDecodingError](tgClient.execute("any").unsafeRunSync())
      }

      "unsuccessful result as FailedMethod" in {
        val response = """{"ok" : false, "result" : "any"}"""
        val tgClient = new Http4sTelegramClient("", Client.fromHttpApp(HttpApp[IO](_ => Ok(response))))

        assertThrows[FailedMethod[String, String]](tgClient.execute("any").unsafeRunSync())
      }
    }
  }
} 
Example 51
Source File: Http4sTelegramClient.scala    From canoe   with MIT License 5 votes vote down vote up
package canoe.api.clients

import canoe.api.{FailedMethod, ResponseDecodingError, TelegramClient}
import canoe.methods.Method
import canoe.models.{InputFile, Response => TelegramResponse}
import cats.effect.Sync
import cats.syntax.all._
import fs2.Stream
import io.chrisdavenport.log4cats.Logger
import org.http4s._
import org.http4s.circe._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl._
import org.http4s.multipart.{Multipart, Part}

private[api] class Http4sTelegramClient[F[_]: Sync: Logger](token: String, client: Client[F])
    extends TelegramClient[F] {

  private val botApiUri: Uri = Uri.unsafeFromString("https://api.telegram.org") / s"bot$token"

  def execute[Req, Res](request: Req)(implicit M: Method[Req, Res]): F[Res] = {

    val req = prepareRequest(botApiUri / M.name, M, request)

    implicit val decoder: EntityDecoder[F, TelegramResponse[Res]] =
      jsonOf(Sync[F], TelegramResponse.decoder(M.decoder))

    F.debug(s"Executing '${M.name}' Telegram method.") *>
      client
        .expect[TelegramResponse[Res]](req)
        .recoverWith { case error: InvalidMessageBodyFailure => handleUnknownEntity(M.name, request, error) }
        .flatMap(handleTelegramResponse(M, request))
  }

  private def handleUnknownEntity[I, A](method: String, input: I, error: InvalidMessageBodyFailure): F[A] =
    F.error(
      s"Received unknown Telegram entity during execution of '$method' method. \nInput data: $input. \n${error.details}"
    ) *>
      ResponseDecodingError(error.details.dropWhile(_ != '{')).raiseError[F, A]

  private def prepareRequest[Req, Res](url: Uri, method: Method[Req, Res], action: Req): F[Request[F]] = {
    val uploads = method.attachments(action).collect {
      case (name, InputFile.Upload(filename, contents)) =>
        Part.fileData(name, filename, Stream.emits(contents).covary[F])
    }

    if (uploads.isEmpty) jsonRequest(url, method, action)
    else multipartRequest(url, method, action, uploads)
  }

  private def jsonRequest[Req, Res](url: Uri, method: Method[Req, Res], action: Req): F[Request[F]] =
    Method.POST(action, url)(F, jsonEncoderOf(method.encoder))

  private def multipartRequest[Req, Res](url: Uri,
                                         method: Method[Req, Res],
                                         action: Req,
                                         parts: List[Part[F]]): F[Request[F]] = {
    val multipart = Multipart[F](parts.toVector)

    val params =
      method
        .encoder(action)
        .asObject
        .map(
          _.toIterable
            .filterNot(kv => kv._2.isNull || kv._2.isObject)
            .map { case (k, j) => k -> j.toString }
            .toMap
        )
        .getOrElse(Map.empty)

    val urlWithQueryParams = params.foldLeft(url) {
      case (url, (key, value)) => url.withQueryParam(key, value)
    }

    Method.POST(multipart, urlWithQueryParams).map(_.withHeaders(multipart.headers))
  }

  private def handleTelegramResponse[A, I, C](m: Method[I, A], input: I)(response: TelegramResponse[A]): F[A] =
    response match {
      case TelegramResponse(true, Some(result), _, _, _) => result.pure[F]

      case failed =>
        F.error(s"Received failed response from Telegram: $failed. Method name: ${m.name}, input data: $input") *>
          FailedMethod(m, input, failed).raiseError[F, A]
    }
} 
Example 52
Source File: SolrUpdate.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.ftssolr

import cats.effect._

import docspell.ftsclient._
import docspell.ftssolr.JsonCodec._

import _root_.io.circe._
import _root_.io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl

trait SolrUpdate[F[_]] {

  def add(tds: List[TextData]): F[Unit]

  def update(tds: List[TextData]): F[Unit]

  def delete(q: String, commitWithin: Option[Int]): F[Unit]
}

object SolrUpdate {

  def apply[F[_]: ConcurrentEffect](cfg: SolrConfig, client: Client[F]): SolrUpdate[F] = {
    val dsl = new Http4sClientDsl[F] {}
    import dsl._

    new SolrUpdate[F] {
      val url = (Uri.unsafeFromString(cfg.url.asString) / "update")
        .withQueryParam("commitWithin", cfg.commitWithin.toString)
        .withQueryParam("overwrite", "true")
        .withQueryParam("wt", "json")

      def add(tds: List[TextData]): F[Unit] = {
        val req = Method.POST(tds.asJson, url)
        client.expect[Unit](req)
      }

      def update(tds: List[TextData]): F[Unit] = {
        val req = Method.POST(tds.filter(minOneChange).map(SetFields).asJson, url)
        client.expect[Unit](req)
      }

      def delete(q: String, commitWithin: Option[Int]): F[Unit] = {
        val uri = commitWithin match {
          case Some(n) =>
            if (n <= 0)
              url.removeQueryParam("commitWithin").withQueryParam("commit", "true")
            else url.withQueryParam("commitWithin", n.toString)
          case None =>
            url
        }
        val req = Method.POST(Delete(q).asJson, uri)
        client.expect[Unit](req)
      }

      private val minOneChange: TextData => Boolean =
        _ match {
          case td: TextData.Attachment =>
            td.name.isDefined || td.text.isDefined
          case td: TextData.Item =>
            td.name.isDefined || td.notes.isDefined
        }
    }
  }

  case class Delete(query: String)
  object Delete {
    implicit val jsonEncoder: Encoder[Delete] =
      new Encoder[Delete] {
        def apply(d: Delete): Json =
          Json.obj(
            ("delete", Json.obj("query" -> d.query.asJson))
          )
      }
  }
} 
Example 53
Source File: KamonSupport.scala    From kamon-http4s   with Apache License 2.0 5 votes vote down vote up
package kamon.http4s
package middleware.client

import cats.effect.{Effect, Resource}
import cats.implicits._
import com.typesafe.config.Config
import kamon.Kamon
import kamon.context.Context
import kamon.instrumentation.http.HttpClientInstrumentation
import org.http4s.{Request, Response}
import org.http4s.client.Client

object KamonSupport {

  private var _instrumentation = instrumentation(Kamon.config())

  private def instrumentation(kamonConfig: Config): HttpClientInstrumentation = {
    val httpClientConfig = kamonConfig.getConfig("kamon.instrumentation.http4s.client")
    HttpClientInstrumentation.from(httpClientConfig, "http4s.client")
  }

  Kamon.onReconfigure(newConfig => _instrumentation = instrumentation(newConfig))


  def apply[F[_]](underlying: Client[F])(implicit F:Effect[F]): Client[F] = Client { request =>

    for {
      ctx <- Resource.liftF(F.delay(Kamon.currentContext()))
      k   <- kamonClient(underlying)(request)(ctx)(_instrumentation)
    } yield k
  }


  private def kamonClient[F[_]](underlying: Client[F])
                               (request: Request[F])
                               (ctx: Context)
                               (instrumentation: HttpClientInstrumentation)
                               (implicit F:Effect[F]): Resource[F, Response[F]] =
    for {
      requestHandler  <- Resource.liftF(F.delay(instrumentation.createHandler(getRequestBuilder(request), ctx)))
      response        <- underlying.run(requestHandler.request).attempt
      trackedResponse <- Resource.liftF(handleResponse(response, requestHandler, instrumentation.settings))
    } yield trackedResponse

  def handleResponse[F[_]](
                       response: Either[Throwable, Response[F]],
                       requestHandler: HttpClientInstrumentation.RequestHandler[Request[F]],
                       settings: HttpClientInstrumentation.Settings
                     )(implicit F:Effect[F]): F[Response[F]] =
      response match {
        case Right(res) =>
          if(res.status.code == 404) requestHandler.span.name(settings.defaultOperationName)
          requestHandler.processResponse(getResponseBuilder(res))
          F.delay(res)
        case Left(error) =>
          requestHandler.span.fail(error).finish()
          F.raiseError(error)
      }

} 
Example 54
Source File: JdkHttpClient.scala    From http4s-jdk-http-client   with Apache License 2.0 5 votes vote down vote up
package org.http4s.client.jdkhttpclient

import java.net.URI
import java.net.http.HttpRequest.BodyPublishers
import java.net.http.HttpResponse.BodyHandlers
import java.net.http.{HttpClient, HttpRequest, HttpResponse}
import java.nio.ByteBuffer
import java.util
import java.util.concurrent.Flow

import cats.ApplicativeError
import cats.effect._
import cats.implicits._
import fs2.concurrent.SignallingRef
import fs2.interop.reactivestreams._
import fs2.{Chunk, Stream}
import org.http4s.client.Client
import org.http4s.client.jdkhttpclient.compat.CollectionConverters._
import org.http4s.internal.fromCompletionStage
import org.http4s.util.CaseInsensitiveString
import org.http4s.{Header, Headers, HttpVersion, Request, Response, Status}
import org.reactivestreams.FlowAdapters

object JdkHttpClient {

  
  def simple[F[_]](implicit F: ConcurrentEffect[F], CS: ContextShift[F]): F[Client[F]] =
    F.delay(HttpClient.newHttpClient()).map(apply(_))

  def convertHttpVersionFromHttp4s[F[_]](
      version: HttpVersion
  )(implicit F: ApplicativeError[F, Throwable]): F[HttpClient.Version] =
    version match {
      case HttpVersion.`HTTP/1.1` => HttpClient.Version.HTTP_1_1.pure[F]
      case HttpVersion.`HTTP/2.0` => HttpClient.Version.HTTP_2.pure[F]
      case _ => F.raiseError(new IllegalArgumentException("invalid HTTP version"))
    }

  // see jdk.internal.net.http.common.Utils#DISALLOWED_HEADERS_SET
  private val restrictedHeaders =
    Set(
      "connection",
      "content-length",
      "date",
      "expect",
      "from",
      "host",
      "upgrade",
      "via",
      "warning"
    ).map(CaseInsensitiveString(_))
} 
Example 55
Source File: Dhall.scala    From http4s-jdk-http-client   with Apache License 2.0 5 votes vote down vote up
import cats.effect._
import java.nio.file.{Files, Paths}
import org.dhallj.core.Expr
import org.dhallj.core.converters.JsonConverter
import org.dhallj.imports.syntax._
import org.dhallj.parser.DhallParser
import org.dhallj.yaml.YamlConverter
import org.http4s.client.Client
import org.http4s.client.jdkhttpclient.JdkHttpClient
import sbt.{IO => _, _}
import scala.concurrent.ExecutionContext
import upickle.default.{ReadWriter, macroRW}

object Dhall {

  lazy val convertDhall = taskKey[Unit]("Generate YAML/JSON from Dhall.")

  private lazy val http = {
    implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
    JdkHttpClient.simple[IO].unsafeRunSync()
  }

  private def loadDhall(expr: String): Expr = {
    implicit val c: Client[IO] = http
    DhallParser
      .parse(expr)
      .normalize()
      .resolveImports[IO]
      .unsafeRunSync()
      .normalize()
  }

  val convertDhallTask = convertDhall := {
    val baseDir = (Keys.baseDirectory in LocalRootProject).value.absolutePath
    def convertYaml(from: String, to: String): Unit = {
      val dhall = loadDhall(s"$baseDir/dhall/$from.dhall")
      val yaml = YamlConverter.toYamlString(dhall)
      Files.writeString(Paths.get(s"$baseDir/$to"), yaml)
    }
    List("ci", "release", "dhall").foreach { file =>
      convertYaml(file, s".github/workflows/$file.yml")
    }
    convertYaml("mergify", s".mergify.yml")
  }

  case class ScalaVersions(default: String, all: List[String])
  object ScalaVersions { implicit val rw: ReadWriter[ScalaVersions] = macroRW }

  val scalaVersions = settingKey[ScalaVersions]("Read the Scala versions via Dhall")

  val scalaVersionsImpl = scalaVersions := {
    val baseDir = (Keys.baseDirectory in LocalRootProject).value.absolutePath
    val dhall = loadDhall(s"$baseDir/dhall/scalaVersions.dhall")
    val json = JsonConverter.toCompactString(dhall)
    upickle.default.read[ScalaVersions](json)
  }

} 
Example 56
Source File: main.scala    From seals   with Apache License 2.0 5 votes vote down vote up
package com.example.messaging

import scala.concurrent.ExecutionContext.Implicits.global

import cats.implicits._
import cats.effect.{ IO, IOApp, ExitCode }

import org.http4s._
import org.http4s.dsl.io._
import org.http4s.client.Client
import org.http4s.circe._
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.server.Router
import org.http4s.implicits._

import dev.tauri.seals._
import dev.tauri.seals.circe.Codecs._

object Protocol {
  final case class Ping(seqNr: Long, payload: Vector[Int])
  final case class Pong(seqNr: Long)
  final case class PingIncompatible(seqNr: Long, payload: Vector[Int], flags: Int)
}

object MyClient extends IOApp {

  import org.http4s.client.blaze._
  import Protocol._

  override def run(args: List[String]): IO[ExitCode] = {
    BlazeClientBuilder[IO](global).resource.use { client =>
      for {
        pongGood <- ping(client, jsonEncoderOf[IO, Envelope[Ping]].toEntity(
          Envelope(Ping(42L, Vector(1, 2, 3, 4)))
        ))
        _ <- IO { assert(pongGood == Pong(42L)) }
        _ <- IO { println(pongGood) }
        pongBad <- ping(client, jsonEncoderOf[IO, Envelope[PingIncompatible]].toEntity(
          Envelope(PingIncompatible(99L, Vector(4, 5), 0))
        ))
        _ <- IO { println(pongBad) }
      } yield ExitCode.Success
    }
  }

  def ping(client: Client[IO], ping: Entity[IO]): IO[Pong] = {
    for {
      pong <- client
        .expect(Request(
          POST,
          Uri(authority = Some(Uri.Authority(port = Some(1234))), path = "/test"),
          body = ping.body
        ))(jsonOf[IO, Envelope[Pong]])
    } yield pong.value
  }
}

object MyServer extends IOApp {

  import org.http4s.server.blaze._
  import Protocol._

  val service = HttpRoutes.of[IO] {
    case p @ POST -> Root / "test" =>
      for {
        env <- p.as(implicitly, jsonOf[IO, Envelope[Ping]])
        resp <- Ok(Envelope(Pong(env.value.seqNr)))(implicitly, jsonEncoderOf)
      } yield resp
  }

  override def run(args: List[String]): IO[ExitCode] = {
    BlazeServerBuilder[IO]
      .bindHttp(1234, "localhost")
      .withHttpApp(Router("/" -> service).orNotFound)
      .serve
      .compile
      .drain
      .as(ExitCode.Success)
  }
} 
Example 57
Source File: Github.scala    From sonar-scala   with GNU Lesser General Public License v3.0 5 votes vote down vote up
package com.mwz.sonar.scala
package pr
package github

import cats.effect.Sync
import cats.syntax.flatMap._
import com.mwz.sonar.scala.pr.github.Codec._
import io.circe.generic.auto._
import mouse.boolean._
import org.http4s.client.Client
import org.http4s.{Header, Headers, Method, Request, Uri}

trait Github[F[_]] {
  def authenticatedUser: F[User]
  def pullRequest: F[PullRequest]
  def comments: F[List[Comment]]
  def createComment(comment: NewComment): F[Unit]
  def files: F[List[File]]
  def createStatus(sha: String, status: NewStatus): F[Unit]
}

object Github {
  def apply[F[_]: Sync](client: Client[F], pr: GlobalConfig.PullRequest): Github[F] =
    new Github[F] {
      val auth: Header = Header("Authorization", s"token ${pr.github.oauth}")
      val userUri: Uri = pr.github.apiuri / "user"
      val prUri: Uri =
        (pr.github.apiuri / "repos").addPath(pr.github.repository) / "pulls" / pr.prNumber
      val commentsUri: Uri = prUri / "comments"
      val filesUri: Uri = prUri / "files"
      def newStatusUri(sha: String): Uri =
        (pr.github.apiuri / "repos").addPath(pr.github.repository) / "statuses" / sha
      def request(uri: Uri): Request[F] = {
        Request[F](
          uri = uri,
          headers = Headers.of(auth)
        )
      }
      def authenticatedUser: F[User] = client.expect[User](request(userUri))
      def pullRequest: F[PullRequest] = client.expect[PullRequest](request(prUri))
      def comments: F[List[Comment]] = client.expect[List[Comment]](request(commentsUri))
      def createComment(comment: NewComment): F[Unit] = {
        val request: F[Request[F]] = Sync[F].pure(
          Request(Method.POST, commentsUri, headers = Headers.of(auth))
            .withEntity(comment)
        )
        pr.dryRun.fold(Sync[F].unit, client.expect[Comment](request) >> Sync[F].unit)
      }
      def files: F[List[File]] = client.expect[List[File]](request(filesUri))
      def createStatus(sha: String, status: NewStatus): F[Unit] = {
        val request: F[Request[F]] = Sync[F].pure(
          Request(Method.POST, newStatusUri(sha), headers = Headers.of(auth))
            .withEntity(status)
        )
        pr.dryRun.fold(Sync[F].unit, client.expect[Status](request) >> Sync[F].unit)
      }
    }
} 
Example 58
Source File: TracingClient.scala    From opencensus-scala   with Apache License 2.0 5 votes vote down vote up
package io.opencensus.scala.http4s

import cats.effect.{Effect, Resource}
import cats.implicits._
import io.opencensus.scala.Tracing
import io.opencensus.scala.http.propagation.Propagation
import io.opencensus.scala.http.{HttpAttributes => BaseHttpAttributes}
import io.opencensus.scala.http4s.HttpAttributes._
import io.opencensus.scala.http4s.TracingUtils.recordResponse
import io.opencensus.scala.http4s.propagation.Http4sFormatPropagation
import io.opencensus.trace.{Span, Status}
import org.http4s.client.Client
import org.http4s.{Header, Request, Response}

abstract class TracingClient[F[_]: Effect] {

  protected val tracing: Tracing
  protected val propagation: Propagation[Header, Request[F]]

  
  def trace(client: Client[F], parentSpan: Option[Span] = None): Client[F] = {
    val tracedOpen: Request[F] => Resource[F, Response[F]] =
      req =>
        for {
          span <- Resource.liftF(startSpan(parentSpan, req))
          enrichedReq = addTraceHeaders(req, span)
          res <- client
            .run(enrichedReq)
            .onError(traceError(span).andThen(x => Resource.liftF(x)))
        } yield recordResponse(span, tracing)(res)

    Client(tracedOpen)
  }

  private def traceError(span: Span): PartialFunction[Throwable, F[Unit]] = {
    case _ => recordException(span)
  }

  private def startSpan(parentSpan: Option[Span], req: Request[F]) =
    Effect[F].delay(startAndEnrichSpan(req, parentSpan))

  private def startAndEnrichSpan(
      req: Request[F],
      parentSpan: Option[Span]
  ): Span = {
    val name = req.uri.path.toString
    val span = parentSpan.fold(tracing.startSpan(name))(span =>
      tracing.startSpanWithParent(name, span)
    )
    BaseHttpAttributes.setAttributesForRequest(span, req)
    span
  }

  private def addTraceHeaders(request: Request[F], span: Span): Request[F] =
    request.withHeaders(
      request.headers.put(propagation.headersWithTracingContext(span): _*)
    )

  private def recordException(span: Span) =
    Effect[F].delay(tracing.endSpan(span, Status.INTERNAL))
}

object TracingClient {
  def apply[F[_]: Effect]: TracingClient[F] =
    new TracingClient[F] {
      override protected val tracing: Tracing = Tracing
      override protected val propagation: Propagation[Header, Request[F]] =
        new Http4sFormatPropagation[F] {}
    }
} 
Example 59
Source File: MavenCentralClientTest.scala    From zorechka-bot   with MIT License 5 votes vote down vote up
package com.wix.zorechka.clients

import com.wix.zorechka.{Dep, TestHelpers}
import org.http4s.client.Client
import zio.blocking.Blocking
import zio.Task
import zio.test._

object MavenCentralClientSpec extends DefaultRunnableSpec(
  suite("MavenCentralClientTest")(
    testM("return list of deps for an artifact") {

      val env = new Blocking.Live with MavenCentralClient.Live {
        override protected val httpClient: Client[Task] = TestHelpers.httpClient
      }

      val searchDep = Dep("org.scalacheck", "scalacheck_2.12", "1.10.0")

      for {
        result <- env.client.allVersions(searchDep)
      } yield assert(result, Assertion.equalTo(List("1.14.3", "1.14.2", "1.14.1", "1.14.1-RC2",
        "1.14.1-RC1", "1.14.0", "1.13.5", "1.12.6", "1.13.4", "1.11.6").map {
        version => searchDep.copy(version = version)
      }))

    }
  )
) 
Example 60
Source File: TestHelpers.scala    From zorechka-bot   with MIT License 5 votes vote down vote up
package com.wix.zorechka

import com.wix.zorechka.clients.Http4sClient
import org.http4s.client.Client
import zio.{Runtime, Task, ZIO}
import zio.blocking.Blocking
import zio.internal.PlatformLive

object TestHelpers {

  def httpClient: Client[Task] = Runtime(Blocking.Live, PlatformLive.Default)
    .unsafeRunSync(
      ZIO.runtime[Blocking].flatMap { implicit rt =>
        for {
          httpClientReservation <- Http4sClient.newHttpClient.reserve
          httpClient <- httpClientReservation.acquire
        } yield httpClient
      }
    )
    .getOrElse(err => throw err.squash)

} 
Example 61
Source File: MavenCentralClient.scala    From zorechka-bot   with MIT License 5 votes vote down vote up
package com.wix.zorechka.clients

import com.wix.zorechka.Dep
import org.http4s.{EntityDecoder, Header, Headers, Method, Request, Uri}
import zio.{Task, ZIO}
import zio.interop.catz._
import io.circe.generic.auto._
import org.http4s.circe.jsonOf
import org.http4s.client.Client

trait MavenCentralClient {
  val client: MavenCentralClient.Service
}

object MavenCentralClient {
  trait Service {
    def allVersions(dep: Dep): Task[List[Dep]]
  }

  trait Live extends MavenCentralClient {
    protected val httpClient: Client[Task]

    val client = new MavenCentralClient.Service {
      case class Response(response: InnerResponse)
      case class InnerResponse(docs: Seq[Document])
      case class Document(v: String)

      implicit val decoder: EntityDecoder[Task, Response] = jsonOf[Task, Response]

      override def allVersions(dep: Dep): Task[List[Dep]] = {
        ZIO.accessM {
          client =>
            val uri = Uri
              .unsafeFromString("http://search.maven.org/solrsearch/select")
              .withQueryParam("rows", "10")
              .withQueryParam("core", "gav")
              .withQueryParam("q", s""" g:"${dep.groupId}" AND a:"${dep.artifactId}" """)
            println(s"Maven search: ${uri.renderString}")

            val request = Request[Task](Method.GET, uri, headers = Headers.of(Header("Accept", "application/json")))

            httpClient.fetch(request)(response => response.as[Response]).map {
              _.response.docs.map(_.v).map(v => Dep(dep.groupId, dep.artifactId, v)).toList
            }
        }
      }
    }
  }
} 
Example 62
Source File: InternalWhitelistPilotGrader.scala    From pizza-auth-3   with MIT License 5 votes vote down vote up
package moe.pizza.auth.plugins.pilotgraders

import moe.pizza.auth.interfaces.PilotGrader
import moe.pizza.auth.models.Pilot
import moe.pizza.auth.models.Pilot.Status
import moe.pizza.crestapi.CrestApi
import moe.pizza.eveapi._
import org.http4s.client.Client

import scala.concurrent.ExecutionContext
import scala.util.Try

class InternalWhitelistPilotGrader(c: CrestApi, ids: List[Long])(
    implicit val client: Client)
    extends PilotGrader {
  override def grade(p: Pilot): Status.Value = {
    p.getCrestTokens.flatMap { t =>
      Try {
        c.refresh(t.token).unsafePerformSync
      }.toOption
    }.map { f =>
      val verify = c.verify(f.access_token).unsafePerformSync
      verify.CharacterID
    }.find { ids.contains } match {
      case Some(k) =>
        Status.internal
      case None =>
        Status.unclassified
    }

  }
} 
Example 63
Source File: Http4sTextPlainTest.scala    From guardrail   with MIT License 5 votes vote down vote up
package generators.Http4s.Client.contentType

import _root_.tests.contentTypes.textPlain.client.http4s.foo.FooClient
import _root_.tests.contentTypes.textPlain.client.{ http4s => cdefs }
import _root_.tests.contentTypes.textPlain.server.http4s.foo.{ DoBarResponse, DoBazResponse, DoFooResponse, FooHandler, FooResource }
import _root_.tests.contentTypes.textPlain.server.{ http4s => sdefs }
import org.scalatest.{ EitherValues, FunSuite, Matchers }
import org.http4s.dsl.io._
import org.http4s.headers._
import cats.effect.IO
import org.http4s.client.Client
import org.http4s.{ Charset, HttpRoutes, MediaType }

class Http4sTextPlainTest extends FunSuite with Matchers with EitherValues {
  import org.http4s.implicits._
  test("Plain text should be emitted for required parameters (raw)") {
    val route: HttpRoutes[IO] = HttpRoutes.of {
      case req @ POST -> Root / "foo" =>
        if (req.contentType.contains(`Content-Type`(MediaType.text.plain, Charset.`UTF-8`))) {
          for {
            value <- req.as[String]
            resp  <- if (value == "sample") Created() else NotAcceptable()
          } yield resp
        } else NotAcceptable()
    }
    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doFoo("sample").attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoFooResponse.Created
  }

  test("Plain text should be emitted for optional parameters (raw)") {
    val route: HttpRoutes[IO] = HttpRoutes.of {
      case req @ POST -> Root / "bar" =>
        if (req.contentType.contains(`Content-Type`(MediaType.text.plain, Charset.`UTF-8`))) {
          for {
            value <- req.as[String]
            resp  <- if (value == "sample") Created() else NotAcceptable()
          } yield resp
        } else NotAcceptable()
    }
    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doBar(Some("sample")).attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoBarResponse.Created
  }

  test("Plain text should be emitted for required parameters") {
    val route: HttpRoutes[IO] = new FooResource[IO]().routes(new FooHandler[IO] {
      def doFoo(respond: DoFooResponse.type)(body: String): IO[sdefs.foo.DoFooResponse] =
        if (body == "sample") {
          IO.pure(respond.Created)
        } else {
          IO.pure(respond.NotAcceptable)
        }
      def doBar(respond: DoBarResponse.type)(body: Option[String]): IO[sdefs.foo.DoBarResponse] = ???
      def doBaz(respond: DoBazResponse.type)(body: Option[String]): IO[sdefs.foo.DoBazResponse] = ???
    })

    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doFoo("sample").attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoFooResponse.Created
  }

  test("Plain text should be emitted for present optional parameters") {
    val route: HttpRoutes[IO] = new FooResource[IO]().routes(new FooHandler[IO] {
      def doFoo(respond: DoFooResponse.type)(body: String): IO[sdefs.foo.DoFooResponse] = ???
      def doBar(respond: DoBarResponse.type)(body: Option[String]): IO[sdefs.foo.DoBarResponse] =
        if (body.contains("sample")) {
          IO.pure(respond.Created)
        } else {
          IO.pure(respond.NotAcceptable)
        }
      def doBaz(respond: DoBazResponse.type)(body: Option[String]): IO[sdefs.foo.DoBazResponse] = ???
    })

    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doBar(Some("sample")).attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoBarResponse.Created
  }

  test("Plain text should be emitted for missing optional parameters") {
    val route: HttpRoutes[IO] = new FooResource[IO]().routes(new FooHandler[IO] {
      def doFoo(respond: DoFooResponse.type)(body: String): IO[sdefs.foo.DoFooResponse] = ???
      def doBar(respond: DoBarResponse.type)(body: Option[String]): IO[sdefs.foo.DoBarResponse] =
        if (body.isEmpty) {
          IO.pure(respond.Created)
        } else {
          IO.pure(respond.NotAcceptable)
        }
      def doBaz(respond: DoBazResponse.type)(body: Option[String]): IO[sdefs.foo.DoBazResponse] = ???
    })

    val client: Client[IO] = Client.fromHttpApp(route.orNotFound)
    val fooClient          = FooClient.httpClient(client)
    fooClient.doBar(None).attempt.unsafeRunSync().right.value shouldBe cdefs.foo.DoBarResponse.Created
  }
} 
Example 64
Source File: ProjectClientSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import java.util.UUID

import cats.effect.IO
import cats.effect.concurrent.Ref
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.{AbstractCliSpec, Console}
import ch.epfl.bluebrain.nexus.cli.CliError.ClientError.ClientStatusError
import ch.epfl.bluebrain.nexus.cli.config.{AppConfig, EnvConfig}
import ch.epfl.bluebrain.nexus.cli.sse._
import ch.epfl.bluebrain.nexus.cli.utils.Http4sExtras
import izumi.distage.model.definition.ModuleDef
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.{HttpApp, Response, Status}

class ProjectClientSpec extends AbstractCliSpec with Http4sExtras {

  private val projectJson = jsonContentOf("/templates/project.json", replacements)

  type Cache    = Map[(OrgUuid, ProjectUuid), (OrgLabel, ProjectLabel)]
  type CacheRef = Ref[IO, Cache]

  override def overrides: ModuleDef =
    new ModuleDef {
      include(defaultModules)
      make[Client[IO]].from { cfg: AppConfig =>
        val token   = cfg.env.token
        val httpApp = HttpApp[IO] {
          case GET -> `v1` / "projects" / OrgUuidVar(`orgUuid`) / ProjectUuidVar(`projectUuid`) optbearer `token` =>
            Response[IO](Status.Ok).withEntity(projectJson).pure[IO]
          case GET -> `v1` / "projects" / OrgUuidVar(_) / ProjectUuidVar(_) optbearer `token`                     =>
            Response[IO](Status.NotFound).withEntity(notFoundJson).pure[IO]
          case GET -> `v1` / "projects" / OrgUuidVar(_) / ProjectUuidVar(_) bearer (_: BearerToken)               =>
            Response[IO](Status.Forbidden).withEntity(authFailedJson).pure[IO]
        }
        Client.fromHttpApp(httpApp)
      }
      make[CacheRef].fromEffect {
        Ref.of[IO, Cache](Map.empty)
      }
    }

  "A ProjectClient" should {
    "resolve a known (orgUuid, projUuid) pair" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val cl = ProjectClient[IO](client, env, cache, console)
        for {
          labels <- cl.labels(orgUuid, projectUuid)
          _       = labels shouldEqual Right((orgLabel, projectLabel))
        } yield ()
    }
    "resolve from cache a known (orgUuid, projUuid) pair" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val errClient = Client.fromHttpApp(HttpApp[IO] { case GET -> Root => IO.pure(Response[IO](Status.NotFound)) })
        for {
          _      <- ProjectClient[IO](client, env, cache, console).labels(orgUuid, projectUuid)
          labels <- ProjectClient[IO](errClient, env, cache, console).labels(orgUuid, projectUuid)
          _       = labels shouldEqual Right((orgLabel, projectLabel))
        } yield ()
    }
    "fail to resolve an unknown (orgUuid, projUuid) pair" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val cl = ProjectClient[IO](client, env, cache, console)
        for {
          labels <- cl.labels(OrgUuid(UUID.randomUUID()), projectUuid)
          _       = labels shouldEqual Left(ClientStatusError(Status.NotFound, notFoundJson.noSpaces))
        } yield ()
    }
    "fail to resolve a known (orgUuid, projUuid) pair with bad credentials" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val cl = ProjectClient[IO](client, env.copy(token = Some(BearerToken("bad"))), cache, console)
        for {
          labels <- cl.labels(orgUuid, projectUuid)
          _       = labels shouldEqual Left(ClientStatusError(Status.Forbidden, authFailedJson.noSpaces))
        } yield ()
    }
  }
} 
Example 65
Source File: SparqlClientSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import cats.effect.IO
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.{AbstractCliSpec, Console}
import ch.epfl.bluebrain.nexus.cli.CliError.ClientError.{ClientStatusError, ServerStatusError}
import ch.epfl.bluebrain.nexus.cli.config.{AppConfig, EnvConfig}
import ch.epfl.bluebrain.nexus.cli.sse._
import ch.epfl.bluebrain.nexus.cli.utils.Http4sExtras
import izumi.distage.model.definition.ModuleDef
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.headers.`Content-Type`
import org.http4s.{HttpApp, Response, Status, Uri}
import org.scalatest.OptionValues

class SparqlClientSpec extends AbstractCliSpec with Http4sExtras with OptionValues {

  private val sparqlResultsJson = jsonContentOf("/templates/sparql-results.json")
  private val sparqlResults     = sparqlResultsJson.as[SparqlResults].toOption.value
  private val query             = "SELECT * {?s ?p ?o} LIMIT 10"

  override def overrides: ModuleDef =
    new ModuleDef {
      include(defaultModules)
      make[Client[IO]].from { cfg: AppConfig =>
        val token   = cfg.env.token
        val ct      = `Content-Type`(SparqlClient.`application/sparql-query`)
        val view    = cfg.env.defaultSparqlView.renderString
        val httpApp = HttpApp[IO] {
          // success
          case req @ POST -> `v1` / "views" / OrgLabelVar(`orgLabel`) / ProjectLabelVar(
                `projectLabel`
              ) / `view` / "sparql" contentType `ct` optbearer `token` =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.Ok).withEntity(sparqlResultsJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
          // unknown view id
          case req @ POST -> `v1` / "views" / OrgLabelVar(`orgLabel`) / ProjectLabelVar(
                `projectLabel`
              ) / (_: String) / "sparql" contentType `ct` optbearer `token` =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.NotFound).withEntity(notFoundJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
          // unknown token
          case req @ POST -> `v1` / "views" / OrgLabelVar(`orgLabel`) / ProjectLabelVar(
                `projectLabel`
              ) / `view` / "sparql" contentType `ct` optbearer (_: Option[
                BearerToken
              ]) =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.Forbidden).withEntity(authFailedJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
          // other - internal error
          case req @ POST -> "v1" /: (_: Path) contentType `ct` optbearer `token` =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.InternalServerError).withEntity(internalErrorJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
        }
        Client.fromHttpApp(httpApp)
      }
    }

  "A SparqlClient" should {
    "return sparql results" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env, console)
      for {
        results <- cl.query(orgLabel, projectLabel, query)
        _        = results shouldEqual Right(sparqlResults)
      } yield ()
    }
    "return not found" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env, console)
      for {
        results <- cl.query(orgLabel, projectLabel, Uri.unsafeFromString(genString()), query)
        _        = results shouldEqual Left(ClientStatusError(Status.NotFound, notFoundJson.noSpaces))
      } yield ()
    }
    "return internal error" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env, console)
      for {
        results <- cl.query(orgLabel, ProjectLabel(genString()), Uri.unsafeFromString(genString()), query)
        _        = results shouldEqual Left(ServerStatusError(Status.InternalServerError, internalErrorJson.noSpaces))
      } yield ()
    }
    "return bad token" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env.copy(token = Some(BearerToken("bad"))), console)
      for {
        results <- cl.query(orgLabel, projectLabel, query)
        _        = results shouldEqual Left(ClientStatusError(Status.Forbidden, authFailedJson.noSpaces))
      } yield ()
    }

  }
} 
Example 66
Source File: InfluxClient.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import cats.effect.{Sync, Timer}
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli._
import ch.epfl.bluebrain.nexus.cli.config.influx.InfluxConfig
import ch.epfl.bluebrain.nexus.cli.config.{AppConfig, EnvConfig}
import io.circe.Json
import org.http4s.client.Client
import org.http4s.{Method, Request, UrlForm}

trait InfluxClient[F[_]] {

  
  final def apply[F[_]: Sync: Timer](
      client: Client[F],
      config: AppConfig,
      console: Console[F]
  ): InfluxClient[F] = {
    implicit val c: Console[F] = console
    new LiveInfluxDbClient[F](client, config.influx, config.env)
  }
} 
Example 67
Source File: ProjectClient.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import cats.effect.concurrent.Ref
import cats.effect.{Sync, Timer}
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.config.EnvConfig
import ch.epfl.bluebrain.nexus.cli.sse.{OrgLabel, OrgUuid, ProjectLabel, ProjectUuid}
import ch.epfl.bluebrain.nexus.cli.{ClientErrOr, Console}
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
import org.http4s.client.Client
import org.http4s.{Headers, Request}

trait ProjectClient[F[_]] {

  
  final def apply[F[_]: Sync: Timer](
      client: Client[F],
      env: EnvConfig,
      cache: Ref[F, Map[(OrgUuid, ProjectUuid), (OrgLabel, ProjectLabel)]],
      console: Console[F]
  ): ProjectClient[F] = {
    implicit val c: Console[F] = console
    new LiveProjectClient[F](client, env, cache)
  }

  private class LiveProjectClient[F[_]: Timer: Console: Sync](
      client: Client[F],
      env: EnvConfig,
      cache: Ref[F, Map[(OrgUuid, ProjectUuid), (OrgLabel, ProjectLabel)]]
  ) extends AbstractHttpClient[F](client, env)
      with ProjectClient[F] {

    override def labels(org: OrgUuid, proj: ProjectUuid): F[ClientErrOr[(OrgLabel, ProjectLabel)]] =
      cache.get.flatMap { map =>
        map.get((org, proj)) match {
          // value in cache, return
          case Some(value) => F.pure(Right(value))
          // value not in cache, fetch, update and return
          case None        =>
            get(org, proj).flatMap {
              // propagate error
              case l @ Left(_)      => F.pure(l)
              // success, update cache and return
              case r @ Right(value) =>
                cache.modify(m => (m.updated((org, proj), value), value)) *> F.pure(r)
            }
        }
      }

    private def get(org: OrgUuid, proj: ProjectUuid): F[ClientErrOr[(OrgLabel, ProjectLabel)]] = {
      val uri = env.project(org, proj)
      val req = Request[F](uri = uri, headers = Headers(env.authorizationHeader.toList))
      executeParse[NexusAPIProject](req).map {
        case Right(NexusAPIProject(orgLabel, projectLabel)) => Right((orgLabel, projectLabel))
        case Left(err)                                      => Left(err)
      }
    }
  }

  final private[ProjectClient] case class NexusAPIProject(`_organizationLabel`: OrgLabel, `_label`: ProjectLabel)
  private[ProjectClient] object NexusAPIProject {
    implicit val nexusAPIProjectDecoder: Decoder[NexusAPIProject] = deriveDecoder[NexusAPIProject]
  }
} 
Example 68
Source File: SparqlClient.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import cats.effect.{Sync, Timer}
import ch.epfl.bluebrain.nexus.cli.config.EnvConfig
import ch.epfl.bluebrain.nexus.cli.sse.{OrgLabel, ProjectLabel}
import ch.epfl.bluebrain.nexus.cli.{ClientErrOr, Console}
import org.http4s._
import org.http4s.client.Client
import org.http4s.headers.`Content-Type`

trait SparqlClient[F[_]] {

  
  final def apply[F[_]: Sync: Timer](client: Client[F], env: EnvConfig, console: Console[F]): SparqlClient[F] = {
    implicit val c: Console[F] = console
    new LiveSparqlClient[F](client, env)
  }

  final val `application/sparql-query`: MediaType =
    new MediaType("application", "sparql-query")

  final private class LiveSparqlClient[F[_]: Timer: Console: Sync](client: Client[F], env: EnvConfig)
      extends AbstractHttpClient(client, env)
      with SparqlClient[F] {

    override def query(
        org: OrgLabel,
        proj: ProjectLabel,
        view: Option[Uri],
        queryStr: String
    ): F[ClientErrOr[SparqlResults]] = {
      val uri     = env.sparql(org, proj, view.getOrElse(env.defaultSparqlView))
      val headers = Headers(env.authorizationHeader.toList)
      val req     = Request[F](method = Method.POST, uri = uri, headers = headers)
        .withEntity(queryStr)
        .withContentType(`Content-Type`(`application/sparql-query`))
      executeParse[SparqlResults](req)
    }
  }
} 
Example 69
Source File: AbstractHttpClient.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import cats.effect.{Sync, Timer}
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.CliError.ClientError
import ch.epfl.bluebrain.nexus.cli.CliError.ClientError.{SerializationError, Unexpected}
import ch.epfl.bluebrain.nexus.cli.config.EnvConfig
import ch.epfl.bluebrain.nexus.cli.{logRetryErrors, ClientErrOr, Console}
import io.circe.Decoder
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.client.Client
import org.http4s.{Request, Response}
import retry.CatsEffect._
import retry.RetryPolicy
import retry.syntax.all._

import scala.reflect.ClassTag
import scala.util.control.NonFatal

class AbstractHttpClient[F[_]: Timer](client: Client[F], env: EnvConfig)(implicit
    protected val F: Sync[F],
    protected val console: Console[F]
) {

  protected val retry                                = env.httpClient.retry
  protected def successCondition[A]                  = retry.condition.notRetryFromEither[A] _
  implicit protected val retryPolicy: RetryPolicy[F] = retry.retryPolicy
  implicit protected def logOnError[A]               = logRetryErrors[F, A]("interacting with an HTTP API")

  protected def executeDiscard[A](req: Request[F], returnValue: => A): F[ClientErrOr[A]] =
    execute(req, _.body.compile.drain.as(Right(returnValue)))

  protected def executeParse[A: Decoder](req: Request[F])(implicit A: ClassTag[A]): F[ClientErrOr[A]] =
    execute(
      req,
      _.attemptAs[A].value.map(
        _.leftMap(err =>
          SerializationError(err.message, s"The response payload was not of type '${A.runtimeClass.getSimpleName}'")
        )
      )
    )

  private def execute[A](req: Request[F], f: Response[F] => F[ClientErrOr[A]]): F[ClientErrOr[A]] =
    client
      .fetch(req)(ClientError.errorOr[F, A](r => f(r)))
      .recoverWith {
        case NonFatal(err) => F.delay(Left(Unexpected(Option(err.getMessage).getOrElse("").take(30))))
      }
      .retryingM(successCondition[A])
} 
Example 70
Source File: CliModule.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli

import cats.effect.concurrent.Ref
import cats.effect.{ConcurrentEffect, Timer}
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.clients._
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.sse.{OrgLabel, OrgUuid, ProjectLabel, ProjectUuid}
import distage.{ModuleDef, TagK}
import izumi.distage.model.definition.StandardAxis.Repo
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.ExecutionContext
import scala.concurrent.duration.Duration

final class CliModule[F[_]: ConcurrentEffect: Timer: TagK] extends ModuleDef {

  make[Console[F]].tagged(Repo.Prod).from(Console[F])

  make[Client[F]].tagged(Repo.Prod).fromResource {
    BlazeClientBuilder[F](ExecutionContext.global).withIdleTimeout(Duration.Inf).resource
  }

  make[ProjectClient[F]].tagged(Repo.Prod).fromEffect { (cfg: AppConfig, client: Client[F], console: Console[F]) =>
    Ref.of[F, Map[(OrgUuid, ProjectUuid), (OrgLabel, ProjectLabel)]](Map.empty).map { cache =>
      ProjectClient(client, cfg.env, cache, console)
    }
  }

  make[SparqlClient[F]].tagged(Repo.Prod).from { (cfg: AppConfig, client: Client[F], console: Console[F]) =>
    SparqlClient(client, cfg.env, console)
  }

  make[EventStreamClient[F]].tagged(Repo.Prod).from { (cfg: AppConfig, client: Client[F], pc: ProjectClient[F]) =>
    EventStreamClient(client, pc, cfg.env)
  }

  make[InfluxClient[F]].tagged(Repo.Prod).from { (cfg: AppConfig, client: Client[F], console: Console[F]) =>
    InfluxClient(client, cfg, console)
  }
}

object CliModule {

  final def apply[F[_]: ConcurrentEffect: Timer: TagK]: CliModule[F] =
    new CliModule[F]

} 
Example 71
Source File: CrestKeyGrader.scala    From pizza-auth-3   with MIT License 5 votes vote down vote up
package moe.pizza.auth.plugins.pilotgraders

import moe.pizza.auth.interfaces.PilotGrader
import moe.pizza.auth.models.Pilot
import moe.pizza.auth.models.Pilot.Status
import moe.pizza.crestapi.CrestApi
import org.http4s.client.Client

import scala.util.Try


class CrestKeyGrader(c: CrestApi)(implicit val client: Client)
    extends PilotGrader {
  override def grade(p: Pilot): Status.Value = {
    p.getCrestTokens.flatMap { t =>
      Try {
        c.refresh(t.token).unsafePerformSync
      }.toOption
    }.map { f =>
      val verify = c.verify(f.access_token).unsafePerformSync
      verify.CharacterName
    }.find { _ == p.characterName } match {
      case Some(k) =>
        Status.unclassified
      case None =>
        Status.expired
    }

  }
} 
Example 72
Source File: HttpClients.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.network.http.client

import cats.effect._
import javax.net.ssl.SSLContext
import jbok.common.thread.ThreadUtil
import jbok.network.http.client.middleware.{LoggerMiddleware, MetricsMiddleware, RetryMiddleware}
import okhttp3.OkHttpClient
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.client.okhttp.OkHttpBuilder

object HttpClients {

  def withMiddlewares[F[_]](client: Client[F])(implicit F: Concurrent[F], T: Timer[F]): F[Client[F]] =
    MetricsMiddleware(LoggerMiddleware()(RetryMiddleware()(client)))

  def okHttp[F[_]](sslContext: Option[SSLContext] = None)(implicit F: ConcurrentEffect[F], cs: ContextShift[F]): Resource[F, Client[F]] =
    ThreadUtil.blockingThreadPool[F]("jbok-okhttp-client").flatMap { blockEC =>
      val builder = sslContext match {
        case Some(ctx) => new OkHttpClient.Builder().sslSocketFactory(ctx.getSocketFactory)
        case None      => new OkHttpClient.Builder()
      }

      OkHttpBuilder[F](builder.build(), blockEC).resource
    }

  def blaze[F[_]](sslContext: Option[SSLContext] = None)(implicit F: ConcurrentEffect[F]): Resource[F, Client[F]] =
    ThreadUtil.blockingThreadPool[F]("jbok-blaze-client").flatMap { blockEC =>
      BlazeClientBuilder[F](blockEC)
        .withSslContextOption(sslContext)
        .resource
    }
} 
Example 73
Source File: CaldariCrestKeyGrader.scala    From pizza-auth-3   with MIT License 5 votes vote down vote up
package moe.pizza.auth.plugins.pilotgraders

import moe.pizza.auth.interfaces.PilotGrader
import moe.pizza.auth.models.Pilot
import moe.pizza.auth.models.Pilot.Status
import moe.pizza.crestapi.CrestApi
import moe.pizza.eveapi.{EVEAPI}
import org.http4s.client.Client

import scala.util.Try

class CaldariCrestKeyGrader(c: CrestApi, eve: Option[EVEAPI] = None)(
    implicit val client: Client)
    extends PilotGrader {
  val eveapi = eve.getOrElse(new EVEAPI(client))

  override def grade(p: Pilot): Status.Value = {
    p.getCrestTokens.flatMap { t =>
      Try { c.refresh(t.token).unsafePerformSync }.toOption
    }.map { f =>
      val verify = c.verify(f.access_token).unsafePerformSync
      verify.CharacterID
    }.exists { id =>
      eveapi.char.CharacterInfo(id.toInt).unsafePerformSync.result.race == "Caldari"
    } match {
      case true =>
        // caldari is the enemy
        Status.banned
      case false =>
        // non-caldari is okay
        Status.unclassified
    }
  }
} 
Example 74
Source File: LocationManager.scala    From pizza-auth-3   with MIT License 5 votes vote down vote up
package moe.pizza.auth.plugins

import moe.pizza.auth.models.Pilot
import moe.pizza.crestapi.CrestApi
import moe.pizza.eveapi._

import org.http4s.client.Client


object LocationManager {

  def locateUsers(crest: CrestApi)(pilots: List[Pilot])(
      implicit client: Client) = {

    pilots.map { p =>
      p.getCrestTokens.map { token =>
        val refreshed = crest.refresh(token.token).unsafePerformSync
        val verify = crest.verify(refreshed.access_token).unsafePerformSync
        (p, verify.CharacterName,
         crest.character.location(token.characterID, refreshed.access_token))
      }
    }
  }

} 
Example 75
Source File: ConfigFile.scala    From pizza-auth-3   with MIT License 5 votes vote down vote up
package moe.pizza.auth.config

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.JsonNode
import moe.pizza.auth.adapters.GraderChain
import moe.pizza.auth.adapters.PilotGraderLike.PilotGraderFactory
import moe.pizza.auth.interfaces.PilotGrader
import moe.pizza.auth.webapp.oauth.OAuthApplication

import scala.concurrent.ExecutionContext
import org.http4s.client.Client


object ConfigFile {
  case class PingBotConfig(
      host: String,
      password: String
  )
  case class EmbeddedLdapConfig(
      instancePath: String = "./ldap",
      port: Int = 389,
      basedn: String = "ou=pizza",
      host: String = "localhost",
      password: Option[String] = None
  )
  case class AuthGroupConfig(closed: List[String],
                             open: List[String],
                             public: List[String])

  case class AuthConfig(
      domain: String,
      corporation: String,
      alliance: String,
      groupName: String,
      groupShortName: String,
      groups: AuthGroupConfig,
      graders: List[JsonNode],
      pingbot: Option[PingBotConfig],
      restkeys: List[String],
      applications: List[OAuthApplication] = List()
  ) {
    def constructGraders(c: ConfigFile)(
        implicit client: Client): PilotGrader =
      new GraderChain(
        graders.map(g => PilotGraderFactory.fromYaml(g, c)).flatten.toList)

  }
  case class CrestConfig(
      @JsonProperty("login_url") loginUrl: String,
      @JsonProperty("crest_url") crestUrl: String,
      clientID: String,
      secretKey: String,
      redirectUrl: String
  )
  case class ConfigFile(
      crest: CrestConfig,
      auth: AuthConfig,
      embeddedldap: EmbeddedLdapConfig
  )

} 
Example 76
Source File: Update.scala    From pizza-auth-3   with MIT License 5 votes vote down vote up
package moe.pizza.auth.tasks

import moe.pizza.auth.interfaces.PilotGrader
import moe.pizza.auth.models.Pilot
import moe.pizza.crestapi.CrestApi
import moe.pizza.eveapi._
import org.http4s.client.Client

import scala.util.Try

class Update(crest: CrestApi, eveapi: EVEAPI, chain: PilotGrader)(
    implicit val client: Client) {

  def updateUser(p: Pilot): Pilot = {
    val keys = p.getCrestTokens
    val mainkey = keys.head
    Try {
      val charinfo = eveapi.eve.CharacterInfo(mainkey.characterID).unsafePerformSync
      val refreshed = crest.refresh(mainkey.token).unsafePerformSync
      val corpAndAlliance = charinfo match {
        case Left(r) => (r.result.corporation, "")
        case Right(r) => (r.result.corporation, r.result.alliance)
      }
      val pilotWithUpdatedMembership =
        p.copy(corporation = corpAndAlliance._1, alliance = corpAndAlliance._2)
      val gradedPilot = pilotWithUpdatedMembership.copy(
        accountStatus = chain.grade(pilotWithUpdatedMembership))
      gradedPilot
    }.getOrElse(p.copy(accountStatus = Pilot.Status.banned))
  }

} 
Example 77
Source File: UserRegistry.scala    From http4s-tracer   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.tracer.http
package client

import cats.effect.Sync
import cats.syntax.functor._
import dev.profunktor.tracer.model.user.User
import io.circe.syntax._
import org.http4s.Method._
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.client.dsl.Http4sClientDsl

trait UserRegistry[F[_]] {
  def register(user: User): F[Unit]
}

final case class LiveUserRegistry[F[_]: Sync](
    client: Client[F]
) extends UserRegistry[F]
    with Http4sClientDsl[F] {

  private val uri = Uri.uri("https://jsonplaceholder.typicode.com/posts")

  def register(user: User): F[Unit] =
    client.successful(POST(user.asJson, uri)).void
} 
Example 78
Source File: TracedHttpClients.scala    From http4s-tracer   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.tracer.module.tracer

import cats.effect.Sync
import cats.syntax.apply._
import dev.profunktor.tracer.Trace.Trace
import dev.profunktor.tracer.http.client.UserRegistry
import dev.profunktor.tracer.model.user.User
import dev.profunktor.tracer.module.{HttpClients, LiveHttpClients}
import dev.profunktor.tracer.{Trace, TracerLog}
import org.http4s.client.Client

case class TracedHttpClients[F[_]: Sync: λ[T[_] => TracerLog[Trace[T, ?]]]] private (
    client: Client[F]
) extends HttpClients[Trace[F, ?]] {
  private val clients = LiveHttpClients[F](client)

  override val userRegistry: UserRegistry[Trace[F, ?]] = new TracedUserRegistry[F](clients.userRegistry)
}

private[tracer] final class TracedUserRegistry[F[_]: Sync](
    registry: UserRegistry[F]
)(implicit L: TracerLog[Trace[F, ?]])
    extends UserRegistry[Trace[F, ?]] {

  override def register(user: User): Trace[F, Unit] =
    L.info[UserRegistry[F]](s"Registering user: ${user.username.value}") *>
      Trace(_ => registry.register(user))

} 
Example 79
Source File: BackendApp.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.backend

import scala.concurrent.ExecutionContext

import cats.effect._

import docspell.backend.auth.Login
import docspell.backend.ops._
import docspell.backend.signup.OSignup
import docspell.ftsclient.FtsClient
import docspell.joexapi.client.JoexClient
import docspell.store.Store
import docspell.store.queue.JobQueue
import docspell.store.usertask.UserTaskStore

import emil.javamail.{JavaMailEmil, Settings}
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder

trait BackendApp[F[_]] {

  def login: Login[F]
  def signup: OSignup[F]
  def collective: OCollective[F]
  def source: OSource[F]
  def tag: OTag[F]
  def equipment: OEquipment[F]
  def organization: OOrganization[F]
  def upload: OUpload[F]
  def node: ONode[F]
  def job: OJob[F]
  def item: OItem[F]
  def itemSearch: OItemSearch[F]
  def fulltext: OFulltext[F]
  def mail: OMail[F]
  def joex: OJoex[F]
  def userTask: OUserTask[F]
}

object BackendApp {

  def create[F[_]: ConcurrentEffect: ContextShift](
      cfg: Config,
      store: Store[F],
      httpClient: Client[F],
      ftsClient: FtsClient[F],
      blocker: Blocker
  ): Resource[F, BackendApp[F]] =
    for {
      utStore        <- UserTaskStore(store)
      queue          <- JobQueue(store)
      loginImpl      <- Login[F](store)
      signupImpl     <- OSignup[F](store)
      collImpl       <- OCollective[F](store)
      sourceImpl     <- OSource[F](store)
      tagImpl        <- OTag[F](store)
      equipImpl      <- OEquipment[F](store)
      orgImpl        <- OOrganization(store)
      joexImpl       <- OJoex(JoexClient(httpClient), store)
      uploadImpl     <- OUpload(store, queue, cfg.files, joexImpl)
      nodeImpl       <- ONode(store)
      jobImpl        <- OJob(store, joexImpl)
      itemImpl       <- OItem(store, ftsClient)
      itemSearchImpl <- OItemSearch(store)
      fulltextImpl   <- OFulltext(itemSearchImpl, ftsClient, store, queue, joexImpl)
      javaEmil =
        JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug))
      mailImpl     <- OMail(store, javaEmil)
      userTaskImpl <- OUserTask(utStore, queue, joexImpl)
    } yield new BackendApp[F] {
      val login: Login[F]            = loginImpl
      val signup: OSignup[F]         = signupImpl
      val collective: OCollective[F] = collImpl
      val source                     = sourceImpl
      val tag                        = tagImpl
      val equipment                  = equipImpl
      val organization               = orgImpl
      val upload                     = uploadImpl
      val node                       = nodeImpl
      val job                        = jobImpl
      val item                       = itemImpl
      val itemSearch                 = itemSearchImpl
      val fulltext                   = fulltextImpl
      val mail                       = mailImpl
      val joex                       = joexImpl
      val userTask                   = userTaskImpl
    }

  def apply[F[_]: ConcurrentEffect: ContextShift](
      cfg: Config,
      connectEC: ExecutionContext,
      httpClientEc: ExecutionContext,
      blocker: Blocker
  )(ftsFactory: Client[F] => Resource[F, FtsClient[F]]): Resource[F, BackendApp[F]] =
    for {
      store      <- Store.create(cfg.jdbc, connectEC, blocker)
      httpClient <- BlazeClientBuilder[F](httpClientEc).resource
      ftsClient  <- ftsFactory(httpClient)
      backend    <- create(cfg, store, httpClient, ftsClient, blocker)
    } yield backend
} 
Example 80
Source File: JoexClient.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.joexapi.client

import scala.concurrent.ExecutionContext

import cats.effect._
import cats.implicits._

import docspell.common.syntax.all._
import docspell.common.{Ident, LenientUri}
import docspell.joexapi.model.BasicResult

import org.http4s.circe.CirceEntityDecoder._
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.{Method, Request, Uri}
import org.log4s.getLogger

trait JoexClient[F[_]] {

  def notifyJoex(base: LenientUri): F[Unit]

  def notifyJoexIgnoreErrors(base: LenientUri): F[Unit]

  def cancelJob(base: LenientUri, job: Ident): F[BasicResult]

}

object JoexClient {

  private[this] val logger = getLogger

  def apply[F[_]: Sync](client: Client[F]): JoexClient[F] =
    new JoexClient[F] {
      def notifyJoex(base: LenientUri): F[Unit] = {
        val notifyUrl = base / "api" / "v1" / "notify"
        val req       = Request[F](Method.POST, uri(notifyUrl))
        logger.fdebug(s"Notify joex at ${notifyUrl.asString}") *>
          client.expect[String](req).map(_ => ())
      }

      def notifyJoexIgnoreErrors(base: LenientUri): F[Unit] =
        notifyJoex(base).attempt.map {
          case Right(()) => ()
          case Left(ex) =>
            logger.warn(
              s"Notifying Joex instance '${base.asString}' failed: ${ex.getMessage}"
            )
            ()
        }

      def cancelJob(base: LenientUri, job: Ident): F[BasicResult] = {
        val cancelUrl = base / "api" / "v1" / "job" / job.id / "cancel"
        val req       = Request[F](Method.POST, uri(cancelUrl))
        client.expect[BasicResult](req)
      }

      private def uri(u: LenientUri): Uri =
        Uri.unsafeFromString(u.asString)
    }

  def resource[F[_]: ConcurrentEffect](ec: ExecutionContext): Resource[F, JoexClient[F]] =
    BlazeClientBuilder[F](ec).resource.map(apply[F])
} 
Example 81
Source File: RestAppImpl.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver

import scala.concurrent.ExecutionContext

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.common.NodeType
import docspell.ftsclient.FtsClient
import docspell.ftssolr.SolrFtsClient

import org.http4s.client.Client

final class RestAppImpl[F[_]: Sync](val config: Config, val backend: BackendApp[F])
    extends RestApp[F] {

  def init: F[Unit] =
    backend.node.register(config.appId, NodeType.Restserver, config.baseUrl)

  def shutdown: F[Unit] =
    backend.node.unregister(config.appId)
}

object RestAppImpl {

  def create[F[_]: ConcurrentEffect: ContextShift](
      cfg: Config,
      connectEC: ExecutionContext,
      httpClientEc: ExecutionContext,
      blocker: Blocker
  ): Resource[F, RestApp[F]] =
    for {
      backend <- BackendApp(cfg.backend, connectEC, httpClientEc, blocker)(
        createFtsClient[F](cfg)
      )
      app = new RestAppImpl[F](cfg, backend)
      appR <- Resource.make(app.init.map(_ => app))(_.shutdown)
    } yield appR

  private def createFtsClient[F[_]: ConcurrentEffect: ContextShift](
      cfg: Config
  )(client: Client[F]): Resource[F, FtsClient[F]] =
    if (cfg.fullTextSearch.enabled) SolrFtsClient(cfg.fullTextSearch.solr, client)
    else Resource.pure[F, FtsClient[F]](FtsClient.none[F])
}