view pkg/models/common.go @ 5736:55892008ec96 default tip

Fixed a bunch of corner cases in WG import.
author Sascha Wilde <wilde@sha-bang.de>
date Wed, 29 May 2024 19:02:42 +0200
parents 6270951dda28
children
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package models

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"time"

	"gemma.intevation.de/gemma/pkg/common"
)

var (
	errNoString    = errors.New("not a string")
	errNoByteSlice = errors.New("not a byte slice")
)

// WGS84 is the EPSG of the World Geodetic System 1984.
const WGS84 = 4326

type (
	// Date is the common on-wire date represention.
	Date struct{ time.Time }
	// Time is the common on-wire time represention.
	Time struct{ time.Time }

	// Country is a valid country 2 letter code.
	Country string
	// UniqueCountries is a list of unique countries.
	UniqueCountries []Country

	// SafePath should only contain chars that directory traversal safe.
	SafePath string
)

// MarshalJSON implements the json.Marshaler interface.
func (d Date) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.Format(common.DateFormat))
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (d *Date) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	d2, err := time.Parse(common.DateFormat, s)
	if err == nil {
		*d = Date{d2}
	}
	return err
}

// MarshalJSON implements the json.Marshaler interface.
func (t Time) MarshalJSON() ([]byte, error) {
	return json.Marshal(t.Format(common.TimeFormat))
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *Time) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	t2, err := time.Parse(common.TimeFormat, s)
	if err == nil {
		*t = Time{t2}
	}
	return err
}

var (
	validCountries = []string{
		"AT", "BG", "DE", "HU", "HR",
		"MD", "RO", "RS", "SK", "UA",
	}
)

// Valid checks if the given country is a known one.
func (c Country) Valid() bool {
	for _, v := range validCountries {
		if string(c) == v {
			return true
		}
	}
	return false
}

// UnmarshalJSON ensures that the given string forms a valid
// two letter country code.
func (c *Country) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	u := strings.ToUpper(s)
	for _, v := range validCountries {
		if v == u {
			*c = Country(v)
			return nil
		}
	}
	return fmt.Errorf("'%s' is not a valid country", s)
}

// Value implements the driver.Valuer interface.
func (c Country) Value() (driver.Value, error) {
	return string(c), nil
}

// Scan implements the sql.Scanner interfaces.
func (c *Country) Scan(src any) (err error) {
	if s, ok := src.(string); ok {
		*c = Country(s)
	} else {
		err = errNoString
	}
	return
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (uc *UniqueCountries) UnmarshalJSON(data []byte) error {
	var countries []Country
	if err := json.Unmarshal(data, &countries); err != nil {
		return err
	}
	unique := map[Country]struct{}{}
	for _, c := range countries {
		if _, found := unique[c]; found {
			return fmt.Errorf("country '%s' is not unique", string(c))
		}
		unique[c] = struct{}{}
	}
	*uc = countries
	return nil
}

func (uc UniqueCountries) String() string {
	var b strings.Builder
	for i, c := range uc {
		if i > 0 {
			b.WriteString(", ")
		}
		b.WriteString(string(c))
	}
	return b.String()
}

// SafePathExp is used to check if a path consists only
// of harmless runes.
const SafePathExp = "[a-zA-Z0-9_-]+"

var safePathRegExp = regexp.MustCompile("^" + SafePathExp + "$")

// Valid checks is the given path is safe.
func (sp SafePath) Valid() bool {
	return safePathRegExp.MatchString(string(sp))
}

// UnmarshalJSON ensures that the given string only consist
// of runes that are directory traversal safe.
func (sp *SafePath) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	if c := SafePath(s); c.Valid() {
		*sp = c
		return nil
	}
	return fmt.Errorf("'%s' is not a safe path", s)
}