diff --git a/.gitignore b/.gitignore index 3ced74ee..aeee5300 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Generated files .docusaurus +.netlify .cache-loader # Misc diff --git a/docker/Dockerfile b/docker/Dockerfile index 691e5b11..0f741691 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,6 +16,6 @@ RUN yarn build FROM nginx:stable-alpine as deploy WORKDIR /restate-documentation # Copy what we've built from production -COPY --from=builder /restate-documentation/build /usr/share/nginx/html/docs +COPY --from=builder /restate-documentation/build /usr/share/nginx/html/ # Update nginx configuration COPY --from=builder /restate-documentation/docker/nginx.conf /etc/nginx/ diff --git a/docker/nginx.conf b/docker/nginx.conf index c4603854..b12d777d 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -36,9 +36,12 @@ http { # keep request port absolute_redirect off; - # redirect to /docs path which is the base url of the documentation - location = / { - return 301 /docs; + # redirect /docs/ to / as some browsers may have the old /docs/ redirect cached + location = /docs/ { + return 301 /; + } + location = /docs { + return 301 /; } } } diff --git a/docs/deployment-operations/errors.md b/docs/deployment-operations/errors.md index 4a030bc6..0186786f 100644 --- a/docs/deployment-operations/errors.md +++ b/docs/deployment-operations/errors.md @@ -72,7 +72,7 @@ Make sure, when updating a service endpoint, to assign it a new uri. You can force the override using the `"force": true` field in the discover request, but beware that this can lead in-flight invocations to an unrecoverable error state. -See the [versioning documentation](http://restate.dev/docs/deployment-operations/versioning) for more information. +See the [versioning documentation](./versioning) for more information. ## META0005 {#META0005} @@ -89,7 +89,7 @@ When implementing a new service revision, make sure that: * The service instance type and the key definition, if any, is exactly the same as of the previous revisions. * The Protobuf contract and message definitions are backward compatible. -See the [versioning documentation](http://restate.dev/docs/deployment-operations/versioning) for more information. +See the [versioning documentation](./versioning) for more information. ## RT0001 {#RT0001} @@ -105,7 +105,7 @@ Suggestions: Cannot start Restate because the configuration cannot be parsed. Check the configuration file and the environment variables provided. -For a complete list of configuration options, and a sample configuration, check http://restate.dev/docs/deployment-operations/configuration +For a complete list of configuration options, and a sample configuration, check http://docs.restate.dev/deployment-operations/configuration ## RT0003 {#RT0003} diff --git a/docusaurus.config.js b/docusaurus.config.js index a6e08ea2..0132e308 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -25,10 +25,10 @@ const config = { favicon: 'img/favicon.ico', // Set the production url of your site here - url: 'https://restate.dev', + url: 'https://docs.restate.dev', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: '/docs', + baseUrl: '/', // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..bac39d2d --- /dev/null +++ b/netlify.toml @@ -0,0 +1,4 @@ +[[edge_functions]] +function = "auth" +path = "/*" +excludedPath = "/oauth" diff --git a/netlify/edge-functions/auth.ts b/netlify/edge-functions/auth.ts new file mode 100644 index 00000000..b5ceb2f0 --- /dev/null +++ b/netlify/edge-functions/auth.ts @@ -0,0 +1,108 @@ +import type {Context} from "https://edge.netlify.com"; +import {create, verify} from "https://deno.land/x/djwt@v2.9.1/mod.ts"; +import {getCookies, setCookie} from "https://deno.land/std@0.170.0/http/cookie.ts"; + +const deploy_context = Deno.env.get("DEPLOY_CONTEXT"); +const client_id = Deno.env.get("GITHUB_CLIENT_ID"); +const client_secret = Deno.env.get("GITHUB_CLIENT_SECRET"); +const allowlist = new Set((Deno.env.get("GITHUB_ALLOWLIST") || "").split(",")); +const jwt_secret = Deno.env.get("JWT_SECRET") + +const login = async (context: Context, code: string, key: CryptoKey) => { + const token_response = await fetch( + "https://github.com/login/oauth/access_token", + { + method: "POST", + headers: { + "content-type": "application/json", + "user-agent": "cloudflare-worker-github-oauth-login-demo", + accept: "application/json", + }, + body: JSON.stringify({client_id, client_secret, code}), + } + ); + const token_result = await token_response.json(); + const headers = { + "Access-Control-Allow-Origin": "*", + }; + + if (token_result.error) { + return new Response(JSON.stringify(token_result), {status: 401, headers}); + } + + const user_response = await fetch( + "https://api.github.com/user", + { + method: "GET", + headers: { + "user-agent": "cloudflare-worker-github-oauth-login-demo", + accept: "application/vnd.github+json", + }, + } + ); + + const user_result = await user_response.json(); + + if (!user_result.login) { + return new Response("No login found in user response from GitHub", {status: 500, headers}); + } + + // issue a jwt to avoid having to do a github api call on every request + const jwt = await create({alg: "HS512", typ: "JWT"}, {aud: user_result.login}, key) + + const next = await context.next(); + setCookie(next.headers, {name: "RESTATE_DOCS", value: jwt}) + + return next +} + +const redirect = Response.redirect(`https://github.com/login/oauth/authorize?client_id=${client_id}`, 302) + +export default async (request: Request, context: Context) => { + if (!(deploy_context === "PRODUCTION" || deploy_context === "LOCAL")) { + // there's no way to do oauth on branch or preview builds as url is unpredictable; we use passwords there instead + // for local we use a different github oauth app that redirects to localhost:8888 + return context.next() + } + + const key = await crypto.subtle.importKey("jwk", { + alg: "HS512", ext: true, + k: jwt_secret, + key_ops: [ + "sign", + "verify" + ], + kty: "oct" + }, + {name: "HMAC", hash: "SHA-512"}, + true, + ["sign", "verify"], + ) + + const code = new URL(request.url).searchParams.get("code"); + + if (code) { + // this is a redirect from github auth; process the code + return login(context, code, key); + } + + // no code, check for jwt + + const cookies = getCookies(request.headers); + if (!cookies.RESTATE_DOCS) { + // no code or cookie, login flow + return redirect + } + + const payload = await verify(cookies.RESTATE_DOCS, key) + if (!payload.login) { + // weird payload, get them to oauth + return redirect + } + if (!payload.aud || !(typeof payload.aud == "string") || !allowlist.has(payload.aud)) { + return new Response("GitHub user not allowlisted.", {status: 403}) + } + + // load page + return context.next() +} diff --git a/static/schemas/openapi-meta.json b/static/schemas/openapi-meta.json index 56e64399..de456d0c 100644 --- a/static/schemas/openapi-meta.json +++ b/static/schemas/openapi-meta.json @@ -1 +1 @@ -{"openapi":"3.0.0","info":{"title":"Meta REST Operational API","version":"0.1.6"},"paths":{"/services/{service}":{"patch":{"tags":["service"],"summary":"Modify a service","description":"Modify a registered service.","operationId":"modify_service","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyServiceRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/invocations":{"delete":{"tags":["invocation"],"summary":"Kill an invocation","description":"Kill the given invocation. When killing, consistency is not guaranteed for service instance state, in-flight invocation to other services, etc. Future releases will support graceful invocation cancellation.","operationId":"cancel_invocation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelInvocationRequest"}}},"required":true},"responses":{"200":{"description":""},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services":{"get":{"tags":["service"],"summary":"List services","description":"List all registered services.","operationId":"list_services","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServicesResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/endpoints/{endpoint}":{"delete":{"tags":["service_endpoint"],"summary":"Delete service endpoint","description":"Delete service endpoint. Currently it's supported to remove a service endpoint only using the force flag","operationId":"delete_service_endpoint","parameters":[{"name":"endpoint","in":"path","description":"Endpoint identifier","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"If true, the service endpoint will be forcefully deleted. This might break in-flight invocations, use with caution.","style":"simple","schema":{"type":"boolean"}}],"responses":{"202":{"description":"Accepted"},"501":{"description":"Not implemented. Only using the force flag is supported at the moment."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/methods/{method}":{"get":{"tags":["service_method"],"summary":"Get service method","description":"Get the method of a service","operationId":"get_service_method","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}},{"name":"method","in":"path","description":"Method name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetServiceMethodResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/methods":{"get":{"tags":["service_method"],"summary":"List service methods","description":"List all the methods of the given service.","operationId":"list_service_methods","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServiceMethodsResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/openapi":{"get":{"tags":["openapi"],"summary":"OpenAPI specification","externalDocs":{"url":"https://swagger.io/specification/"},"operationId":"openapi_spec","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/endpoints":{"post":{"tags":["service_endpoint"],"summary":"Create service endpoint","description":"Create service endpoint. Restate will invoke the endpoint to gather additional information required for registration, such as the services exposed by the service endpoint and their Protobuf descriptor. If the service endpoint is already registered, this method will fail unless `force` is set to `true`.","operationId":"create_service_endpoint","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}}},"components":{"schemas":{"ModifyServiceRequest":{"type":"object","required":["public"],"properties":{"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"ServiceMetadata":{"type":"object","required":["endpoint_id","instance_type","methods","name","public","revision"],"properties":{"name":{"type":"string"},"methods":{"type":"array","items":{"type":"string"}},"instance_type":{"$ref":"#/components/schemas/InstanceType"},"endpoint_id":{"title":"Endpoint Id","description":"Endpoint exposing the latest revision of the service.","type":"string"},"revision":{"title":"Revision","description":"Latest revision of the service.","type":"integer","format":"uint32","minimum":0.0},"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"InstanceType":{"type":"string","enum":["Keyed","Unkeyed","Singleton"]},"ErrorDescriptionResponse":{"title":"Error description response","description":"Error details of the response","type":"object","required":["message"],"properties":{"message":{"type":"string"},"restate_code":{"title":"Restate code","description":"Restate error code describing this error","type":"string","nullable":true}}},"CancelInvocationRequest":{"type":"object","required":["sid"],"properties":{"sid":{"title":"Target identifier","description":"Identifier of the service invocation to cancel/kill.","allOf":[{"$ref":"#/components/schemas/ServiceInvocationId"}]}}},"ServiceInvocationId":{"title":"Service invocation id","description":"Identifier for a service invocation.","anyOf":[{"title":"Token representation","description":"Token representation of the service invocation identifier. This is the same representation used by the Restate CLI SQL interface.","type":"string"},{"title":"Structured representation","description":"Structured representation of the service invocation identifier.\n\nWhen providing the key, it must be non-empty for Keyed and Unkeyed services, and it must be empty for Singleton services.","type":"object","required":["invocation_id","service"],"properties":{"service":{"type":"string"},"key":{"nullable":true},"invocation_id":{"type":"string"}}}]},"ListServicesResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"GetServiceMethodResponse":{"type":"object","required":["method_name","service_name"],"properties":{"service_name":{"type":"string"},"method_name":{"type":"string"}}},"ListServiceMethodsResponse":{"type":"object","required":["methods"],"properties":{"methods":{"type":"array","items":{"type":"string"}}}},"RegisterServiceEndpointRequest":{"type":"object","required":["uri"],"properties":{"uri":{"title":"Uri","description":"Uri to use to discover/invoke the service endpoint.","type":"string"},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the service endpoint.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any endpoint using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](http://restate.dev/docs/deployment-operations/versioning) for more information.","default":true,"type":"boolean"}}},"RegisterServiceEndpointResponse":{"type":"object","required":["id","services"],"properties":{"id":{"type":"string"},"services":{"type":"array","items":{"$ref":"#/components/schemas/RegisterServiceResponse"}}}},"RegisterServiceResponse":{"type":"object","required":["name","revision"],"properties":{"name":{"type":"string"},"revision":{"type":"integer","format":"uint32","minimum":0.0}}}}}} +{"openapi":"3.0.0","info":{"title":"Meta REST Operational API","version":"0.1.6"},"paths":{"/services/{service}":{"patch":{"tags":["service"],"summary":"Modify a service","description":"Modify a registered service.","operationId":"modify_service","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyServiceRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/invocations":{"delete":{"tags":["invocation"],"summary":"Kill an invocation","description":"Kill the given invocation. When killing, consistency is not guaranteed for service instance state, in-flight invocation to other services, etc. Future releases will support graceful invocation cancellation.","operationId":"cancel_invocation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelInvocationRequest"}}},"required":true},"responses":{"200":{"description":""},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services":{"get":{"tags":["service"],"summary":"List services","description":"List all registered services.","operationId":"list_services","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServicesResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/endpoints/{endpoint}":{"delete":{"tags":["service_endpoint"],"summary":"Delete service endpoint","description":"Delete service endpoint. Currently it's supported to remove a service endpoint only using the force flag","operationId":"delete_service_endpoint","parameters":[{"name":"endpoint","in":"path","description":"Endpoint identifier","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"If true, the service endpoint will be forcefully deleted. This might break in-flight invocations, use with caution.","style":"simple","schema":{"type":"boolean"}}],"responses":{"202":{"description":"Accepted"},"501":{"description":"Not implemented. Only using the force flag is supported at the moment."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/methods/{method}":{"get":{"tags":["service_method"],"summary":"Get service method","description":"Get the method of a service","operationId":"get_service_method","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}},{"name":"method","in":"path","description":"Method name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetServiceMethodResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/methods":{"get":{"tags":["service_method"],"summary":"List service methods","description":"List all the methods of the given service.","operationId":"list_service_methods","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServiceMethodsResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/openapi":{"get":{"tags":["openapi"],"summary":"OpenAPI specification","externalDocs":{"url":"https://swagger.io/specification/"},"operationId":"openapi_spec","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/endpoints":{"post":{"tags":["service_endpoint"],"summary":"Create service endpoint","description":"Create service endpoint. Restate will invoke the endpoint to gather additional information required for registration, such as the services exposed by the service endpoint and their Protobuf descriptor. If the service endpoint is already registered, this method will fail unless `force` is set to `true`.","operationId":"create_service_endpoint","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterServiceEndpointResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}}},"components":{"schemas":{"ModifyServiceRequest":{"type":"object","required":["public"],"properties":{"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"ServiceMetadata":{"type":"object","required":["endpoint_id","instance_type","methods","name","public","revision"],"properties":{"name":{"type":"string"},"methods":{"type":"array","items":{"type":"string"}},"instance_type":{"$ref":"#/components/schemas/InstanceType"},"endpoint_id":{"title":"Endpoint Id","description":"Endpoint exposing the latest revision of the service.","type":"string"},"revision":{"title":"Revision","description":"Latest revision of the service.","type":"integer","format":"uint32","minimum":0.0},"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"}}},"InstanceType":{"type":"string","enum":["Keyed","Unkeyed","Singleton"]},"ErrorDescriptionResponse":{"title":"Error description response","description":"Error details of the response","type":"object","required":["message"],"properties":{"message":{"type":"string"},"restate_code":{"title":"Restate code","description":"Restate error code describing this error","type":"string","nullable":true}}},"CancelInvocationRequest":{"type":"object","required":["sid"],"properties":{"sid":{"title":"Target identifier","description":"Identifier of the service invocation to cancel/kill.","allOf":[{"$ref":"#/components/schemas/ServiceInvocationId"}]}}},"ServiceInvocationId":{"title":"Service invocation id","description":"Identifier for a service invocation.","anyOf":[{"title":"Token representation","description":"Token representation of the service invocation identifier. This is the same representation used by the Restate CLI SQL interface.","type":"string"},{"title":"Structured representation","description":"Structured representation of the service invocation identifier.\n\nWhen providing the key, it must be non-empty for Keyed and Unkeyed services, and it must be empty for Singleton services.","type":"object","required":["invocation_id","service"],"properties":{"service":{"type":"string"},"key":{"nullable":true},"invocation_id":{"type":"string"}}}]},"ListServicesResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"GetServiceMethodResponse":{"type":"object","required":["method_name","service_name"],"properties":{"service_name":{"type":"string"},"method_name":{"type":"string"}}},"ListServiceMethodsResponse":{"type":"object","required":["methods"],"properties":{"methods":{"type":"array","items":{"type":"string"}}}},"RegisterServiceEndpointRequest":{"type":"object","required":["uri"],"properties":{"uri":{"title":"Uri","description":"Uri to use to discover/invoke the service endpoint.","type":"string"},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the service endpoint.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any endpoint using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](http://docs.restate.dev/deployment-operations/versioning) for more information.","default":true,"type":"boolean"}}},"RegisterServiceEndpointResponse":{"type":"object","required":["id","services"],"properties":{"id":{"type":"string"},"services":{"type":"array","items":{"$ref":"#/components/schemas/RegisterServiceResponse"}}}},"RegisterServiceResponse":{"type":"object","required":["name","revision"],"properties":{"name":{"type":"string"},"revision":{"type":"integer","format":"uint32","minimum":0.0}}}}}}