Skip to content

Instantly share code, notes, and snippets.

@owulveryck
Last active October 17, 2022 19:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save owulveryck/8af03b6711c84f6672efc3e8b979a536 to your computer and use it in GitHub Desktop.
Save owulveryck/8af03b6711c84f6672efc3e8b979a536 to your computer and use it in GitHub Desktop.
Simple webservice to validate data with CUE
package sample
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
)
// DataProduct is a structure that holds the definition as a CUE value
type DataProduct struct {
definition cue.Value
// ...
}
// ServeHTTP to make the *DataProduct a http handler
// This is an example, we do not handle the method properly nor we check the content type
// This methods reads the payload from the request an calls the ExtractData method for validation
func (d *DataProduct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
defer r.Body.Close()
_, err := d.ExtractData(b)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprint(w, "ok")
}
// ExtractData tries to reconstruct a data from the payload b, and
// unifies it with the data definition.
// then it validates the resulting value
func (d *DataProduct) ExtractData(b []byte) (cue.Value, error) {
data := d.definition.Context().CompileBytes(b)
unified := d.definition.Unify(data)
opts := []cue.Option{
cue.Attributes(true),
cue.Definitions(true),
cue.Hidden(true),
}
return data, unified.Validate(opts...)
}
// TestHandler is triggering a http test server with the DataProduct's handler,
// send a post request with various payload and check the result
func TestHandler(t *testing.T) {
ctx := cuecontext.New()
val := ctx.CompileString(`
{
// first name of the person
first: =~ "[A-Z].*"
// Last name of the person
Last: =~ "[A-Z].*"
// Age of the person
Age?: number & < 130
}
`)
ts := httptest.NewServer(&DataProduct{
definition: val,
})
defer ts.Close()
tests := []struct {
name string
payload io.Reader
expectedStatus int
}{
{
name: "valid",
payload: bytes.NewBufferString(`
{
"first": "John",
"Last": "Doe",
"Age": 40
}
`),
expectedStatus: http.StatusOK,
},
{
name: "invalid",
payload: bytes.NewBufferString(`
{
"first": "John",
"Last": "Doe",
"Age": 140
}
`),
expectedStatus: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("POST", ts.URL, tt.payload)
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Error(err)
}
if res.StatusCode != tt.expectedStatus {
t.Errorf("expected %v, got %v", tt.expectedStatus, res.StatusCode)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment