<commit_before>package sqlr

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"
)

var db *sql.DB

func ExampleSchema_Prepare() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Define different schemas for different dialects and naming conventions

	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
	)

	mysql := NewSchema(
		WithDialect(MySQL),
		WithNamingConvention(LowerCase),
	)

	postgres := NewSchema(
		WithDialect(Postgres),
		WithNamingConvention(SnakeCase),
	)

	// for each schema, print the SQL generated for each statement
	for _, schema := range []*Schema{mssql, mysql, postgres} {
		stmt, err := schema.Prepare(UserRow{}, `insert into users({}) values({})`)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println(stmt)
	}

	// Output:
	// insert into users([GivenName], [FamilyName]) values(?, ?)
	// insert into users(`givenname`, `familyname`) values(?, ?)
	// insert into users("given_name", "family_name") values($1, $2)
}

func ExampleStmt_Exec_insert() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `insert into users({}) values({})`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	row := UserRow{
		GivenName:  "John",
		FamilyName: "Citizen",
	}

	_, err = stmt.Exec(ctx, db, row)

	if err != nil {
		log.Fatal(err)
	}
}

func ExampleStmt_Exec_update() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `update users set {} where {}`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	row := UserRow{
		ID:         42,
		GivenName:  "John",
		FamilyName: "Citizen",
	}

	_, err = stmt.Exec(ctx, db, row)

	if err != nil {
		log.Fatal(err)
	}
}

func ExampleStmt_Exec_delete() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `delete from users where {}`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	row := UserRow{
		ID:         42,
		GivenName:  "John",
		FamilyName: "Citizen",
	}

	_, err = stmt.Exec(ctx, db, row)

	if err != nil {
		log.Fatal(err)
	}
}

func ExampleStmt_Select_oneRow() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `select {} from users where {}`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	// find user with ID=42
	var row UserRow
	n, err := stmt.Select(ctx, db, &row, 42)
	if err != nil {
		log.Fatal(err)
	}
	if n > 0 {
		log.Printf("found: %v", row)
	} else {
		log.Printf("not found")
	}
}

func ExampleStmt_Select_multipleRows() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `
		select {alias u}
		from users u
		inner join user_search_terms t on t.user_id = u.id
		where t.search_term like ?
		limit ? offset ?`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	// find users with search terms
	var rows []UserRow
	n, err := stmt.Select(ctx, db, &rows, "smith%", 0, 100)
	if err != nil {
		log.Fatal(err)
	}
	if n > 0 {
		for i, row := range rows {
			log.Printf("row %d: %v", i, row)
		}
	} else {
		log.Printf("not found")
	}
}

func ExampleWithIdentifier() {
	// Take an example of a program that operates against an SQL Server
	// database where a table is named "[User]", but the same table is
	// named "users" in the Postgres schema.
	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
		WithIdentifier("[User]", "users"),
		WithIdentifier("UserId", "user_id"),
		WithIdentifier("[Name]", "name"),
	)
	postgres := NewSchema(
		WithDialect(Postgres),
		WithNamingConvention(SnakeCase),
	)

	type User struct {
		UserId int `sql:"primary key"`
		Name   string
	}

	// If a statement is prepared and executed for both
	const query = "select {} from users where user_id = ?"

	mssqlStmt, err := mssql.Prepare(User{}, query)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(mssqlStmt)
	postgresStmt, err := postgres.Prepare(User{}, query)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(postgresStmt)

	// Output:
	// select [UserId], [Name] from [User] where UserId = ?
	// select "user_id", "name" from users where user_id = $1
}

/**** obsolete

func ExampleInsert() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	row := UserRow{
		GivenName:  "John",
		FamilyName: "Citizen",
	}
	err := sqlr.Insert(db, &row, "users")
	if err != nil {
		log.Fatal(err)
	}

	// row.ID will contain the new ID for the row
	log.Printf("Row inserted, ID=%d", row.ID)
}

func ExampleSchema_Insert() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := sqlr.Schema{
		Dialect:    sqlr.DialectFor("mssql"),
		Convention: sqlr.ConventionSame,
	}

	row := UserRow{
		GivenName:  "John",
		FamilyName: "Citizen",
	}
	err := mssql.Insert(db, &row, "users")
	if err != nil {
		log.Fatal(err)
	}

	// row.ID will contain the new ID for the row
	log.Printf("Row inserted, ID=%d", row.ID)
}

func ExampleUpdate() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	row := UserRow{
		ID:         43,
		GivenName:  "John",
		FamilyName: "Citizen",
	}
	n, err := sqlr.Update(db, &row, "users")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Number of rows updated = %d", n)
}

func ExampleSchema_Update() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := sqlr.Schema{
		Dialect:    sqlr.DialectFor("mssql"),
		Convention: sqlr.ConventionSame,
	}

	row := UserRow{
		ID:         43,
		GivenName:  "John",
		FamilyName: "Citizen",
	}
	n, err := mssql.Update(db, &row, "users")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Number of rows updated = %d", n)
}

func ExampleDelete() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	row := UserRow{
		ID:         43,
		GivenName:  "John",
		FamilyName: "Citizen",
	}
	n, err := sqlr.Delete(db, &row, "users")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Number of rows deleted = %d", n)
}

func ExampleSchema_Delete() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := sqlr.Schema{
		Dialect:    sqlr.DialectFor("mssql"),
		Convention: sqlr.ConventionSame,
	}

	row := UserRow{
		ID:         43,
		GivenName:  "John",
		FamilyName: "Citizen",
	}
	n, err := mssql.Delete(db, &row, "users")
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Number of rows deleted = %d", n)
}

func ExampleDialectFor() {
	// Set the default dialect for PostgreSQL.
	sqlr.Default.Dialect = sqlr.DialectFor("postgres")
}

func ExampleSelect_oneRow() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// find user with ID=42
	var row UserRow
	n, err := sqlr.Select(db, &row, `select {} from users where ID=?`, 42)
	if err != nil {
		log.Fatal(err)
	}

	if n > 0 {
		log.Printf("found: %v", row)
	} else {
		log.Printf("not found")
	}
}
****/

func ExampleSchema_Select_oneRow() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
	)

	// find user with ID=42
	var row UserRow
	n, err := mssql.Select(db, &row, `select {} from [Users] where ID=?`, 42)
	if err != nil {
		log.Fatal(err)
	}

	if n > 0 {
		log.Printf("found: %v", row)
	} else {
		log.Printf("not found")
	}
}

func ExampleSchema_Select_multipleRows() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
	)

	// find users with search terms
	var rows []UserRow
	n, err := mssql.Select(db, &rows, `
		select {alias u}
		from [Users] u
		inner join [UserSearchTerms] t on t.UserID = u.ID
		where t.SearchTerm like ?
		limit ? offset ?`, "smith%", 100, 0)
	if err != nil {
		log.Fatal(err)
	}

	if n > 0 {
		for i, row := range rows {
			log.Printf("row %d: %v", i, row)
		}
	} else {
		log.Printf("not found")
	}
}

func ExampleSession_MakeQuery() {
	var schema Schema
	ctx := context.Background()
	tx := beginTransaction() // get a DB transaction, assumes no errors
	defer tx.Commit()        // WARNING: no error handling here: example code only

	type Row struct {
		ID            int64 `sql:"primary key"`
		Name          string
		FavoriteColor string
	}

	// begin a request-scoped database session
	sess := NewSession(ctx, tx, &schema)

	// data access object
	var dao struct {
		Get    func(id int64) (*Row, error)
		Select func(query string, args ...interface{}) ([]*Row, error)
	}

	sess.MakeQuery(&dao.Get, &dao.Select)

	// can now use the type-safe data access functions
	row42, err := dao.Get(42)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Row 42:", row42)

	redRows, err := dao.Select("select {} from rows where favorite_color = ?", "red")
	if err != nil {
		log.Fatal(err)
	}
	for _, row := range redRows {
		log.Println("Likes red:", row)
	}
}

func beginTransaction() *sql.Tx {
	return nil
}

func ExampleMustCreateSchema() {
	type UserRow struct {
		ID   int64 `sql:"primary key"`
		Name string
	}

	type PostRow struct {
		ID        int64 `sql:"primary key"`
		UserID    int64
		CreatedAt time.Time
		Title     string
		Content   string
	}

	schema := MustCreateSchema(
		WithNamingConvention(SnakeCase),
		WithTables(TablesConfig{
			(*UserRow)(nil): {
				TableName: "users_table", // override naming convention for table
			},
			(*PostRow)(nil): {
				Columns: ColumnsConfig{
					"CreatedAt": {
						ColumnName: "create_timestamp", // override naming convention
						EmptyNull:  true,               // store empty string as null
					},
				},
			},
		}),
	)

	doSomethingWith(schema)
}

func ExampleColumnConfig() {
	type Address struct {
		Street   string
		Locality string
		City     string
		State    string
		Country  string
	}

	type PersonRow struct {
		ID      int64 `sql:"primary key"`
		Name    string
		DOB     time.Time
		Address Address
	}

	schema := MustCreateSchema(
		WithTables(TablesConfig{
			(*PersonRow)(nil): {
				Columns: ColumnsConfig{
					"Address.Locality": {
						ColumnName: "address_suburb",
					},
					"DOB": {
						ColumnName: "date_of_birth",
						EmptyNull:  true,
					},
				},
			},
		}),
	)

	doSomethingWith(schema)
}

func ExampleColumnsConfig() {
	type Address struct {
		Street   string
		Locality string
		City     string
		State    string
		Country  string
	}

	type PersonRow struct {
		ID      int64 `sql:"primary key"`
		Name    string
		DOB     time.Time
		Address Address
	}

	schema := MustCreateSchema(
		WithTables(TablesConfig{
			(*PersonRow)(nil): {
				Columns: ColumnsConfig{
					"Address.Locality": {
						ColumnName: "address_suburb",
					},
					"DOB": {
						ColumnName: "date_of_birth",
						EmptyNull:  true,
					},
				},
			},
		}),
	)

	doSomethingWith(schema)
}

func doSomethingWith(v interface{}) {

}
<commit_msg>Remove obsolete tests (commented out).<commit_after>package sqlr

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"
)

var db *sql.DB

func ExampleSchema_Prepare() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Define different schemas for different dialects and naming conventions

	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
	)

	mysql := NewSchema(
		WithDialect(MySQL),
		WithNamingConvention(LowerCase),
	)

	postgres := NewSchema(
		WithDialect(Postgres),
		WithNamingConvention(SnakeCase),
	)

	// for each schema, print the SQL generated for each statement
	for _, schema := range []*Schema{mssql, mysql, postgres} {
		stmt, err := schema.Prepare(UserRow{}, `insert into users({}) values({})`)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println(stmt)
	}

	// Output:
	// insert into users([GivenName], [FamilyName]) values(?, ?)
	// insert into users(`givenname`, `familyname`) values(?, ?)
	// insert into users("given_name", "family_name") values($1, $2)
}

