• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger / identus-cloud-agent / 10793991050

10 Sep 2024 01:56PM CUT coverage: 48.504% (-4.5%) from 52.962%
10793991050

push

web-flow
build: sbt and plugins dependency update (#1337)

Signed-off-by: Hyperledger Bot <hyperledger-bot@hyperledger.org>
Signed-off-by: Yurii Shynbuiev <yurii.shynbuiev@iohk.io>
Co-authored-by: Hyperledger Bot <hyperledger-bot@hyperledger.org>
Co-authored-by: Yurii Shynbuiev <yurii.shynbuiev@iohk.io>

7406 of 15269 relevant lines covered (48.5%)

0.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/Service.scala
1
package org.hyperledger.identus.castor.controller.http
2

3
import io.circe.Json
4
import org.hyperledger.identus.api.http.codec.CirceJsonInterop
5
import org.hyperledger.identus.api.http.Annotation
6
import org.hyperledger.identus.castor.controller.http.Service.annotations
7
import org.hyperledger.identus.castor.core.model.{did as castorDomain, ProtoModelHelper}
8
import org.hyperledger.identus.castor.core.model.did.w3c
9
import org.hyperledger.identus.shared.utils.Traverse.*
10
import sttp.tapir.Schema
11
import sttp.tapir.Schema.annotations.{description, encodedExample}
12
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder}
13

14
import scala.language.implicitConversions
15

16
@description("A service that should appear in the DID document. https://www.w3.org/TR/did-core/#services")
×
17
final case class Service(
18
    @description(annotations.id.description)
×
19
    @encodedExample(annotations.id.example)
×
20
    id: String,
21
    @description(annotations.`type`.description)
×
22
    @encodedExample(annotations.`type`.example)
×
23
    `type`: ServiceType,
24
    @description(annotations.serviceEndpoint.description)
×
25
    @encodedExample(annotations.serviceEndpoint.example)
×
26
    serviceEndpoint: ServiceEndpoint
27
)
28

29
object Service {
30

31
  object annotations {
32
    object id
33
        extends Annotation[String](
34
          description = """The id of the service.
35
              |Requires a URI fragment when use in create / update DID.
36
              |Returns the full ID (with DID prefix) when resolving DID""".stripMargin,
37
          example = "service-1"
38
        )
39

40
    object `type`
41
        extends Annotation[String](
42
          description =
43
            "Service type. Can contain multiple possible values as described in the [Create DID operation](https://github.com/input-output-hk/prism-did-method-spec/blob/main/w3c-spec/PRISM-method.md#create-did) under the construction section.",
44
          example = "LinkedDomains"
45
        )
46

47
    object serviceEndpoint
48
        extends Annotation[Json](
×
49
          description =
50
            "The service endpoint. Can contain multiple possible values as described in the [Create DID operation](https://github.com/input-output-hk/prism-did-method-spec/blob/main/w3c-spec/PRISM-method.md#create-did)",
51
          example = Json.fromString("https://example.com")
×
52
        )
53
  }
54

55
  given encoder: JsonEncoder[Service] = DeriveJsonEncoder.gen[Service]
×
56
  given decoder: JsonDecoder[Service] = DeriveJsonDecoder.gen[Service]
×
57
  given schema: Schema[Service] = Schema
58
    .derived[Service]
59
    .modify(_.serviceEndpoint)(_.copy(isOptional = false))
×
60

61
  given Conversion[w3c.ServiceRepr, Service] = (service: w3c.ServiceRepr) =>
62
    Service(
63
      id = service.id,
64
      `type` = service.`type`,
×
65
      serviceEndpoint = ServiceEndpoint.fromJson(service.serviceEndpoint)
×
66
    )
67

68
  extension (service: Service) {
69
    def toDomain: Either[String, castorDomain.Service] = {
×
70
      for {
×
71
        serviceEndpoint <- service.serviceEndpoint.toDomain
×
72
        serviceType <- service.`type`.toDomain
×
73
      } yield castorDomain
74
        .Service(
75
          id = service.id,
76
          `type` = serviceType,
×
77
          serviceEndpoint = serviceEndpoint
78
        )
79
        .normalizeServiceEndpoint()
×
80
    }
81
  }
82
}
83

