I have a custom built JSON schema that only has a few more top-levels. The problem here is that it doesn't validate everything to 100%. For example, it only detects 2 out of 4 fields, and the required fields do not work at all, neither does additionalproperties, etc. I'm using this library for my json schema.
{
"users": {
"PUT": {
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"title": "The Root Schema",
"required": [
"DisplayName",
"Username",
"Email",
"Password"
],
"properties": {
"DisplayName": {
"$id": "#/properties/DisplayName",
"type": "string",
"title": "The Displayname Schema",
"default": "",
"examples": [
""
],
"minLength": 3,
"maxLength": 24,
"pattern": "^(.*)$"
},
"Username": {
"$id": "#/properties/Username",
"type": "string",
"title": "The Username Schema",
"default": "",
"examples": [
""
],
"minLength": 3,
"maxLength": 15,
"pattern": "^(.*)$"
},
"Email": {
"$id": "#/properties/Email",
"type": "string",
"title": "The Email Schema",
"default": "",
"examples": [
""
],
"minLength": 7,
"pattern": "^(.*)$",
"format": "email"
},
"Password": {
"$id": "#/properties/Password",
"type": "string",
"title": "The Password Schema",
"default": "",
"examples": [
""
],
"pattern": "^(.*)$"
}
},
"additionalProperties": false
}
}
}
I'm parsing everything like this:
func Validate(data interface{}, r *http.Request) (interface{}, error) {
// Convert the data struct to a readable JSON bytes
JSONparams, err := json.Marshal(data)
if err != nil {
return nil, err
}
// Split URL segments so we know what part of the API they are accessing
modules := strings.Split(r.URL.String(), "/")
modules = modules[(len(modules) - 1):]
// Read the schema file
fileSchema, _ := ioutil.ReadFile("config/schema/schema.json")
var object interface{}
// Unmarshal it so we can choose what schema we specifically want
err = json.Unmarshal(fileSchema, &object)
if err != nil {
log.Fatal(err)
}
// Choose the preferred schema
encodedJSON, err := json.Marshal(object.(map[string]interface{})[strings.Join(modules, "") + "s"].(map[string]interface{})[r.Method])
if err != nil {
log.Fatal(err)
}
// Load the JSON schema
schema := gojsonschema.NewStringLoader(string(encodedJSON))
// Load the JSON params
document := gojsonschema.NewStringLoader(string(JSONparams))
// Validate the document
result, err := gojsonschema.Validate(schema, document)
if err != nil {
return nil, err
}
if !result.Valid() {
// Map the errors into a new array
var errors = make(map[string]string)
for _, err := range result.Errors() {
errors[err.Field()] = err.Description()
}
// Convert the array to an interface that we can convert to JSON
resultMap := map[string]interface{}{
"success": false,
"result": map[string]interface{}{},
"errors": errors,
}
// Convert the interface to a JSON object
errorObject, err := json.Marshal(resultMap)
if err != nil {
return nil, err
}
return errorObject, nil
}
return nil, nil
}
type CreateParams struct {
DisplayName string
Username string
Email string
Password string
}
var (
response interface{}
status int = 0
)
func Create(w http.ResponseWriter, r *http.Request) {
status = 0
// Parse the request so we can access the query parameters
r.ParseForm()
// Assign them to the interface variables
data := &CreateParams{
DisplayName: r.Form.Get("DisplayName"),
Username: r.Form.Get("Username"),
Email: r.Form.Get("Email"),
Password: r.Form.Get("Password"),
}
// Validate the JSON data
errors, err := schema.Validate(data, r)
if err != nil {
responseJSON := map[string]interface{}{
"success": false,
"result": map[string]interface{}{},
}
log.Fatal(err.Error())
response, err = json.Marshal(responseJSON)
status = http.StatusInternalServerError
}
// Catch any errors generated by the validator and assign them to the response interface
if errors != nil {
response = errors
status = http.StatusBadRequest
}
// Status has not been set yet, so it's safe to assume that everything went fine
if status == 0 {
responseJSON := map[string]interface{}{
"success": true,
"result": map[string]interface{} {
"DisplayName": data.DisplayName,
"Username": data.Username,
"Email": data.Email,
"Password": nil,
},
}
response, err = json.Marshal(responseJSON)
status = http.StatusOK
}
// We are going to respond with JSON, so set the appropriate header
w.Header().Set("Content-Type", "application/json")
// Write the header and the response
w.WriteHeader(status)
w.Write(response.([]byte))
}
The reason to why I'm doing it like this is I'm building a REST API and if api/auth/user
gets a PUT request, I want to be able to specify the data requirements for specifically the "users" parts with the PUT method.
Any idea how this can be achieved?
EDIT: My json data:
{
"DisplayName": "1234",
"Username": "1234",
"Email": "test@gmail.com",
"Password": "123456"
}
EDIT 2: This data should fail with the schema.
{
"DisplayName": "1", // min length is 3
"Username": "", // this field is required but is empty here
"Email": "testgmail.com", // not following the email format
"Password": "123456111111111111111111111111111111111111111111111" // too long
}