func ExampleStmt_Exec_insert() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `insert into users({}) values({})`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	row := UserRow{
		GivenName:  "John",
		FamilyName: "Citizen",
	}

	_, err = stmt.Exec(ctx, db, row)

	if err != nil {
		log.Fatal(err)
	}
}

func ExampleStmt_Exec_update() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `update users set {} where {}`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	row := UserRow{
		ID:         42,
		GivenName:  "John",
		FamilyName: "Citizen",
	}

	_, err = stmt.Exec(ctx, db, row)

	if err != nil {
		log.Fatal(err)
	}
}

func ExampleStmt_Exec_delete() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `delete from users where {}`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	row := UserRow{
		ID:         42,
		GivenName:  "John",
		FamilyName: "Citizen",
	}

	_, err = stmt.Exec(ctx, db, row)

	if err != nil {
		log.Fatal(err)
	}
}

func ExampleStmt_Select_oneRow() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `select {} from users where {}`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	// find user with ID=42
	var row UserRow
	n, err := stmt.Select(ctx, db, &row, 42)
	if err != nil {
		log.Fatal(err)
	}
	if n > 0 {
		log.Printf("found: %v", row)
	} else {
		log.Printf("not found")
	}
}

func ExampleStmt_Select_multipleRows() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	schema := NewSchema()
	ctx := context.Background()

	stmt, err := schema.Prepare(UserRow{}, `
		select {alias u}
		from users u
		inner join user_search_terms t on t.user_id = u.id
		where t.search_term like ?
		limit ? offset ?`)
	if err != nil {
		log.Fatal(err)
	}

	// ... later on ...

	// find users with search terms
	var rows []UserRow
	n, err := stmt.Select(ctx, db, &rows, "smith%", 0, 100)
	if err != nil {
		log.Fatal(err)
	}
	if n > 0 {
		for i, row := range rows {
			log.Printf("row %d: %v", i, row)
		}
	} else {
		log.Printf("not found")
	}
}

func ExampleWithIdentifier() {
	// Take an example of a program that operates against an SQL Server
	// database where a table is named "[User]", but the same table is
	// named "users" in the Postgres schema.
	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
		WithIdentifier("[User]", "users"),
		WithIdentifier("UserId", "user_id"),
		WithIdentifier("[Name]", "name"),
	)
	postgres := NewSchema(
		WithDialect(Postgres),
		WithNamingConvention(SnakeCase),
	)

	type User struct {
		UserId int `sql:"primary key"`
		Name   string
	}

	// If a statement is prepared and executed for both
	const query = "select {} from users where user_id = ?"

	mssqlStmt, err := mssql.Prepare(User{}, query)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(mssqlStmt)
	postgresStmt, err := postgres.Prepare(User{}, query)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(postgresStmt)

	// Output:
	// select [UserId], [Name] from [User] where UserId = ?
	// select "user_id", "name" from users where user_id = $1
}

func ExampleSchema_Select_oneRow() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
	)

	// find user with ID=42
	var row UserRow
	n, err := mssql.Select(db, &row, `select {} from [Users] where ID=?`, 42)
	if err != nil {
		log.Fatal(err)
	}

	if n > 0 {
		log.Printf("found: %v", row)
	} else {
		log.Printf("not found")
	}
}

func ExampleSchema_Select_multipleRows() {
	type UserRow struct {
		ID         int `sql:"primary key autoincrement"`
		GivenName  string
		FamilyName string
	}

	// Schema for an MSSQL database, where column names
	// are the same as the Go struct field names.
	mssql := NewSchema(
		WithDialect(MSSQL),
		WithNamingConvention(SameCase),
	)

	// find users with search terms
	var rows []UserRow
	n, err := mssql.Select(db, &rows, `
		select {alias u}
		from [Users] u
		inner join [UserSearchTerms] t on t.UserID = u.ID
		where t.SearchTerm like ?
		limit ? offset ?`, "smith%", 100, 0)
	if err != nil {
		log.Fatal(err)
	}

	if n > 0 {
		for i, row := range rows {
			log.Printf("row %d: %v", i, row)
		}
	} else {
		log.Printf("not found")
	}
}

func ExampleSession_MakeQuery() {
	var schema Schema
	ctx := context.Background()
	tx := beginTransaction() // get a DB transaction, assumes no errors
	defer tx.Commit()        // WARNING: no error handling here: example code only

	type Row struct {
		ID            int64 `sql:"primary key"`
		Name          string
		FavoriteColor string
	}

	// begin a request-scoped database session
	sess := NewSession(ctx, tx, &schema)

	// data access object
	var dao struct {
		Get    func(id int64) (*Row, error)
		Select func(query string, args ...interface{}) ([]*Row, error)
	}

	sess.MakeQuery(&dao.Get, &dao.Select)

	// can now use the type-safe data access functions
	row42, err := dao.Get(42)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Row 42:", row42)

	redRows, err := dao.Select("select {} from rows where favorite_color = ?", "red")
	if err != nil {
		log.Fatal(err)
	}
	for _, row := range redRows {
		log.Println("Likes red:", row)
	}
}

func beginTransaction() *sql.Tx {
	return nil
}

func ExampleMustCreateSchema() {
	type UserRow struct {
		ID   int64 `sql:"primary key"`
		Name string
	}

	type PostRow struct {
		ID        int64 `sql:"primary key"`
		UserID    int64
		CreatedAt time.Time
		Title     string
		Content   string
	}

	schema := MustCreateSchema(
		WithNamingConvention(SnakeCase),
		WithTables(TablesConfig{
			(*UserRow)(nil): {
				TableName: "users_table", // override naming convention for table
			},
			(*PostRow)(nil): {
				Columns: ColumnsConfig{
					"CreatedAt": {
						ColumnName: "create_timestamp", // override naming convention
						EmptyNull:  true,               // store empty string as null
					},
				},
			},
		}),
	)

	doSomethingWith(schema)
}

func ExampleColumnConfig() {
	type Address struct {
		Street   string
		Locality string
		City     string
		State    string
		Country  string
	}

	type PersonRow struct {
		ID      int64 `sql:"primary key"`
		Name    string
		DOB     time.Time
		Address Address
	}

	schema := MustCreateSchema(
		WithTables(TablesConfig{
			(*PersonRow)(nil): {
				Columns: ColumnsConfig{
					"Address.Locality": {
						ColumnName: "address_suburb",
					},
					"DOB": {
						ColumnName: "date_of_birth",
						EmptyNull:  true,
					},
				},
			},
		}),
	)

	doSomethingWith(schema)
}

func ExampleColumnsConfig() {
	type Address struct {
		Street   string
		Locality string
		City     string
		State    string
		Country  string
	}

	type PersonRow struct {
		ID      int64 `sql:"primary key"`
		Name    string
		DOB     time.Time
		Address Address
	}

	schema := MustCreateSchema(
		WithTables(TablesConfig{
			(*PersonRow)(nil): {
				Columns: ColumnsConfig{
					"Address.Locality": {
						ColumnName: "address_suburb",
					},
					"DOB": {
						ColumnName: "date_of_birth",
						EmptyNull:  true,
					},
				},
			},
		}),
	)

	doSomethingWith(schema)
}

func doSomethingWith(v interface{}) {

}
<|endoftext|>


<commit_before>package dotquotedetect

import (
	"github.com/reiver/go-whitespace"

	"unicode/utf8"
)

var (
	errNotKeyLenLessThanTwo = newNotKeyComplainer("length less that 2")
)

func DetectKey(b []byte) (int, int, error) {
	if nil == b {
		return -1, -1, errNilBytes
	}

	lenb := len(b)

	if 0 >= lenb {
		return -1, -1, errEmptyRequest
	}

	if 2 > lenb {
		return -1, -1, errNotKeyLenLessThanTwo
	}

	{
		r0, _ := utf8.DecodeRune(b)
		if utf8.RuneError == r0 {
			return -1, -1, errNotUTF8
		}

		if '"' != r0 {
			return -1, -1, newNotKeyComplainer("first rune not a double quote (i.e,. \"): %q %d", string(r0), r0)
		}
	}

	p := b

	begin, end, err := DetectQuote(p)
	if nil != err {
		return -1, -1, newNotKeyComplainer("could not detect quoted part of key: %s", err)
	}
	p = p[end:]

	for {
		if 0 >= len(p) {
			return -1, -1, errEmptyRequest
		}

		r0, size := utf8.DecodeRune(p)
		if utf8.RuneError == r0 {
			return -1, -1, errNotUTF8
		}
		if '=' == r0 {
			break
		}
		if whitespace.IsWhitespace(r0) {
			break
		}

		if '.' != r0 {
			return -1, -1, newNotKeyComplainer("not a dot at index %d of |||||%s|||||: %q (%d)", end, string(b), string(r0), r0)
		}

		p = p[size:]
		end += size

		_, finish, err := DetectQuote(p)
		if nil != err {
			return -1, -1, newNotKeyComplainer("could not detect quoted part of key: %s", err)
		}

		end += finish

                p = p[finish:]
	}

	return begin, end, nil
}
<commit_msg>added some docs<commit_after>package dotquotedetect

import (
	"github.com/reiver/go-whitespace"

	"unicode/utf8"
)

var (
	errNotKeyLenLessThanTwo = newNotKeyComplainer("length less that 2")
)

// DetectKey detect for a dotquote key in the dotquote data in a []byte, and returns
// the beginning index and the ending index.
func DetectKey(b []byte) (int, int, error) {
	if nil == b {
		return -1, -1, errNilBytes
	}

	lenb := len(b)

	if 0 >= lenb {
		return -1, -1, errEmptyRequest
	}

	if 2 > lenb {
		return -1, -1, errNotKeyLenLessThanTwo
	}

	{
		r0, _ := utf8.DecodeRune(b)
		if utf8.RuneError == r0 {
			return -1, -1, errNotUTF8
		}

		if '"' != r0 {
			return -1, -1, newNotKeyComplainer("first rune not a double quote (i.e,. \"): %q %d", string(r0), r0)
		}
	}

	p := b

	begin, end, err := DetectQuote(p)
	if nil != err {
		return -1, -1, newNotKeyComplainer("could not detect quoted part of key: %s", err)
	}
	p = p[end:]

	for {
		if 0 >= len(p) {
			return -1, -1, errEmptyRequest
		}

		r0, size := utf8.DecodeRune(p)
		if utf8.RuneError == r0 {
			return -1, -1, errNotUTF8
		}
		if '=' == r0 {
			break
		}
		if whitespace.IsWhitespace(r0) {
			break
		}

		if '.' != r0 {
			return -1, -1, newNotKeyComplainer("not a dot at index %d of |||||%s|||||: %q (%d)", end, string(b), string(r0), r0)
		}

		p = p[size:]
		end += size

		_, finish, err := DetectQuote(p)
		if nil != err {
			return -1, -1, newNotKeyComplainer("could not detect quoted part of key: %s", err)
		}

		end += finish

                p = p[finish:]
	}

	return begin, end, nil
}
<|endoftext|>


