Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,12 +685,46 @@ inside it.

> curious couldn't finish logging you in.

### server-error-unexplained

**addresses.** A person is waiting for a finished deploy to be given its public address.

> The server wouldn't give this deploy an address.

**build log.** A person is watching the build log stream in while the build runs.

> The server wouldn't send the build log.

**building.** A person is waiting while the server builds the project and decides whether to take the result.

> The server wouldn't start the build.

**deploys.** A person is starting a deploy: curious is asking whether there is room today and for somewhere to upload the project.

> curious couldn't start the deploy.

**logins.** A person is logging in, or curious is saving the login they just completed.

> curious couldn't finish logging you in.

The answer carried no error code, so something in front of the service
wrote it rather than the service itself, and curious cannot tell what is
behind it: that is what "unexplained" means here. The message shows the
HTTP status, which is a 5xx, a 408, a 425 or a 429, and answers like these
usually pass on their own, for example while the service restarts. Try
again in a moment. Where the step may already have happened, the message
says what curious cannot tell.

### server-fault

**addresses.** A person is waiting for a finished deploy to be given its public address.

> The server hit a problem finishing the deploy.

**build log.** A person is watching the build log stream in while the build runs.

> The server hit a problem sending the build log.

**building.** A person is waiting while the server builds the project and decides whether to take the result.

> The server hit a problem starting the build.
Expand All @@ -699,6 +733,36 @@ inside it.

> The server hit a problem starting the deploy.

### server-request-unserved

**addresses.** A person is waiting for a finished deploy to be given its public address.

> The server wouldn't give this deploy an address.

**build log.** A person is watching the build log stream in while the build runs.

> The server wouldn't send the build log.

**building.** A person is waiting while the server builds the project and decides whether to take the result.

> The server wouldn't start the build.

**deploys.** A person is starting a deploy: curious is asking whether there is room today and for somewhere to upload the project.

> curious couldn't start the deploy.

**logins.** A person is logging in, or curious is saving the login they just completed.

> curious couldn't finish logging you in.

The answer carried no error code, and its HTTP status is one that does
not change by waiting: a 404 or a 405 for a request the address does not
serve, or a redirect, which curious never follows. Something other than
the service may be answering for the address. Check `CURIOUS_API_URL`, or
unset it to use the default, and anything between this machine and the
service, such as a proxy. If both are right, please report it with the
output of `curious version`.

### server-unanswered

**addresses.** A person is waiting for a finished deploy to be given its public address.
Expand Down
46 changes: 46 additions & 0 deletions catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -697,24 +697,70 @@
"logins"
]
},
{
"action": [
"Wait"
],
"family": null,
"headline": {
"addresses": "The server wouldn't give this deploy an address.",
"build log": "The server wouldn't send the build log.",
"building": "The server wouldn't start the build.",
"deploys": "curious couldn't start the deploy.",
"logins": "curious couldn't finish logging you in."
},
"headline_reason": {},
"id": "server-error-unexplained",
"stages": [
"addresses",
"build log",
"building",
"deploys",
"logins"
]
},
{
"action": [
"Wait"
],
"family": null,
"headline": {
"addresses": "The server hit a problem finishing the deploy.",
"build log": "The server hit a problem sending the build log.",
"building": "The server hit a problem starting the build.",
"deploys": "The server hit a problem starting the deploy."
},
"headline_reason": {},
"id": "server-fault",
"stages": [
"addresses",
"build log",
"building",
"deploys"
]
},
{
"action": [
"GiveUp"
],
"family": null,
"headline": {
"addresses": "The server wouldn't give this deploy an address.",
"build log": "The server wouldn't send the build log.",
"building": "The server wouldn't start the build.",
"deploys": "curious couldn't start the deploy.",
"logins": "curious couldn't finish logging you in."
},
"headline_reason": {},
"id": "server-request-unserved",
"stages": [
"addresses",
"build log",
"building",
"deploys",
"logins"
]
},
{
"action": [
"Wait"
Expand Down
12 changes: 12 additions & 0 deletions internal/flow/capacity.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ func capacityCheckFailure(err error) error {
apiErr.Message, ui.NextWait,
"Try again in a moment. "+uploadedNothing)

case "":
// No code: something in front of the service answered, after the
// client had already asked three times if the status was a 5xx.
// The check is a read, so nothing has happened either way. The
// headline is the create's, for the reason given below.
if clearsOnItsOwn(apiErr) {
return errorUnexplained(ui.StageDeploys, "curious couldn't start the deploy.",
apiErr, uploadedNothing)
}
return requestUnserved(ui.StageDeploys, "curious couldn't start the deploy.",
apiErr, uploadedNothing)

default:
// The endpoint answers with the two codes above and no other, so
// anything else is a code this step has no copy for. Its headline
Expand Down
96 changes: 96 additions & 0 deletions internal/flow/codeless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package flow

import (
"fmt"
"net/http"

"github.com/curiouspub/cli/internal/api"
"github.com/curiouspub/cli/internal/ui"
)

// A REFUSAL WITH NO CODE is a body that was not the wire envelope at all,
// or an envelope with an empty code. The service's own handlers always
// write the envelope, so an answer without one came from somewhere else: a
// proxy in front of the service while the service restarts or is down, a
// router answering for a route or a method it does not serve, a captive
// portal, or a CURIOUS_API_URL that points somewhere else.
//
// It is not a code this build has no copy for, so it does not get that
// answer. Updating curious is the remedy for few of the causes above, and
// nothing in the answer says which one it was. The client's own sentence
// about an answer it could not read is not the server's, so nothing is
// quoted.
//
// WHAT CAN BE TOLD IS THE STATUS, and it decides the remedy. An answer that
// passes on its own says to wait. One that does not says what to check,
// and claims neither that this build is older than the server nor newer.
// Each handler meets the empty code in a `case "":` of its own switch,
// passes what it can honestly say about what had already happened, and a
// guard holds every handler to it.

// clearsOnItsOwn reports whether a code-less answer is one that passes
// without anything outside the run changing: any 5xx, and the three 4xx
// statuses that say "not now" rather than "not this".
//
// Everything else does not pass by waiting. A 404 or a 405 is a router
// that does not serve the request, and a 1xx or 3xx arrives here only
// because curious never follows a redirect: something else is answering
// for the address.
func clearsOnItsOwn(apiErr *api.APIError) bool {
switch apiErr.Status {
case http.StatusRequestTimeout, http.StatusTooEarly, http.StatusTooManyRequests:
return true
}
return apiErr.Status >= http.StatusInternalServerError
}

const (
// whyUnexplained and whyUnserved are the fixed half of each answer's
// Why, with the status in it. They are constants at the construction
// so that a setting either one names is visible there.
whyUnexplained = "The server answered HTTP %d, in a form curious could not read as an\n" +
"answer. Answers like this usually pass on their own."
whyUnserved = "The server answered HTTP %d, in a form curious could not read as an\n" +
"answer, and sending the same request again will not change that. The\n" +
"address may not be the curious.pub service, something in between may be\n" +
"answering for it, or the service may not serve this request. The address\n" +
"comes from CURIOUS_API_URL."

tryAgainUnexplained = "Try again in a moment. If it keeps happening, check anything between\n" +
"this machine and the service, such as a proxy."
checkTheAddress = "Check CURIOUS_API_URL, or unset it to use the default, and anything\n" +
"between this machine and the service, such as a proxy. If both are\n" +
"right, please report this with the output of `curious version`."
)

// errorUnexplained is the answer to a code-less refusal that passes on
// its own. The caller says where the person was, what did not happen, and
// what it can and cannot tell about what had already happened.
func errorUnexplained(stage ui.Stage, what string, apiErr *api.APIError, situation string) *ui.Failure {
return ui.NewFailure(
ui.IDServerErrorUnexplained,
stage,
what,
fmt.Sprintf(whyUnexplained, apiErr.Status)+thenSituation(situation), ui.NextWait,
tryAgainUnexplained)
}

// requestUnserved is the answer to a code-less refusal that does not pass
// by waiting. It names what can be checked from this machine.
func requestUnserved(stage ui.Stage, what string, apiErr *api.APIError, situation string) *ui.Failure {
return ui.NewFailure(
ui.IDServerRequestUnserved,
stage,
what,
fmt.Sprintf(whyUnserved, apiErr.Status)+thenSituation(situation), ui.NextGiveUp,
checkTheAddress)
}

// thenSituation is the caller's paragraph about what had already
// happened, or nothing when it has none.
func thenSituation(situation string) string {
if situation == "" {
return ""
}
return "\n\n" + situation
}
Loading
Loading