summaryrefslogtreecommitdiff
path: root/caddyconfig/configadapters.go
blob: 0ca3c3af13f63e20af45e613247bc87270c20bfe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package caddyconfig

import (
	"encoding/json"
	"fmt"

	"github.com/caddyserver/caddy/v2"
)

// Adapter is a type which can adapt a configuration to Caddy JSON.
// It returns the results and any warnings, or an error.
type Adapter interface {
	Adapt(body []byte, options map[string]any) ([]byte, []Warning, error)
}

// Warning represents a warning or notice related to conversion.
type Warning struct {
	File      string `json:"file,omitempty"`
	Line      int    `json:"line,omitempty"`
	Directive string `json:"directive,omitempty"`
	Message   string `json:"message,omitempty"`
}

func (w Warning) String() string {
	var directive string
	if w.Directive != "" {
		directive = fmt.Sprintf(" (%s)", w.Directive)
	}
	return fmt.Sprintf("%s:%d%s: %s", w.File, w.Line, directive, w.Message)
}

// JSON encodes val as JSON, returning it as a json.RawMessage. Any
// marshaling errors (which are highly unlikely with correct code)
// are converted to warnings. This is convenient when filling config
// structs that require a json.RawMessage, without having to worry
// about errors.
func JSON(val any, warnings *[]Warning) json.RawMessage {
	b, err := json.Marshal(val)
	if err != nil {
		if warnings != nil {
			*warnings = append(*warnings, Warning{Message: err.Error()})
		}
		return nil
	}
	return b
}

// JSONModuleObject is like JSON(), except it marshals val into a JSON object
// with an added key named fieldName with the value fieldVal. This is useful
// for encoding module values where the module name has to be described within
// the object by a certain key; for example, `"handler": "file_server"` for a
// file server HTTP handler (fieldName="handler" and fieldVal="file_server").
// The val parameter must encode into a map[string]any (i.e. it must be
// a struct or map). Any errors are converted into warnings.
func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
	// encode to a JSON object first
	enc, err := json.Marshal(val)
	if err != nil {
		if warnings != nil {
			*warnings = append(*warnings, Warning{Message: err.Error()})
		}
		return nil
	}

	// then decode the object
	var tmp map[string]any
	err = json.Unmarshal(enc, &tmp)
	if err != nil {
		if warnings != nil {
			*warnings = append(*warnings, Warning{Message: err.Error()})
		}
		return nil
	}

	// so we can easily add the module's field with its appointed value
	tmp[fieldName] = fieldVal

	// then re-marshal as JSON
	result, err := json.Marshal(tmp)
	if err != nil {
		if warnings != nil {
			*warnings = append(*warnings, Warning{Message: err.Error()})
		}
		return nil
	}

	return result
}

// RegisterAdapter registers a config adapter with the given name.
// This should usually be done at init-time. It panics if the
// adapter cannot be registered successfully.
func RegisterAdapter(name string, adapter Adapter) {
	if _, ok := configAdapters[name]; ok {
		panic(fmt.Errorf("%s: already registered", name))
	}
	configAdapters[name] = adapter
	caddy.RegisterModule(adapterModule{name, adapter})
}

// GetAdapter returns the adapter with the given name,
// or nil if one with that name is not registered.
func GetAdapter(name string) Adapter {
	return configAdapters[name]
}

// adapterModule is a wrapper type that can turn any config
// adapter into a Caddy module, which has the benefit of being
// counted with other modules, even though they do not
// technically extend the Caddy configuration structure.
// See caddyserver/caddy#3132.
type adapterModule struct {
	name string
	Adapter
}

func (am adapterModule) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  caddy.ModuleID("caddy.adapters." + am.name),
		New: func() caddy.Module { return am },
	}
}

var configAdapters = make(map[string]Adapter)