<commit_before>package gop

import (
	"fmt"
	"github.com/gorilla/mux"
	"github.com/gorilla/schema"
	"io/ioutil"
	"os"
	"runtime"
	"runtime/debug"
	"time"
)

var decoder = schema.NewDecoder() // Single-instance so struct info cached

func gopHandler(g *Req) error {
	enabled, _ := g.Cfg.GetBool("gop", "enable_gop_urls", false)
	if !enabled {
		return NotFound("Not enabled")
	}
	vars := mux.Vars(g.R)
	switch vars["action"] {
	case "status":
		{
			return handleStatus(g)
		}
	case "stack":
		{
			return handleStack(g)
		}
	case "mem":
		{
			return handleMem(g)
		}
	case "test":
		{
			return handleTest(g)
		}
	case "config":
		{
			return handleConfig(g)
		}
	default:
		{
			return ErrNotFound
		}
	}
}

func handleConfig(g *Req) error {
	// We can be called with and without section+key
	vars := mux.Vars(g.R)
	section := vars["section"]
	key := vars["key"]
	if g.R.Method == "PUT" {
		if section == "" {
			return BadRequest("No section in url")
		}
		if key == "" {
			return BadRequest("No key in url")
		}
		value, err := ioutil.ReadAll(g.R.Body)
		if err != nil {
			return ServerError("Failed to read value: " + err.Error())
		}
		g.Cfg.PersistentOverride(section, key, string(value))
	}

	if section != "" {
		if key != "" {
			strVal, found := g.Cfg.Get(section, key, "")
			if found {
				return g.SendJson("config", strVal)
			} else {
				return NotFound("No such key in section")
			}
		} else {
			sectionKeys := g.Cfg.SectionKeys(section)
			sectionMap := make(map[string]string)
			for _, key := range sectionKeys {
				strVal, _ := g.Cfg.Get(section, key, "")
				sectionMap[key] = strVal
			}
			return g.SendJson("config", sectionMap)
		}
	} else {
		configMap := g.Cfg.AsMap()
		return g.SendJson("config", configMap)
	}
	return nil
}

func handleMem(g *Req) error {
	if g.R.Method == "POST" {
		type memParams struct {
			GCNow     int `schema:"gc_now"`
			GCPercent int `schema:"gc_percent"`
		}
		params := memParams{}
		err := g.Decoder.Decode(&params, g.R.Form)
		if err != nil {
			g.Error("Failed to decode params: " + err.Error())
			return ServerError("Failed to decode params: " + err.Error())
		}
		msg := "Adjusting mem system\n"
		if params.GCNow > 0 {
			info := "Running GC by request to handler"
			g.Info(info)
			msg += info + "\n"

			runtime.GC()
		}
		if params.GCPercent > 0 {
			oldVal := debug.SetGCPercent(params.GCPercent)
			info := fmt.Sprintf("Set GC%% to [%d] was [%d]", params.GCPercent, oldVal)
			g.Info(info)
			msg += info + "\n"
		}
		return g.SendText([]byte(msg))
		return nil
	}
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)
	return g.SendJson("memstats", memStats)
}

func handleStack(g *Req) error {
	buf := make([]byte, 1024)
	for {
		traceLen := runtime.Stack(buf, true)
		if traceLen < len(buf) {
			break
		}
		// Try a bigger buf
		buf = make([]byte, 2*len(buf))
	}
	g.W.Write(buf)
	return nil
}

func handleStatus(g *Req) error {
	type requestInfo struct {
		Id       int
		Method   string
		Url      string
		Duration float64
		RemoteIP string
		IsHTTPS  bool
	}
	type requestStatus struct {
		ProjectName   string
		AppName       string
		Pid           int
		StartTime     time.Time
		UptimeSeconds float64
		NumGoros      int
		RequestInfo   []requestInfo
	}
	appDuration := time.Since(g.app.startTime).Seconds()
	status := requestStatus{
		ProjectName:   g.app.ProjectName,
		AppName:       g.app.AppName,
		Pid:           os.Getpid(),
		StartTime:     g.app.startTime,
		UptimeSeconds: appDuration,
		NumGoros:      runtime.NumGoroutine(),
	}
	reqChan := make(chan *Req)
	g.app.getReqs <- reqChan
	for req := range reqChan {
		reqDuration := time.Since(req.startTime)
		info := requestInfo{
			Id:       req.id,
			Method:   req.R.Method,
			Url:      req.R.URL.String(),
			Duration: reqDuration.Seconds(),
			RemoteIP: req.RealRemoteIP,
			IsHTTPS:  req.IsHTTPS,
		}
		status.RequestInfo = append(status.RequestInfo, info)
	}
	return g.SendJson("status", status)
	/*
	   fmt.Fprintf(w, "%s - %s PID %d up for %.3fs (%s)\n\n", g.app.ProjectName, g.app.AppName, os.Getpid(), appDuration, g.app.startTime)
	   for req := range reqChan {
	       reqDuration := time.Since(req.startTime).Seconds()
	       fmt.Fprintf(w, "%d: %.3f\t%s\t%s\n", req.id, reqDuration, req.r.Method, req.r.URL.String())
	   }
	*/
}

func handleTest(g *Req) error {
	type details struct {
		Kbytes int `schema:"kbytes"`
		Secs   int `schema:"secs"`
	}
	args := details{}
	err := g.Decoder.Decode(&args, g.R.Form)
	if err != nil {
		return ServerError("Failed to decode params: " + err.Error())
	}
	g.Debug("Test req - taking %d secs, %d KB", args.Secs, args.Kbytes)
	buf := make([]byte, args.Kbytes*1024)
	// Touch/do something with the mem to ensure it's actually allocated
	for i := range buf {
		buf[i] = 1
	}
	time.Sleep(time.Second * time.Duration(args.Secs))
	g.SendText([]byte(fmt.Sprintf("Slow request took additional %d secs and allocated additional %d KB\n", args.Secs, args.Kbytes)))
	return nil
}

func (a *App) registerGopHandlers() {
	a.HandleFunc("/gop/{action}", gopHandler)
	a.HandleFunc("/gop/config/{section}", handleConfig)
	a.HandleFunc("/gop/config/{section}/{key}", handleConfig)
}
<commit_msg>export pprof performnace counters (see 'go tool pprof') based on cfg var. recheck on cfg reload so can add profiling to running servers<commit_after>package gop

import (
	"fmt"
	"github.com/gorilla/mux"
	"github.com/gorilla/schema"
	"io/ioutil"
	"net/http/pprof"
	"os"
	"runtime"
	"runtime/debug"
	"time"
)

var decoder = schema.NewDecoder() // Single-instance so struct info cached

func gopHandler(g *Req) error {
	enabled, _ := g.Cfg.GetBool("gop", "enable_gop_urls", false)
	if !enabled {
		return NotFound("Not enabled")
	}
	vars := mux.Vars(g.R)
	switch vars["action"] {
	case "status":
		{
			return handleStatus(g)
		}
	case "stack":
		{
			return handleStack(g)
		}
	case "mem":
		{
			return handleMem(g)
		}
	case "test":
		{
			return handleTest(g)
		}
	case "config":
		{
			return handleConfig(g)
		}
	default:
		{
			return ErrNotFound
		}
	}
}

func handleConfig(g *Req) error {
	// We can be called with and without section+key
	vars := mux.Vars(g.R)
	section := vars["section"]
	key := vars["key"]
	if g.R.Method == "PUT" {
		if section == "" {
			return BadRequest("No section in url")
		}
		if key == "" {
			return BadRequest("No key in url")
		}
		value, err := ioutil.ReadAll(g.R.Body)
		if err != nil {
			return ServerError("Failed to read value: " + err.Error())
		}
		g.Cfg.PersistentOverride(section, key, string(value))
	}

	if section != "" {
		if key != "" {
			strVal, found := g.Cfg.Get(section, key, "")
			if found {
				return g.SendJson("config", strVal)
			} else {
				return NotFound("No such key in section")
			}
		} else {
			sectionKeys := g.Cfg.SectionKeys(section)
			sectionMap := make(map[string]string)
			for _, key := range sectionKeys {
				strVal, _ := g.Cfg.Get(section, key, "")
				sectionMap[key] = strVal
			}
			return g.SendJson("config", sectionMap)
		}
	} else {
		configMap := g.Cfg.AsMap()
		return g.SendJson("config", configMap)
	}
	return nil
}

func handleMem(g *Req) error {
	if g.R.Method == "POST" {
		type memParams struct {
			GCNow     int `schema:"gc_now"`
			GCPercent int `schema:"gc_percent"`
		}
		params := memParams{}
		err := g.Decoder.Decode(&params, g.R.Form)
		if err != nil {
			g.Error("Failed to decode params: " + err.Error())
			return ServerError("Failed to decode params: " + err.Error())
		}
		msg := "Adjusting mem system\n"
		if params.GCNow > 0 {
			info := "Running GC by request to handler"
			g.Info(info)
			msg += info + "\n"

			runtime.GC()
		}
		if params.GCPercent > 0 {
			oldVal := debug.SetGCPercent(params.GCPercent)
			info := fmt.Sprintf("Set GC%% to [%d] was [%d]", params.GCPercent, oldVal)
			g.Info(info)
			msg += info + "\n"
		}
		return g.SendText([]byte(msg))
		return nil
	}
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)
	return g.SendJson("memstats", memStats)
}

func handleStack(g *Req) error {
	buf := make([]byte, 1024)
	for {
		traceLen := runtime.Stack(buf, true)
		if traceLen < len(buf) {
			break
		}
		// Try a bigger buf
		buf = make([]byte, 2*len(buf))
	}
	g.W.Write(buf)
	return nil
}

func handleStatus(g *Req) error {
	type requestInfo struct {
		Id       int
		Method   string
		Url      string
		Duration float64
		RemoteIP string
		IsHTTPS  bool
	}
	type requestStatus struct {
		ProjectName   string
		AppName       string
		Pid           int
		StartTime     time.Time
		UptimeSeconds float64
		NumGoros      int
		RequestInfo   []requestInfo
	}
	appDuration := time.Since(g.app.startTime).Seconds()
	status := requestStatus{
		ProjectName:   g.app.ProjectName,
		AppName:       g.app.AppName,
		Pid:           os.Getpid(),
		StartTime:     g.app.startTime,
		UptimeSeconds: appDuration,
		NumGoros:      runtime.NumGoroutine(),
	}
	reqChan := make(chan *Req)
	g.app.getReqs <- reqChan
	for req := range reqChan {
		reqDuration := time.Since(req.startTime)
		info := requestInfo{
			Id:       req.id,
			Method:   req.R.Method,
			Url:      req.R.URL.String(),
			Duration: reqDuration.Seconds(),
			RemoteIP: req.RealRemoteIP,
			IsHTTPS:  req.IsHTTPS,
		}
		status.RequestInfo = append(status.RequestInfo, info)
	}
	return g.SendJson("status", status)
	/*
	   fmt.Fprintf(w, "%s - %s PID %d up for %.3fs (%s)\n\n", g.app.ProjectName, g.app.AppName, os.Getpid(), appDuration, g.app.startTime)
	   for req := range reqChan {
	       reqDuration := time.Since(req.startTime).Seconds()
	       fmt.Fprintf(w, "%d: %.3f\t%s\t%s\n", req.id, reqDuration, req.r.Method, req.r.URL.String())
	   }
	*/
}

func handleTest(g *Req) error {
	type details struct {
		Kbytes int `schema:"kbytes"`
		Secs   int `schema:"secs"`
	}
	args := details{}
	err := g.Decoder.Decode(&args, g.R.Form)
	if err != nil {
		return ServerError("Failed to decode params: " + err.Error())
	}
	g.Debug("Test req - taking %d secs, %d KB", args.Secs, args.Kbytes)
	buf := make([]byte, args.Kbytes*1024)
	// Touch/do something with the mem to ensure it's actually allocated
	for i := range buf {
		buf[i] = 1
	}
	time.Sleep(time.Second * time.Duration(args.Secs))
	g.SendText([]byte(fmt.Sprintf("Slow request took additional %d secs and allocated additional %d KB\n", args.Secs, args.Kbytes)))
	return nil
}

func (a *App) registerGopHandlers() {
	a.HandleFunc("/gop/{action}", gopHandler)
	a.HandleFunc("/gop/config/{section}", handleConfig)
	a.HandleFunc("/gop/config/{section}/{key}", handleConfig)

	a.Cfg.AddOnChangeCallback(func(cfg *Config) { a.maybeRegisterPProfHandlers() })
}

func (a *App) maybeRegisterPProfHandlers() {
	if enableProfiling, _ := a.Cfg.GetBool("gop", "enable_profiling_urls", false); enableProfiling {
		a.HandleFunc("/pprof/cmdline", func(g *Req) error {
			pprof.Cmdline(g.W, g.R)
			return nil
		})

		a.HandleFunc("/pprof/symbol", func(g *Req) error {
			pprof.Symbol(g.W, g.R)
			return nil
		})

		a.HandleFunc("/pprof/profile", func(g *Req) error {
			pprof.Profile(g.W, g.R)
			return nil
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
)

func NotFound(id string, w http.ResponseWriter) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusNotFound)
	if err := json.NewEncoder(w).Encode(jsonErr{Code: http.StatusNotFound, Text: "Not Found", Id: id}); err != nil {
		Error.Panic(err)
	}

}

func (myjournal *Myjournal) List(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	var err error
	var entry string
	var journals Journals

	entry = vars["entryId"]
	journals, err = myjournal.Parse(entry)
	if (err != nil) && (err.Error() == "NotFound") {
		NotFound(entry, w)
		return
	}

	b, err := json.MarshalIndent(journals, "", "    ")
	if err != nil {
		Error.Panicf("ERROR: encoding JSON: %s\n", err)
	}
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	w.Write(b)
}

func (mj *Myjournal) Index(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	var err error
	var entry string
	var journals Journals
	var current Journals
	var list bool

	journals, err = myjournal.Parse("*")

	entry = vars["entryId"]
	if entry == "" {
		entry = journals[0].Id
		list = true
	} else {
		list = false
	}

	current, err = myjournal.Parse(entry)
	if (err != nil) && (err.Error() == "NotFound") {
		NotFound(entry, w)
		return
	}

	var nextid string
	var previd string
	if currpos := journals.CurrPosition(entry); currpos != -1 {
		nextid = journals.NextId(currpos)
		previd = journals.PrevId(currpos)
	}

	var csslookup = make(map[string]string)
	csslookup["horrible"] = "danger"
	csslookup["Tarlov"] = "danger"
	csslookup["IN_experiment"] = "warning"

	page := Page{Title: "Vitaliy's Food Journal",
		IsList:    list,
		PrevId:    previd,
		NextId:    nextid,
		CssLookup: csslookup,
		Navbar:    journals,
		Content:   current,
	}
	renderTemplate(w, "dop", &page)
}

func (mj *Myjournal) Test(w http.ResponseWriter, r *http.Request) {
	var csslookup = make(map[string]string)
	csslookup["horrible"] = "danger"
	csslookup["Tarlov"] = "danger"
	csslookup["IN_experiment"] = "warning"

	var journals Journals
	page := Page{Title: "Vitaliy's Food Journal",
		IsList:    false,
		PrevId:    "PREVID",
		NextId:    "NEXTID",
		CssLookup: csslookup,
		Navbar:    journals,
		Content:   journals,
	}
	b, err := json.MarshalIndent(page, "", "    ")
	if err != nil {
		Error.Panicf("ERROR: encoding JSON: %s\n", err)
	}
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	w.Write(b)
}
<commit_msg>refactored to use values from dop.json<commit_after>package main

import (
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
)

func NotFound(id string, w http.ResponseWriter) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusNotFound)
	if err := json.NewEncoder(w).Encode(jsonErr{Code: http.StatusNotFound, Text: "Not Found", Id: id}); err != nil {
		Error.Panic(err)
	}

}

func (myjournal *Myjournal) List(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	var err error
	var entry string
	var journals Journals

	entry = vars["entryId"]
	journals, err = myjournal.Parse(entry)
	if (err != nil) && (err.Error() == "NotFound") {
		NotFound(entry, w)
		return
	}

	b, err := json.MarshalIndent(journals, "", "    ")
	if err != nil {
		Error.Panicf("ERROR: encoding JSON: %s\n", err)
	}
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	w.Write(b)
}

func (mj *Myjournal) Index(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	var err error
	var entry string
	var journals Journals
	var current Journals
	var list bool

	journals, err = myjournal.Parse("*")

	entry = vars["entryId"]
	if entry == "" {
		entry = journals[0].Id
		list = true
	} else {
		list = false
	}

	current, err = myjournal.Parse(entry)
	if (err != nil) && (err.Error() == "NotFound") {
		NotFound(entry, w)
		return
	}

	var nextid string
	var previd string
	if currpos := journals.CurrPosition(entry); currpos != -1 {
		nextid = journals.NextId(currpos)
		previd = journals.PrevId(currpos)
	}

	page := Page{
		Title:     mj.Title,
		IsList:    list,
		PrevId:    previd,
		NextId:    nextid,
		CssLookup: mj.CssLookup,
		Navbar:    journals,
		Content:   current,
	}
	renderTemplate(w, "dop", &page)
}

func (mj *Myjournal) Test(w http.ResponseWriter, r *http.Request) {
	var csslookup = make(map[string]string)
	csslookup["horrible"] = "danger"
	csslookup["Tarlov"] = "danger"
	csslookup["IN_experiment"] = "warning"

	var journals Journals
	page := Page{Title: "Vitaliy's Food Journal",
		IsList:    false,
		PrevId:    "PREVID",
		NextId:    "NEXTID",
		CssLookup: csslookup,
		Navbar:    journals,
		Content:   journals,
	}
	b, err := json.MarshalIndent(page, "", "    ")
	if err != nil {
		Error.Panicf("ERROR: encoding JSON: %s\n", err)
	}
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	w.Write(b)
}
<|endoftext|>


<commit_before>package rst

import (
	"net/http"
	"strings"
	"time"
)

// noContent is used to run ResponseWriter.Write even when there's not data to
// write.
var noContent = []byte{}

/*
Resource represents a resource exposed on a REST service using an Endpoint.

There are other interfaces that can be implemented by a resource to either
control its projection in a response payload, or add support for advanced HTTP
features:

- The Ranger interface adds support for range requests and allows the resource to
return partial responses.

- The Marshaler interface allows you to customize the encoding process of the
resource and control the bytes returned in the payload of the response.

- The http.Handler interface can be used to gain direct access to the
ResponseWriter and Request. This is a low level method that should only be used
when you need to write chunked responses, or if you wish to add specific headers
such a Content-Disposition, etc.
*/
type Resource interface {
	ETag() string            // ETag identifying the current version of the resource.
	LastModified() time.Time // Date and time of the last modification of the resource.
	TTL() time.Duration      // Time to live, or caching duration of the resource.
}

/*
ValidateConditions returns true if the If-Unmodified-Since or the If-Match headers of
r are not matching with the current version of resource.

	func (ep *endpoint) Patch(vars RouteVars, r *http.Request) (Resource, error) {
		resource := db.Lookup(vars.Get("id"))
		if ValidateConditions(resource, r) {
			return nil, Conflict()
		}

		// apply the patch safely from here
	}
*/
func ValidateConditions(resource Resource, r *http.Request) bool {
	if d, err := time.Parse(rfc1123, r.Header.Get("If-Unmodified-Since")); err == nil {
		if d.Sub(resource.LastModified()) < 0 {
			return true
		}
	}
	if etag := r.Header.Get("If-Match"); etag != "" && etag != resource.ETag() {
		return true
	}
	return false
}

/*
Ranger is implemented by resources that support partial responses.

Range will only be called if the request contains a valid Range header.
Otherwise, it will be processed as a normal Get request.

	type Doc []byte
	// assuming Doc implements rst.Resource interface

	// Supported units will be displayed in the Accept-Range header
		func (d *Doc) Units() []string {
		return []string{"bytes"}
	}

	// Count returns the total number of range units available
	func (d *Doc) Count() uint64 {
		return uint64(len(d))
	}

	func (d *Doc) Range(rg *rst.Range) (*rst.ContentRange, rst.Resource, error) {
		cr := &ContentRange{rg, c.Count()}
		part := d[rg.From : rg.To+1]
		return cr, part, nil
	}
*/
type Ranger interface {
	// Supported range units
	Units() []string

	// Total number of units available
	Count() uint64

	// Range is used to return the part of the resource that is indicated by the
	// passed range.
	Range(*Range) (*ContentRange, Resource, error)
}

func writeError(err error, w http.ResponseWriter, r *http.Request) {
	ErrorHandler(err).ServeHTTP(w, r)
}