84
sealed trait ServiceType
85

86
object ServiceType {
87
  final case class Single(value: String) extends ServiceType
88
  final case class Multiple(values: Seq[String]) extends ServiceType
89

90
  given encoder: JsonEncoder[ServiceType] = JsonEncoder.string
91
    .orElseEither(JsonEncoder.array[String])
×
92
    .contramap[ServiceType] {
×
93
      case Single(value)    => Left(value)
×
94
      case Multiple(values) => Right(values.toArray)
×
95
    }
96
  given decoder: JsonDecoder[ServiceType] = JsonDecoder.string
97
    .orElseEither(JsonDecoder.array[String])
×
98
    .map[ServiceType] {
×
99
      case Left(value)   => Single(value)
×
100
      case Right(values) => Multiple(values.toSeq)
×
101
    }
102
  given schema: Schema[ServiceType] = Schema
103
    .schemaForEither(Schema.schemaForString, Schema.schemaForArray[String])
×
104
    .map[ServiceType] {
105
      case Left(value)   => Some(ServiceType.Single(value))
×
106
      case Right(values) => Some(ServiceType.Multiple(values.toSeq))
×
107
    } {
×
108
      case Single(value)    => Left(value)
×
109
      case Multiple(values) => Right(values.toArray)
×
110
    }
111

112
  given Conversion[castorDomain.ServiceType, ServiceType] = {
113
    case t: castorDomain.ServiceType.Single   => Single(t.value.value)
×
114
    case t: castorDomain.ServiceType.Multiple => Multiple(t.values.map(_.value))
×
115
  }
116

117
  given Conversion[String | Seq[String], ServiceType] = {
118
    case s: String      => Single(s)
×
119
    case s: Seq[String] => Multiple(s)
×
120
  }
121

122
  extension (serviceType: ServiceType) {
123
    def toDomain: Either[String, castorDomain.ServiceType] = serviceType match {
×
124
      case Single(value) =>
×
125
        castorDomain.ServiceType.Name.fromString(value).map(castorDomain.ServiceType.Single.apply)
×
126
      case Multiple(values) =>
×
127
        values.toList match {
×
128
          case Nil => Left("serviceType cannot be empty")
×
129
          case head :: tail =>
×
130
            for {
×
131
              parsedHead <- castorDomain.ServiceType.Name.fromString(head)
×
132
              parsedTail <- tail.traverse(s => castorDomain.ServiceType.Name.fromString(s))
×
133
            } yield castorDomain.ServiceType.Multiple(parsedHead, parsedTail)
134
        }
135
    }
136
  }
137
}
138

139
opaque type ServiceEndpoint = Json
140

141
object ServiceEndpoint {
142
  given encoder: JsonEncoder[ServiceEndpoint] = CirceJsonInterop.encodeJson
143
  given decoder: JsonDecoder[ServiceEndpoint] = CirceJsonInterop.decodeJson
144
  given schema: Schema[ServiceEndpoint] = CirceJsonInterop.schemaJson
145

146
  def fromJson(json: Json): ServiceEndpoint = json
×
147

148
  extension (serviceEndpoint: ServiceEndpoint) {
149
    def toDomain: Either[String, castorDomain.ServiceEndpoint] = {
×
150
      val stringEncoded = serviceEndpoint.asString match {
×
151
        case Some(s) => s
×
152
        case None    => serviceEndpoint.noSpaces
×
153
      }
154
      ProtoModelHelper.parseServiceEndpoint(stringEncoded)
×
155
    }
156
  }
157
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc