Skip to content

Commit

Permalink
wip: tapir doc & annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
Pat Losoponkul committed Apr 17, 2023
1 parent 2764198 commit c6da28b
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.iohk.atala.api.http.codec.DIDCodec.{didJsonLD, didResolutionJsonLD}
import io.iohk.atala.api.http.{ErrorResponse, RequestContext}
import sttp.model.{Header, StatusCode}
import sttp.tapir.*
import io.iohk.atala.castor.controller.http.DIDResolutionResult
import io.iohk.atala.castor.controller.http.{DIDResolutionResult, DIDInput}
import sttp.tapir.json.zio.jsonBody

object DIDEndpoints {
Expand All @@ -17,7 +17,7 @@ object DIDEndpoints {
(StatusCode, DIDResolutionResult),
Any
] = infallibleEndpoint.get
.in("dids" / path[String]("didRef"))
.in("dids" / DIDInput.didRefPathSegment)
.out(
statusCode
.description(StatusCode.Ok, "The resolution result or W3C DID document representation")
Expand All @@ -35,6 +35,13 @@ object DIDEndpoints {
)
)
.name("getDID")
.summary("Resolve Prism DID to a W3C representation")
.description("""
|Resolve Prism DID to a W3C DID document representation.
|The response can be the [DID resolution result](https://w3c-ccg.github.io/did-resolution/#did-resolution-result)
|or [DID document representation](https://www.w3.org/TR/did-core/#representations) depending on the `Accept` request header.
|The response is implemented according to [resolver HTTP binding](https://w3c-ccg.github.io/did-resolution/#bindings-https) in the DID resolution spec.
|""".stripMargin)
.tag("DID")

}
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
package io.iohk.atala.castor.controller.http

import io.iohk.atala.api.http.Annotation
import io.iohk.atala.castor.core.model.did.w3c
import sttp.tapir.Schema
import sttp.tapir.Schema.annotations.{description, encodedExample}
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonEncoder, JsonDecoder}
import io.iohk.atala.castor.core.model.did.w3c

final case class DIDResolutionResult(
`@context`: String,
didDocument: Option[DIDDocument] = None,
didDocumentMetadata: DIDDocumentMetadata,
didResolutionMetadata: DIDResolutionMetadata
)

object DIDResolutionResult {
given encoder: JsonEncoder[DIDResolutionResult] = DeriveJsonEncoder.gen[DIDResolutionResult]
given decoder: JsonDecoder[DIDResolutionResult] = DeriveJsonDecoder.gen[DIDResolutionResult]
given schema: Schema[DIDResolutionResult] = Schema.derived
}

final case class DIDResolutionMetadata(
error: Option[String] = None,
errorMessage: Option[String] = None,
contentType: Option[String] = None
)

object DIDResolutionMetadata {
given encoder: JsonEncoder[DIDResolutionMetadata] = DeriveJsonEncoder.gen[DIDResolutionMetadata]
given decoder: JsonDecoder[DIDResolutionMetadata] = DeriveJsonDecoder.gen[DIDResolutionMetadata]
given schema: Schema[DIDResolutionMetadata] = Schema.derived
}
import io.iohk.atala.castor.controller.http.DIDDocument.annotations

@description("A W3C compliant Prism DID document representation.")
final case class DIDDocument(
@description(annotations.`@context`.description)
@encodedExample(annotations.`@context`.example)
`@context`: Seq[String],
@description(annotations.id.description)
@encodedExample(annotations.id.example)
id: String,
@description(annotations.controller.description)
@encodedExample(annotations.controller.example)
controller: Option[String] = None,
verificationMethod: Option[Seq[VerificationMethod]] = None,
authentication: Option[Seq[String]] = None,
Expand All @@ -43,6 +28,29 @@ final case class DIDDocument(
)

object DIDDocument {

object annotations {
object `@context`
extends Annotation[Seq[String]](
description = "The JSON-LD context for the DID resolution result.",
example = Seq("https://www.w3.org/ns/did/v1")
)

object id
extends Annotation[String](
description = """
|[DID subject](https://www.w3.org/TR/did-core/#did-subject).
|The value must match the DID that was given to the resolver.""".stripMargin,
example = "did:prism:4a5b5cf0a513e83b598bbea25cd6196746747f361a73ef77068268bc9bd732ff"
)

object controller
extends Annotation[String](
description = "[DID controller](https://www.w3.org/TR/did-core/#did-controller)",
example = "did:prism:4a5b5cf0a513e83b598bbea25cd6196746747f361a73ef77068268bc9bd732ff"
)
}

given encoder: JsonEncoder[DIDDocument] = DeriveJsonEncoder.gen[DIDDocument]
given decoder: JsonDecoder[DIDDocument] = DeriveJsonDecoder.gen[DIDDocument]
given schema: Schema[DIDDocument] = Schema.derived
Expand All @@ -66,86 +74,7 @@ object DIDDocument {
private def toPublicKeyRef(publicKeyReprOrRef: w3c.PublicKeyReprOrRef): String = {
publicKeyReprOrRef match {
case s: String => s
case pk => throw Exception("Embedded public key is not yet supported in W3C representation")
case pk => throw Exception("Embedded public key is not yet supported by PRISM DID.")
}
}
}

final case class DIDDocumentMetadata(
deactivated: Option[Boolean] = None,
canonicalId: Option[String] = None
)

object DIDDocumentMetadata {
given encoder: JsonEncoder[DIDDocumentMetadata] = DeriveJsonEncoder.gen[DIDDocumentMetadata]
given decoder: JsonDecoder[DIDDocumentMetadata] = DeriveJsonDecoder.gen[DIDDocumentMetadata]
given schema: Schema[DIDDocumentMetadata] = Schema.derived

given Conversion[w3c.DIDDocumentMetadataRepr, DIDDocumentMetadata] =
(didDocumentMetadata: w3c.DIDDocumentMetadataRepr) =>
DIDDocumentMetadata(
deactivated = Some(didDocumentMetadata.deactivated),
canonicalId = Some(didDocumentMetadata.canonicalId)
)
}

final case class VerificationMethod(
id: String,
`type`: String,
controller: String,
publicKeyJwk: PublicKeyJwk
)

object VerificationMethod {
given encoder: JsonEncoder[VerificationMethod] = DeriveJsonEncoder.gen[VerificationMethod]
given decoder: JsonDecoder[VerificationMethod] = DeriveJsonDecoder.gen[VerificationMethod]
given schema: Schema[VerificationMethod] = Schema.derived

given Conversion[w3c.PublicKeyRepr, VerificationMethod] = (publicKey: w3c.PublicKeyRepr) =>
VerificationMethod(
id = publicKey.id,
`type` = publicKey.`type`,
controller = publicKey.controller,
publicKeyJwk = publicKey.publicKeyJwk
)
}

final case class PublicKeyJwk(
crv: Option[String] = None,
x: Option[String] = None,
y: Option[String] = None,
kty: String
)

object PublicKeyJwk {
given encoder: JsonEncoder[PublicKeyJwk] = DeriveJsonEncoder.gen[PublicKeyJwk]
given decoder: JsonDecoder[PublicKeyJwk] = DeriveJsonDecoder.gen[PublicKeyJwk]
given schema: Schema[PublicKeyJwk] = Schema.derived

given Conversion[w3c.PublicKeyJwk, PublicKeyJwk] = (publicKeyJwk: w3c.PublicKeyJwk) =>
PublicKeyJwk(
crv = Some(publicKeyJwk.crv),
x = Some(publicKeyJwk.x),
y = Some(publicKeyJwk.y),
kty = publicKeyJwk.kty
)
}

final case class Service(
id: String,
`type`: String,
serviceEndpoint: Seq[String]
)

object Service {
given encoder: JsonEncoder[Service] = DeriveJsonEncoder.gen[Service]
given decoder: JsonDecoder[Service] = DeriveJsonDecoder.gen[Service]
given schema: Schema[Service] = Schema.derived

given Conversion[w3c.ServiceRepr, Service] = (service: w3c.ServiceRepr) =>
Service(
id = service.id,
`type` = service.`type`,
serviceEndpoint = service.serviceEndpoint
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.iohk.atala.castor.controller.http

import io.iohk.atala.api.http.Annotation
import io.iohk.atala.castor.core.model.did.w3c
import sttp.tapir.Schema
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonEncoder, JsonDecoder}
import sttp.tapir.Schema.annotations.{description, encodedExample}
import io.iohk.atala.castor.controller.http.DIDDocumentMetadata.annotations

@description("[DID document metadata](https://www.w3.org/TR/did-core/#did-document-metadata)")
final case class DIDDocumentMetadata(
@description(annotations.deactivated.description)
@encodedExample(annotations.deactivated.example)
deactivated: Option[Boolean] = None,
@description(annotations.canonicalId.description)
@encodedExample(annotations.canonicalId.example)
canonicalId: Option[String] = None
)

object DIDDocumentMetadata {

object annotations {
object deactivated
extends Annotation[Boolean](
description =
"If a DID has been deactivated, DID document metadata MUST include this property with the boolean value true. If a DID has not been deactivated, this property is OPTIONAL, but if included, MUST have the boolean value false.",
example = false
)

object canonicalId
extends Annotation[String](
description = """
|A DID in canonical form.
|If a DID is in long form and has been published, DID document metadata MUST contain a `canonicalId`` property with the short form DID as its value.
|If a DID in short form or has not been published, DID document metadata MUST NOT contain a `canonicalId` property.
|""".stripMargin,
example = "did:prism:4a5b5cf0a513e83b598bbea25cd6196746747f361a73ef77068268bc9bd732ff"
)
}

given encoder: JsonEncoder[DIDDocumentMetadata] = DeriveJsonEncoder.gen[DIDDocumentMetadata]
given decoder: JsonDecoder[DIDDocumentMetadata] = DeriveJsonDecoder.gen[DIDDocumentMetadata]
given schema: Schema[DIDDocumentMetadata] = Schema.derived

given Conversion[w3c.DIDDocumentMetadataRepr, DIDDocumentMetadata] =
(didDocumentMetadata: w3c.DIDDocumentMetadataRepr) =>
DIDDocumentMetadata(
deactivated = Some(didDocumentMetadata.deactivated),
canonicalId = Some(didDocumentMetadata.canonicalId)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.iohk.atala.castor.controller.http

import sttp.tapir.*

object DIDInput {

val didRefPathSegment = path[String]("didRef")
.description(
"Prism DID according to [the Prism DID method syntax](https://github.com/input-output-hk/prism-did-method-spec/blob/main/w3c-spec/PRISM-method.md#prism-did-method-syntax)"
)
.example("did:prism:4a5b5cf0a513e83b598bbea25cd6196746747f361a73ef77068268bc9bd732ff")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.iohk.atala.castor.controller.http

import io.iohk.atala.api.http.Annotation
import io.iohk.atala.castor.controller.http.DIDResolutionMetadata.annotations
import sttp.tapir.Schema
import sttp.tapir.Schema.annotations.{description, encodedExample}
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonEncoder, JsonDecoder}

@description("[DID resolution metadata](https://www.w3.org/TR/did-core/#did-resolution-metadata)")
final case class DIDResolutionMetadata(
@description(annotations.error.description)
@encodedExample(annotations.error.example)
error: Option[String] = None,
@description(annotations.errorMessage.description)
@encodedExample(annotations.errorMessage.example)
errorMessage: Option[String] = None,
@description(annotations.contentType.description)
@encodedExample(annotations.contentType.example)
contentType: Option[String] = None
)

object DIDResolutionMetadata {

object annotations {
object error
extends Annotation[String](
description =
"Resolution error constant according to [DID spec registries](https://www.w3.org/TR/did-spec-registries/#error)",
example = "invalidDid"
)

object errorMessage
extends Annotation[String](
description = "Resolution error message",
example = "The initialState does not match the suffix"
)

object contentType
extends Annotation[String](
description = "The media type of the returned DID document",
example = "application/did+ld+json"
)
}

given encoder: JsonEncoder[DIDResolutionMetadata] = DeriveJsonEncoder.gen[DIDResolutionMetadata]
given decoder: JsonDecoder[DIDResolutionMetadata] = DeriveJsonDecoder.gen[DIDResolutionMetadata]
given schema: Schema[DIDResolutionMetadata] = Schema.derived
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.iohk.atala.castor.controller.http

import io.iohk.atala.api.http.Annotation
import sttp.tapir.Schema
import sttp.tapir.Schema.annotations.{description, encodedExample}
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonEncoder, JsonDecoder}
import io.iohk.atala.castor.controller.http.DIDResolutionResult.annotations

final case class DIDResolutionResult(
@description(annotations.`@context`.description)
@encodedExample(annotations.`@context`.example)
`@context`: String,
didDocument: Option[DIDDocument] = None,
didDocumentMetadata: DIDDocumentMetadata,
didResolutionMetadata: DIDResolutionMetadata
)

object DIDResolutionResult {
object annotations {
object `@context`
extends Annotation[String](
description = "The JSON-LD context for the DID resolution result.",
example = "https://w3id.org/did-resolution/v1"
)
}

given encoder: JsonEncoder[DIDResolutionResult] = DeriveJsonEncoder.gen[DIDResolutionResult]
given decoder: JsonDecoder[DIDResolutionResult] = DeriveJsonDecoder.gen[DIDResolutionResult]
given schema: Schema[DIDResolutionResult] = Schema.derived
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.iohk.atala.castor.controller.http

import io.iohk.atala.castor.core.model.did.w3c
import sttp.tapir.Schema
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonEncoder, JsonDecoder}

final case class PublicKeyJwk(
crv: Option[String] = None,
x: Option[String] = None,
y: Option[String] = None,
kty: String
)

object PublicKeyJwk {
given encoder: JsonEncoder[PublicKeyJwk] = DeriveJsonEncoder.gen[PublicKeyJwk]
given decoder: JsonDecoder[PublicKeyJwk] = DeriveJsonDecoder.gen[PublicKeyJwk]
given schema: Schema[PublicKeyJwk] = Schema.derived

given Conversion[w3c.PublicKeyJwk, PublicKeyJwk] = (publicKeyJwk: w3c.PublicKeyJwk) =>
PublicKeyJwk(
crv = Some(publicKeyJwk.crv),
x = Some(publicKeyJwk.x),
y = Some(publicKeyJwk.y),
kty = publicKeyJwk.kty
)
}
Loading

0 comments on commit c6da28b

Please sign in to comment.