func writeResource(resource Resource, w http.ResponseWriter, r *http.Request) {
	// Time-based conditional retrieval
	if t, err := time.Parse(rfc1123, r.Header.Get("If-Modified-Since")); err == nil {
		if t.Sub(resource.LastModified()).Seconds() >= 0 {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// ETag-based conditional retrieval
	for _, t := range strings.Split(r.Header.Get("If-None-Match"), ";") {
		if t == resource.ETag() {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// Headers
	addVary(w.Header(), "Accept")
	w.Header().Set("Last-Modified", resource.LastModified().UTC().Format(rfc1123))
	w.Header().Set("ETag", resource.ETag())
	w.Header().Set("Expires", time.Now().Add(resource.TTL()).UTC().Format(rfc1123))

	// If resource implements http.Handler, let it write in the ResponseWriter
	// on its own.
	if handler, implemented := resource.(http.Handler); implemented {
		handler.ServeHTTP(w, r)
		return
	}

	var (
		contentType string
		b           []byte
		err         error
	)
	contentType, b, err = Marshal(resource, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.Header().Set("Content-Type", contentType)

	if compression := getCompressionFormat(b, r); compression != "" {
		w.Header().Set("Content-Encoding", compression)
	}

	if strings.ToUpper(r.Method) == Post {
		w.WriteHeader(http.StatusCreated)
		w.Write(b)
		return
	}

	if len(b) == 0 {
		w.WriteHeader(http.StatusNoContent)
		w.Write(noContent) // Allowing an override of ResponseWriter to work.
		return
	}

	if w.Header().Get("Content-Range") != "" {
		w.WriteHeader(http.StatusPartialContent)
	} else {
		w.WriteHeader(http.StatusOK)
	}

	if strings.ToUpper(r.Method) == Head {
		return
	}
	w.Write(b)
}

/*
Endpoint represents an access point exposing a resource in the REST service.
*/
type Endpoint interface{}

/*
Getter is implemented by endpoints allowing the GET and HEAD method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}
		return resource, nil
	}
*/
type Getter interface {
	// Returns the resource or an error. A nil resource pointer will generate
	// a response with status code 204 No Content.
	Get(RouteVars, *http.Request) (Resource, error)
}

// GetFunc allows a Getter.Get method to be used an http.Handler.
type GetFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f GetFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := getVars(r)

	resource, err := f(vars, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	if resource == nil {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	// Check if resource implements Ranger
	ranger, implemented := resource.(Ranger)
	if !implemented {
		writeResource(resource, w, r)
		return
	}
	w.Header().Set("Accept-Ranges", strings.Join(ranger.Units(), ", "))

	// Check if request contains a valid Range header, and check whether it's
	// a valid range.
	rg, err := ParseRange(r.Header.Get("Range"))
	if err != nil || rg.validate(ranger) != nil {
		writeResource(resource, w, r)
		return
	}

	// If-Range can either contain an ETag, or a date.
	// If the precondition fails, the Range header is ignored and the full
	// resource is returned.
	if raw := r.Header.Get("If-Range"); raw != "" {
		date, _ := time.Parse(rfc1123, raw)
		if !date.Equal(resource.LastModified()) && raw != resource.ETag() {
			writeResource(resource, w, r)
			return
		}
	}

	if err := rg.adjust(ranger); err != nil {
		writeError(err, w, r)
		return
	}

	cr, partial, err := ranger.Range(rg)
	if err != nil {
		writeError(err, w, r)
		return
	}

	addVary(w.Header(), "Range")
	w.Header().Set("Content-Range", cr.String())
	writeResource(partial, w, r)
}

/*
Patcher is implemented by endpoints allowing the PATCH method.

	func (ep *endpoint) Patch(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		if r.Header.Get("Content-Type") != "application/www-form-urlencoded" {
			return nil, rst.UnsupportedMediaType("application/www-form-urlencoded")
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Patcher interface {
	// Returns the patched resource or an error.
	Patch(RouteVars, *http.Request) (Resource, error)
}

// PatchFunc allows a Patcher.Patch method to be used an http.Handler.
type PatchFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PatchFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		return
	}
	writeResource(resource, w, r)
}

/*
Putter is implemented by endpoints allowing the PUT method.

	func (ep *endpoint) Put(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Putter interface {
	// Returns the modified resource or an error.
	Put(RouteVars, *http.Request) (Resource, error)
}

// PutFunc allows a Putter.Put method to be used an http.Handler.
type PutFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PutFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		return
	}
	writeResource(resource, w, r)
}

/*
Poster is implemented by endpoints allowing the POST method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, string, error) {
		resource, err := NewResourceFromRequest(r)
		if err != nil {
			return nil, "", err
		}
		uri := "https://example.com/resource/" + resource.ID
		return resource, uri, nil
	}
*/
type Poster interface {
	// Returns the resource newly created and the URI where it can be located, or
	// an error.
	Post(RouteVars, *http.Request) (resource Resource, location string, err error)
}

// PostFunc allows a Poster.Post method to be used an http.Handler.
type PostFunc func(RouteVars, *http.Request) (Resource, string, error)

// ServeHTTP implements the http.Handler interface.
func (f PostFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, location, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}

	if location != "" {
		// TODO: make sure the URI is a fully qualified URL
		w.Header().Set("Location", location)
	}

	if resource == nil {
		w.WriteHeader(http.StatusCreated)
		return
	}
	writeResource(resource, w, r)
}

// Deleter is implemented by endpoints allowing the DELETE method.
type Deleter interface {
	Delete(RouteVars, *http.Request) error
}

// DeleteFunc allows a Deleter.Deleter method to be used an http.Handler.
type DeleteFunc func(RouteVars, *http.Request) error

// ServeHTTP implements the http.Handler interface.
func (f DeleteFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := f(getVars(r), r); err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// OptionsHandler returns a handler that serves responses to OPTIONS requests
// issued to the resource exposed by the given endpoint.
func optionsHandler(endpoint Endpoint) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != Options {
			return
		}

		w.Header().Set("Allow", strings.Join(AllowedMethods(endpoint), ", "))
		w.Header().Set("Content-Type", strings.Join(alternatives, ";"))
		w.WriteHeader(http.StatusNoContent)
	})
}

// EndpointHandler returns a handler that serves HTTP requests for the resource
// exposed by the given endpoint.
func EndpointHandler(endpoint Endpoint) http.Handler {
	return &endpointHandler{endpoint}
}

type endpointHandler struct {
	endpoint Endpoint
}

func (h *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	methodHandler := getMethodHandler(h.endpoint, r.Method, r.Header)
	if methodHandler == nil {
		if allowed := AllowedMethods(h.endpoint); len(allowed) > 0 {
			methodHandler = MethodNotAllowed(r.Method, allowed)
		} else {
			methodHandler = NotFound()
		}
	}
	methodHandler.ServeHTTP(w, r)
}

// getMethodHandler returns the handler in endpoint for the given of HTTP
// request method and header
func getMethodHandler(endpoint Endpoint, method string, header http.Header) http.Handler {
	switch strings.ToUpper(method) {
	case Options:
		return optionsHandler(endpoint)
	case Head, Get:
		if i, supported := endpoint.(Getter); supported {
			return GetFunc(i.Get)
		}
	case Patch:
		if i, supported := endpoint.(Patcher); supported {
			return PatchFunc(i.Patch)
		}
	case Put:
		if i, supported := endpoint.(Putter); supported {
			return PutFunc(i.Put)
		}
	case Post:
		if i, supported := endpoint.(Poster); supported {
			return PostFunc(i.Post)
		}
	case Delete:
		if i, supported := endpoint.(Deleter); supported {
			return DeleteFunc(i.Delete)
		}
	}
	return nil
}

var supportedMethods = []string{Head, Get, Patch, Put, Post, Delete}

// methodLister is implements by endpoints that need to control the list of
// HTTP methods they support.
type methodLister interface {
	allowedMethods() []string
}

// AllowedMethods returns the list of HTTP methods allowed by this endpoint.
func AllowedMethods(endpoint Endpoint) (methods []string) {
	if lister, ok := endpoint.(methodLister); ok {
		return lister.allowedMethods()
	}

	for _, method := range supportedMethods {
		if getMethodHandler(endpoint, method, nil) != nil {
			methods = append(methods, method)
		}
	}
	return methods
}
<commit_msg>Ensuring that ResponseWriter.Write is called every time.<commit_after>package rst

import (
	"net/http"
	"strings"
	"time"
)

// noContent is used to run ResponseWriter.Write even when there's not data to
// write.
var noContent = []byte{}

/*
Resource represents a resource exposed on a REST service using an Endpoint.

There are other interfaces that can be implemented by a resource to either
control its projection in a response payload, or add support for advanced HTTP
features:

- The Ranger interface adds support for range requests and allows the resource to
return partial responses.

- The Marshaler interface allows you to customize the encoding process of the
resource and control the bytes returned in the payload of the response.

- The http.Handler interface can be used to gain direct access to the
ResponseWriter and Request. This is a low level method that should only be used
when you need to write chunked responses, or if you wish to add specific headers
such a Content-Disposition, etc.
*/
type Resource interface {
	ETag() string            // ETag identifying the current version of the resource.
	LastModified() time.Time // Date and time of the last modification of the resource.
	TTL() time.Duration      // Time to live, or caching duration of the resource.
}

/*
ValidateConditions returns true if the If-Unmodified-Since or the If-Match headers of
r are not matching with the current version of resource.

	func (ep *endpoint) Patch(vars RouteVars, r *http.Request) (Resource, error) {
		resource := db.Lookup(vars.Get("id"))
		if ValidateConditions(resource, r) {
			return nil, Conflict()
		}

		// apply the patch safely from here
	}
*/
func ValidateConditions(resource Resource, r *http.Request) bool {
	if d, err := time.Parse(rfc1123, r.Header.Get("If-Unmodified-Since")); err == nil {
		if d.Sub(resource.LastModified()) < 0 {
			return true
		}
	}
	if etag := r.Header.Get("If-Match"); etag != "" && etag != resource.ETag() {
		return true
	}
	return false
}

/*
Ranger is implemented by resources that support partial responses.

Range will only be called if the request contains a valid Range header.
Otherwise, it will be processed as a normal Get request.

	type Doc []byte
	// assuming Doc implements rst.Resource interface

	// Supported units will be displayed in the Accept-Range header
		func (d *Doc) Units() []string {
		return []string{"bytes"}
	}

	// Count returns the total number of range units available
	func (d *Doc) Count() uint64 {
		return uint64(len(d))
	}

	func (d *Doc) Range(rg *rst.Range) (*rst.ContentRange, rst.Resource, error) {
		cr := &ContentRange{rg, c.Count()}
		part := d[rg.From : rg.To+1]
		return cr, part, nil
	}
*/
type Ranger interface {
	// Supported range units
	Units() []string

	// Total number of units available
	Count() uint64

	// Range is used to return the part of the resource that is indicated by the
	// passed range.
	Range(*Range) (*ContentRange, Resource, error)
}

func writeError(err error, w http.ResponseWriter, r *http.Request) {
	ErrorHandler(err).ServeHTTP(w, r)
}

func writeResource(resource Resource, w http.ResponseWriter, r *http.Request) {
	// Time-based conditional retrieval
	if t, err := time.Parse(rfc1123, r.Header.Get("If-Modified-Since")); err == nil {
		if t.Sub(resource.LastModified()).Seconds() >= 0 {
			w.WriteHeader(http.StatusNotModified)
			w.Write(noContent)
			return
		}
	}

	// ETag-based conditional retrieval
	for _, t := range strings.Split(r.Header.Get("If-None-Match"), ";") {
		if t == resource.ETag() {
			w.WriteHeader(http.StatusNotModified)
			w.Write(noContent)
			return
		}
	}

	// Headers
	addVary(w.Header(), "Accept")
	w.Header().Set("Last-Modified", resource.LastModified().UTC().Format(rfc1123))
	w.Header().Set("ETag", resource.ETag())
	w.Header().Set("Expires", time.Now().Add(resource.TTL()).UTC().Format(rfc1123))

	// If resource implements http.Handler, let it write in the ResponseWriter
	// on its own.
	if handler, implemented := resource.(http.Handler); implemented {
		handler.ServeHTTP(w, r)
		return
	}

	var (
		contentType string
		b           []byte
		err         error
	)
	contentType, b, err = Marshal(resource, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.Header().Set("Content-Type", contentType)

	if compression := getCompressionFormat(b, r); compression != "" {
		w.Header().Set("Content-Encoding", compression)
	}

	if strings.ToUpper(r.Method) == Post {
		w.WriteHeader(http.StatusCreated)
		w.Write(b)
		return
	}

	if len(b) == 0 {
		w.WriteHeader(http.StatusNoContent)
		w.Write(noContent) // Allowing an override of ResponseWriter to work.
		return
	}

	if w.Header().Get("Content-Range") != "" {
		w.WriteHeader(http.StatusPartialContent)
	} else {
		w.WriteHeader(http.StatusOK)
	}

	if strings.ToUpper(r.Method) == Head {
		w.Write(noContent)
		return
	}
	w.Write(b)
}

/*
Endpoint represents an access point exposing a resource in the REST service.
*/
type Endpoint interface{}

/*
Getter is implemented by endpoints allowing the GET and HEAD method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}
		return resource, nil
	}
*/
type Getter interface {
	// Returns the resource or an error. A nil resource pointer will generate
	// a response with status code 204 No Content.
	Get(RouteVars, *http.Request) (Resource, error)
}

// GetFunc allows a Getter.Get method to be used an http.Handler.
type GetFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f GetFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := getVars(r)

	resource, err := f(vars, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	if resource == nil {
		w.WriteHeader(http.StatusNoContent)
		w.Write(noContent)
		return
	}

	// Check if resource implements Ranger
	ranger, implemented := resource.(Ranger)
	if !implemented {
		writeResource(resource, w, r)
		return
	}
	w.Header().Set("Accept-Ranges", strings.Join(ranger.Units(), ", "))

	// Check if request contains a valid Range header, and check whether it's
	// a valid range.
	rg, err := ParseRange(r.Header.Get("Range"))
	if err != nil || rg.validate(ranger) != nil {
		writeResource(resource, w, r)
		return
	}

	// If-Range can either contain an ETag, or a date.
	// If the precondition fails, the Range header is ignored and the full
	// resource is returned.
	if raw := r.Header.Get("If-Range"); raw != "" {
		date, _ := time.Parse(rfc1123, raw)
		if !date.Equal(resource.LastModified()) && raw != resource.ETag() {
			writeResource(resource, w, r)
			return
		}
	}

	if err := rg.adjust(ranger); err != nil {
		writeError(err, w, r)
		return
	}

	cr, partial, err := ranger.Range(rg)
	if err != nil {
		writeError(err, w, r)
		return
	}

	addVary(w.Header(), "Range")
	w.Header().Set("Content-Range", cr.String())
	writeResource(partial, w, r)
}

/*
Patcher is implemented by endpoints allowing the PATCH method.

	func (ep *endpoint) Patch(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		if r.Header.Get("Content-Type") != "application/www-form-urlencoded" {
			return nil, rst.UnsupportedMediaType("application/www-form-urlencoded")
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Patcher interface {
	// Returns the patched resource or an error.
	Patch(RouteVars, *http.Request) (Resource, error)
}

// PatchFunc allows a Patcher.Patch method to be used an http.Handler.
type PatchFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PatchFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		w.Write(noContent)
		return
	}
	writeResource(resource, w, r)
}

/*
Putter is implemented by endpoints allowing the PUT method.

	func (ep *endpoint) Put(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Putter interface {
	// Returns the modified resource or an error.
	Put(RouteVars, *http.Request) (Resource, error)
}

// PutFunc allows a Putter.Put method to be used an http.Handler.
type PutFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PutFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		w.Write(noContent)
		return
	}
	writeResource(resource, w, r)
}

/*
Poster is implemented by endpoints allowing the POST method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, string, error) {
		resource, err := NewResourceFromRequest(r)
		if err != nil {
			return nil, "", err
		}
		uri := "https://example.com/resource/" + resource.ID
		return resource, uri, nil
	}
*/
type Poster interface {
	// Returns the resource newly created and the URI where it can be located, or
	// an error.
	Post(RouteVars, *http.Request) (resource Resource, location string, err error)
}

// PostFunc allows a Poster.Post method to be used an http.Handler.
type PostFunc func(RouteVars, *http.Request) (Resource, string, error)

// ServeHTTP implements the http.Handler interface.
func (f PostFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, location, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}

	if location != "" {
		// TODO: make sure the URI is a fully qualified URL
		w.Header().Set("Location", location)
	}

	if resource == nil {
		w.WriteHeader(http.StatusCreated)
		w.Write(noContent)
		return
	}
	writeResource(resource, w, r)
}

// Deleter is implemented by endpoints allowing the DELETE method.
type Deleter interface {
	Delete(RouteVars, *http.Request) error
}

// DeleteFunc allows a Deleter.Deleter method to be used an http.Handler.
type DeleteFunc func(RouteVars, *http.Request) error

// ServeHTTP implements the http.Handler interface.
func (f DeleteFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := f(getVars(r), r); err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
	w.Write(noContent)
}

// OptionsHandler returns a handler that serves responses to OPTIONS requests
// issued to the resource exposed by the given endpoint.
func optionsHandler(endpoint Endpoint) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer w.Write(noContent)
		if r.Method != Options {
			return
		}

		w.Header().Set("Allow", strings.Join(AllowedMethods(endpoint), ", "))
		w.Header().Set("Content-Type", strings.Join(alternatives, ";"))
		w.WriteHeader(http.StatusNoContent)
	})
}

// EndpointHandler returns a handler that serves HTTP requests for the resource
// exposed by the given endpoint.
func EndpointHandler(endpoint Endpoint) http.Handler {
	return &endpointHandler{endpoint}
}

type endpointHandler struct {
	endpoint Endpoint
}

func (h *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	methodHandler := getMethodHandler(h.endpoint, r.Method, r.Header)
	if methodHandler == nil {
		if allowed := AllowedMethods(h.endpoint); len(allowed) > 0 {
			methodHandler = MethodNotAllowed(r.Method, allowed)
		} else {
			methodHandler = NotFound()
		}
	}
	methodHandler.ServeHTTP(w, r)
}

// getMethodHandler returns the handler in endpoint for the given of HTTP
// request method and header
func getMethodHandler(endpoint Endpoint, method string, header http.Header) http.Handler {
	switch strings.ToUpper(method) {
	case Options:
		return optionsHandler(endpoint)
	case Head, Get:
		if i, supported := endpoint.(Getter); supported {
			return GetFunc(i.Get)
		}
	case Patch:
		if i, supported := endpoint.(Patcher); supported {
			return PatchFunc(i.Patch)
		}
	case Put:
		if i, supported := endpoint.(Putter); supported {
			return PutFunc(i.Put)
		}
	case Post:
		if i, supported := endpoint.(Poster); supported {
			return PostFunc(i.Post)
		}
	case Delete:
		if i, supported := endpoint.(Deleter); supported {
			return DeleteFunc(i.Delete)
		}
	}
	return nil
}

var supportedMethods = []string{Head, Get, Patch, Put, Post, Delete}

// methodLister is implements by endpoints that need to control the list of
// HTTP methods they support.
type methodLister interface {
	allowedMethods() []string
}

// AllowedMethods returns the list of HTTP methods allowed by this endpoint.
func AllowedMethods(endpoint Endpoint) (methods []string) {
	if lister, ok := endpoint.(methodLister); ok {
		return lister.allowedMethods()
	}

	for _, method := range supportedMethods {
		if getMethodHandler(endpoint, method, nil) != nil {
			methods = append(methods, method)
		}
	}
	return methods
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
)

func viewPost(w http.ResponseWriter, r *http.Request) {
	post := &Post{
		slug: r.URL.Path[len("/posts/")-1:],
	}

	err := post.Fetch()
	if err != nil {
		fmt.Fprintf(w, "%+v", "Post not found")
	}

	fmt.Fprintf(w, "%+v", post)
}
<commit_msg>Comments<commit_after>package main

import (
	"fmt"
	"net/http"
)

// Fetches a new post and displays it
func viewPost(w http.ResponseWriter, r *http.Request) {
	post := &Post{
		slug: r.URL.Path[len("/posts/")-1:],
	}

	err := post.Fetch()
	if err != nil {
		fmt.Fprintf(w, "%+v", "Post not found")
	}

	fmt.Fprintf(w, "%+v", post)
}
<|endoftext|>


<commit_before>package rst

import (
	"net/http"
	"strings"
	"time"
)

/*
Resource represents a resource exposed on a REST service using an Endpoint.

There are other interfaces that can be implemented by a resource to either
control its projection in a response payload, or add support for advanced HTTP
features:

- The Ranger interface adds support for range requests and allows the resource to
return partial responses.

- The Marshaler interface allows you to customize the encoding process of the
resource and control the bytes returned in the payload of the response.

- The http.Handler interface can be used to gain direct access to the
ResponseWriter and Request. This is a low level method that should only be used
when you need to write chunked responses, or if you wish to add specific headers
such a Content-Disposition, etc.
*/
type Resource interface {
	ETag() string            // ETag identifying the current version of the resource.
	LastModified() time.Time // Date and time of the last modification of the resource.
	TTL() time.Duration      // Time to live, or caching duration of the resource.
}

/*
ValidateConditions returns true if the If-Unmodified-Since or the If-Match headers of
r are not matching with the current version of resource.

	func (ep *endpoint) Patch(vars RouteVars, r *http.Request) (Resource, error) {
		resource := db.Lookup(vars.Get("id"))
		if ValidateConditions(resource, r) {
			return nil, Conflict()
		}

		// apply the patch safely from here
	}
*/
func ValidateConditions(resource Resource, r *http.Request) bool {
	if d, err := time.Parse(rfc1123, r.Header.Get("If-Unmodified-Since")); err == nil {
		if d.Sub(resource.LastModified()) < 0 {
			return true
		}
	}
	if etag := r.Header.Get("If-Match"); etag != "" && etag != resource.ETag() {
		return true
	}
	return false
}

/*
Ranger is implemented by resources that support partial responses.

Range will only be called if the request contains a valid Range header.
Otherwise, it will be processed as a normal Get request.

	type Doc []byte
	// assuming Doc implements rst.Resource interface

	// Supported units will be displayed in the Accept-Range header
		func (d *Doc) Units() []string {
		return []string{"bytes"}
	}

	// Count returns the total number of range units available
	func (d *Doc) Count() uint64 {
		return uint64(len(d))
	}

	func (d *Doc) Range(rg *rst.Range) (*rst.ContentRange, rst.Resource, error) {
		cr := &ContentRange{rg, c.Count()}
		part := d[rg.From : rg.To+1]
		return cr, part, nil
	}
*/
type Ranger interface {
	// Supported range units
	Units() []string

	// Total number of units available
	Count() uint64

	// Range is used to return the part of the resource that is indicated by the
	// passed range.
	Range(*Range) (*ContentRange, Resource, error)
}

func writeError(err error, w http.ResponseWriter, r *http.Request) {
	ErrorHandler(err).ServeHTTP(w, r)
}

func writeResource(resource Resource, w http.ResponseWriter, r *http.Request) {
	// Time-based conditional retrieval
	if t, err := time.Parse(rfc1123, r.Header.Get("If-Modified-Since")); err == nil {
		if t.Sub(resource.LastModified()).Seconds() >= 0 {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// ETag-based conditional retrieval
	for _, t := range strings.Split(r.Header.Get("If-None-Match"), ";") {
		if t == resource.ETag() {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// Headers
	w.Header().Add("Vary", "Accept")
	w.Header().Set("Last-Modified", resource.LastModified().UTC().Format(rfc1123))
	w.Header().Set("ETag", resource.ETag())
	w.Header().Set("Expires", time.Now().Add(resource.TTL()).UTC().Format(rfc1123))

	// If resource implements http.Handler, let it write in the ResponseWriter
	// on its own.
	if handler, implemented := resource.(http.Handler); implemented {
		handler.ServeHTTP(w, r)
		return
	}

	var (
		contentType string
		b           []byte
		err         error
	)
	contentType, b, err = Marshal(resource, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.Header().Set("Content-Type", contentType)

	if compression := getCompressionFormat(b, r); compression != "" {
		w.Header().Set("Content-Encoding", compression)
		w.Header().Add("Vary", "Accept-Encoding")
	}

	if strings.ToUpper(r.Method) == Post {
		w.WriteHeader(http.StatusCreated)
		w.Write(b)
		return
	}

	if len(b) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	if w.Header().Get("Content-Range") != "" {
		w.WriteHeader(http.StatusPartialContent)
	} else {
		w.WriteHeader(http.StatusOK)
	}

	if strings.ToUpper(r.Method) == Head {
		return
	}
	w.Write(b)
}

/*
Endpoint represents an access point exposing a resource in the REST service.
*/
type Endpoint interface{}

/*
Getter is implemented by endpoints allowing the GET and HEAD method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}
		return resource, nil
	}
*/
type Getter interface {
	// Returns the resource or an error. A nil resource pointer will generate
	// a response with status code 204 No Content.
	Get(RouteVars, *http.Request) (Resource, error)
}

// GetFunc allows a Getter.Get method to be used an http.Handler.
type GetFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f GetFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := getVars(r)

	resource, err := f(vars, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	if resource == nil {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	// Check if resource implements Ranger
	ranger, implemented := resource.(Ranger)
	if !implemented {
		writeResource(resource, w, r)
		return
	}
	w.Header().Set("Accept-Ranges", strings.Join(ranger.Units(), ", "))

	// Check if request contains a valid Range header, and check whether it's
	// a valid range.
	rg, err := ParseRange(r.Header.Get("Range"))
	if err != nil || rg.validate(ranger) != nil {
		writeResource(resource, w, r)
		return
	}

	// If-Range can either contain an ETag, or a date.
	// If the precondition fails, the Range header is ignored and the full
	// resource is returned.
	if raw := r.Header.Get("If-Range"); raw != "" {
		date, _ := time.Parse(rfc1123, raw)
		if !date.Equal(resource.LastModified()) && raw != resource.ETag() {
			writeResource(resource, w, r)
			return
		}
	}

	if err := rg.adjust(ranger); err != nil {
		writeError(err, w, r)
		return
	}

	cr, partial, err := ranger.Range(rg)
	if err != nil {
		writeError(err, w, r)
		return
	}

	w.Header().Add("Vary", "Range")
	w.Header().Set("Content-Range", cr.String())
	writeResource(partial, w, r)
}

/*
Patcher is implemented by endpoints allowing the PATCH method.

	func (ep *endpoint) Patch(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		if r.Header.Get("Content-Type") != "application/www-form-urlencoded" {
			return nil, rst.UnsupportedMediaType("application/www-form-urlencoded")
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Patcher interface {
	// Returns the patched resource or an error.
	Patch(RouteVars, *http.Request) (Resource, error)
}

// PatchFunc allows a Patcher.Patch method to be used an http.Handler.
type PatchFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PatchFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		return
	}
	writeResource(resource, w, r)
}

/*
Putter is implemented by endpoints allowing the PUT method.

	func (ep *endpoint) Put(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Putter interface {
	// Returns the modified resource or an error.
	Put(RouteVars, *http.Request) (Resource, error)
}

// PutFunc allows a Putter.Put method to be used an http.Handler.
type PutFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PutFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		return
	}
	writeResource(resource, w, r)
}

/*
Poster is implemented by endpoints allowing the POST method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, string, error) {
		resource, err := NewResourceFromRequest(r)
		if err != nil {
			return nil, "", err
		}
		uri := "https://example.com/resource/" + resource.ID
		return resource, uri, nil
	}
*/
type Poster interface {
	// Returns the resource newly created and the URI where it can be located, or
	// an error.
	Post(RouteVars, *http.Request) (resource Resource, location string, err error)
}

// PostFunc allows a Poster.Post method to be used an http.Handler.
type PostFunc func(RouteVars, *http.Request) (Resource, string, error)

// ServeHTTP implements the http.Handler interface.
func (f PostFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, location, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}

	if location != "" {
		// TODO: make sure the URI is a fully qualified URL
		w.Header().Add("Location", location)
	}

	if resource == nil {
		w.WriteHeader(http.StatusCreated)
		return
	}
	writeResource(resource, w, r)
}

// Deleter is implemented by endpoints allowing the DELETE method.
type Deleter interface {
	Delete(RouteVars, *http.Request) error
}

// DeleteFunc allows a Deleter.Deleter method to be used an http.Handler.
type DeleteFunc func(RouteVars, *http.Request) error

// ServeHTTP implements the http.Handler interface.
func (f DeleteFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := f(getVars(r), r); err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// OptionsHandler returns a handler that serves responses to OPTIONS requests
// issued to the resource exposed by the given endpoint.
func optionsHandler(endpoint Endpoint) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != Options {
			return
		}

		w.Header().Set("Allow", strings.Join(AllowedMethods(endpoint), ", "))
		w.Header().Set("Content-Type", strings.Join(alternatives, ";"))
		w.WriteHeader(http.StatusNoContent)
	})
}

// EndpointHandler returns a handler that serves HTTP requests for the resource
// exposed by the given endpoint.
func EndpointHandler(endpoint Endpoint) http.Handler {
	return &endpointHandler{endpoint}
}

type endpointHandler struct {
	endpoint Endpoint
}

func (h *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	methodHandler := getMethodHandler(h.endpoint, r.Method, r.Header)
	if methodHandler == nil {
		if allowed := AllowedMethods(h.endpoint); len(allowed) > 0 {
			methodHandler = MethodNotAllowed(r.Method, allowed)
		} else {
			methodHandler = NotFound()
		}
	}
	methodHandler.ServeHTTP(w, r)
}

// getMethodHandler returns the handler in endpoint for the given of HTTP
// request method and header
func getMethodHandler(endpoint Endpoint, method string, header http.Header) http.Handler {
	switch strings.ToUpper(method) {
	case Options:
		return optionsHandler(endpoint)
	case Head, Get:
		if i, supported := endpoint.(Getter); supported {
			return GetFunc(i.Get)
		}
	case Patch:
		if i, supported := endpoint.(Patcher); supported {
			return PatchFunc(i.Patch)
		}
	case Put:
		if i, supported := endpoint.(Putter); supported {
			return PutFunc(i.Put)
		}
	case Post:
		if i, supported := endpoint.(Poster); supported {
			return PostFunc(i.Post)
		}
	case Delete:
		if i, supported := endpoint.(Deleter); supported {
			return DeleteFunc(i.Delete)
		}
	}
	return nil
}

var supportedMethods = []string{Head, Get, Patch, Put, Post, Delete}

// methodLister is implements by endpoints that need to control the list of
// HTTP methods they support.
type methodLister interface {
	allowedMethods() []string
}

// AllowedMethods returns the list of HTTP methods allowed by this endpoint.
func AllowedMethods(endpoint Endpoint) (methods []string) {
	if lister, ok := endpoint.(methodLister); ok {
		return lister.allowedMethods()
	}

	for _, method := range supportedMethods {
		if getMethodHandler(endpoint, method, nil) != nil {
			methods = append(methods, method)
		}
	}
	return methods
}
<commit_msg>Fixed a bug that would have allowed "Location" header to be duplicated.<commit_after>package rst

import (
	"net/http"
	"strings"
	"time"
)

/*
Resource represents a resource exposed on a REST service using an Endpoint.

There are other interfaces that can be implemented by a resource to either
control its projection in a response payload, or add support for advanced HTTP
features:

- The Ranger interface adds support for range requests and allows the resource to
return partial responses.

- The Marshaler interface allows you to customize the encoding process of the
resource and control the bytes returned in the payload of the response.

- The http.Handler interface can be used to gain direct access to the
ResponseWriter and Request. This is a low level method that should only be used
when you need to write chunked responses, or if you wish to add specific headers
such a Content-Disposition, etc.
*/
type Resource interface {
	ETag() string            // ETag identifying the current version of the resource.
	LastModified() time.Time // Date and time of the last modification of the resource.
	TTL() time.Duration      // Time to live, or caching duration of the resource.
}

/*
ValidateConditions returns true if the If-Unmodified-Since or the If-Match headers of
r are not matching with the current version of resource.

	func (ep *endpoint) Patch(vars RouteVars, r *http.Request) (Resource, error) {
		resource := db.Lookup(vars.Get("id"))
		if ValidateConditions(resource, r) {
			return nil, Conflict()
		}

		// apply the patch safely from here
	}
*/
func ValidateConditions(resource Resource, r *http.Request) bool {
	if d, err := time.Parse(rfc1123, r.Header.Get("If-Unmodified-Since")); err == nil {
		if d.Sub(resource.LastModified()) < 0 {
			return true
		}
	}
	if etag := r.Header.Get("If-Match"); etag != "" && etag != resource.ETag() {
		return true
	}
	return false
}

/*
Ranger is implemented by resources that support partial responses.

Range will only be called if the request contains a valid Range header.
Otherwise, it will be processed as a normal Get request.

	type Doc []byte
	// assuming Doc implements rst.Resource interface

	// Supported units will be displayed in the Accept-Range header
		func (d *Doc) Units() []string {
		return []string{"bytes"}
	}

	// Count returns the total number of range units available
	func (d *Doc) Count() uint64 {
		return uint64(len(d))
	}

	func (d *Doc) Range(rg *rst.Range) (*rst.ContentRange, rst.Resource, error) {
		cr := &ContentRange{rg, c.Count()}
		part := d[rg.From : rg.To+1]
		return cr, part, nil
	}
*/
type Ranger interface {
	// Supported range units
	Units() []string

	// Total number of units available
	Count() uint64

	// Range is used to return the part of the resource that is indicated by the
	// passed range.
	Range(*Range) (*ContentRange, Resource, error)
}

func writeError(err error, w http.ResponseWriter, r *http.Request) {
	ErrorHandler(err).ServeHTTP(w, r)
}

func writeResource(resource Resource, w http.ResponseWriter, r *http.Request) {
	// Time-based conditional retrieval
	if t, err := time.Parse(rfc1123, r.Header.Get("If-Modified-Since")); err == nil {
		if t.Sub(resource.LastModified()).Seconds() >= 0 {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// ETag-based conditional retrieval
	for _, t := range strings.Split(r.Header.Get("If-None-Match"), ";") {
		if t == resource.ETag() {
			w.WriteHeader(http.StatusNotModified)
			return
		}
	}

	// Headers
	w.Header().Add("Vary", "Accept")
	w.Header().Set("Last-Modified", resource.LastModified().UTC().Format(rfc1123))
	w.Header().Set("ETag", resource.ETag())
	w.Header().Set("Expires", time.Now().Add(resource.TTL()).UTC().Format(rfc1123))

	// If resource implements http.Handler, let it write in the ResponseWriter
	// on its own.
	if handler, implemented := resource.(http.Handler); implemented {
		handler.ServeHTTP(w, r)
		return
	}

	var (
		contentType string
		b           []byte
		err         error
	)
	contentType, b, err = Marshal(resource, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.Header().Set("Content-Type", contentType)

	if compression := getCompressionFormat(b, r); compression != "" {
		w.Header().Set("Content-Encoding", compression)
		w.Header().Add("Vary", "Accept-Encoding")
	}

	if strings.ToUpper(r.Method) == Post {
		w.WriteHeader(http.StatusCreated)
		w.Write(b)
		return
	}

	if len(b) == 0 {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	if w.Header().Get("Content-Range") != "" {
		w.WriteHeader(http.StatusPartialContent)
	} else {
		w.WriteHeader(http.StatusOK)
	}

	if strings.ToUpper(r.Method) == Head {
		return
	}
	w.Write(b)
}

/*
Endpoint represents an access point exposing a resource in the REST service.
*/
type Endpoint interface{}

/*
Getter is implemented by endpoints allowing the GET and HEAD method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}
		return resource, nil
	}
*/
type Getter interface {
	// Returns the resource or an error. A nil resource pointer will generate
	// a response with status code 204 No Content.
	Get(RouteVars, *http.Request) (Resource, error)
}

// GetFunc allows a Getter.Get method to be used an http.Handler.
type GetFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f GetFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := getVars(r)

	resource, err := f(vars, r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	if resource == nil {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	// Check if resource implements Ranger
	ranger, implemented := resource.(Ranger)
	if !implemented {
		writeResource(resource, w, r)
		return
	}
	w.Header().Set("Accept-Ranges", strings.Join(ranger.Units(), ", "))

	// Check if request contains a valid Range header, and check whether it's
	// a valid range.
	rg, err := ParseRange(r.Header.Get("Range"))
	if err != nil || rg.validate(ranger) != nil {
		writeResource(resource, w, r)
		return
	}

	// If-Range can either contain an ETag, or a date.
	// If the precondition fails, the Range header is ignored and the full
	// resource is returned.
	if raw := r.Header.Get("If-Range"); raw != "" {
		date, _ := time.Parse(rfc1123, raw)
		if !date.Equal(resource.LastModified()) && raw != resource.ETag() {
			writeResource(resource, w, r)
			return
		}
	}

	if err := rg.adjust(ranger); err != nil {
		writeError(err, w, r)
		return
	}

	cr, partial, err := ranger.Range(rg)
	if err != nil {
		writeError(err, w, r)
		return
	}

	w.Header().Add("Vary", "Range")
	w.Header().Set("Content-Range", cr.String())
	writeResource(partial, w, r)
}

/*
Patcher is implemented by endpoints allowing the PATCH method.

	func (ep *endpoint) Patch(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		if r.Header.Get("Content-Type") != "application/www-form-urlencoded" {
			return nil, rst.UnsupportedMediaType("application/www-form-urlencoded")
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Patcher interface {
	// Returns the patched resource or an error.
	Patch(RouteVars, *http.Request) (Resource, error)
}

// PatchFunc allows a Patcher.Patch method to be used an http.Handler.
type PatchFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PatchFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		return
	}
	writeResource(resource, w, r)
}

/*
Putter is implemented by endpoints allowing the PUT method.

	func (ep *endpoint) Put(vars rst.RouteVars, r *http.Request) (rst.Resource, error) {
		resource := database.Find(vars.Get("id"))
		if resource == nil {
			return nil, rst.NotFound()
		}

		// Detect any writing conflicts
		if rst.ValidateConditions(resource, r) {
			return nil, rst.PreconditionFailed()
		}

		// Read r.Body, apply changes to resource, then return it
		return resource, nil
	}
*/
type Putter interface {
	// Returns the modified resource or an error.
	Put(RouteVars, *http.Request) (Resource, error)
}

// PutFunc allows a Putter.Put method to be used an http.Handler.
type PutFunc func(RouteVars, *http.Request) (Resource, error)

// ServeHTTP implements the http.Handler interface.
func (f PutFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusOK)
	if resource == nil {
		return
	}
	writeResource(resource, w, r)
}

/*
Poster is implemented by endpoints allowing the POST method.

	func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, string, error) {
		resource, err := NewResourceFromRequest(r)
		if err != nil {
			return nil, "", err
		}
		uri := "https://example.com/resource/" + resource.ID
		return resource, uri, nil
	}
*/
type Poster interface {
	// Returns the resource newly created and the URI where it can be located, or
	// an error.
	Post(RouteVars, *http.Request) (resource Resource, location string, err error)
}

// PostFunc allows a Poster.Post method to be used an http.Handler.
type PostFunc func(RouteVars, *http.Request) (Resource, string, error)

// ServeHTTP implements the http.Handler interface.
func (f PostFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	resource, location, err := f(getVars(r), r)
	if err != nil {
		writeError(err, w, r)
		return
	}

	if location != "" {
		// TODO: make sure the URI is a fully qualified URL
		w.Header().Set("Location", location)
	}

	if resource == nil {
		w.WriteHeader(http.StatusCreated)
		return
	}
	writeResource(resource, w, r)
}

// Deleter is implemented by endpoints allowing the DELETE method.
type Deleter interface {
	Delete(RouteVars, *http.Request) error
}

// DeleteFunc allows a Deleter.Deleter method to be used an http.Handler.
type DeleteFunc func(RouteVars, *http.Request) error

// ServeHTTP implements the http.Handler interface.
func (f DeleteFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := f(getVars(r), r); err != nil {
		writeError(err, w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// OptionsHandler returns a handler that serves responses to OPTIONS requests
// issued to the resource exposed by the given endpoint.
func optionsHandler(endpoint Endpoint) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != Options {
			return
		}

		w.Header().Set("Allow", strings.Join(AllowedMethods(endpoint), ", "))
		w.Header().Set("Content-Type", strings.Join(alternatives, ";"))
		w.WriteHeader(http.StatusNoContent)
	})
}

// EndpointHandler returns a handler that serves HTTP requests for the resource
// exposed by the given endpoint.
func EndpointHandler(endpoint Endpoint) http.Handler {
	return &endpointHandler{endpoint}
}

type endpointHandler struct {
	endpoint Endpoint
}

func (h *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	methodHandler := getMethodHandler(h.endpoint, r.Method, r.Header)
	if methodHandler == nil {
		if allowed := AllowedMethods(h.endpoint); len(allowed) > 0 {
			methodHandler = MethodNotAllowed(r.Method, allowed)
		} else {
			methodHandler = NotFound()
		}
	}
	methodHandler.ServeHTTP(w, r)
}

// getMethodHandler returns the handler in endpoint for the given of HTTP
// request method and header
func getMethodHandler(endpoint Endpoint, method string, header http.Header) http.Handler {
	switch strings.ToUpper(method) {
	case Options:
		return optionsHandler(endpoint)
	case Head, Get:
		if i, supported := endpoint.(Getter); supported {
			return GetFunc(i.Get)
		}
	case Patch:
		if i, supported := endpoint.(Patcher); supported {
			return PatchFunc(i.Patch)
		}
	case Put:
		if i, supported := endpoint.(Putter); supported {
			return PutFunc(i.Put)
		}
	case Post:
		if i, supported := endpoint.(Poster); supported {
			return PostFunc(i.Post)
		}
	case Delete:
		if i, supported := endpoint.(Deleter); supported {
			return DeleteFunc(i.Delete)
		}
	}
	return nil
}

var supportedMethods = []string{Head, Get, Patch, Put, Post, Delete}

// methodLister is implements by endpoints that need to control the list of
// HTTP methods they support.
type methodLister interface {
	allowedMethods() []string
}

// AllowedMethods returns the list of HTTP methods allowed by this endpoint.
func AllowedMethods(endpoint Endpoint) (methods []string) {
	if lister, ok := endpoint.(methodLister); ok {
		return lister.allowedMethods()
	}

	for _, method := range supportedMethods {
		if getMethodHandler(endpoint, method, nil) != nil {
			methods = append(methods, method)
		}
	}
	return methods
}
<|endoftext|>