├── .github └── workflows │ └── go.yaml ├── .gitignore ├── LICENSE ├── README.md ├── exception.go ├── exception_test.go ├── go.mod └── gopher.png /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: All Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.16 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -race -coverprofile=coverage.xml -covermode=atomic -v ./... 26 | #- uses: actions/checkout@master 27 | - uses: codecov/codecov-action@v1 28 | with: 29 | files: ./coverage.xml 30 | flags: unittests # optional 31 | name: codecov-umbrella # optional 32 | fail_ci_if_error: true # optional (default = false) 33 | verbose: true # optional (default = false) 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .DS_Store 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2022 Rahul Baruri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Exception](gopher.png) 2 | 3 | ![Go test workflow](https://github.com/rbrahul/exception/actions/workflows/go.yaml/badge.svg) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/rbrahul/exception)](https://goreportcard.com/report/github.com/rbrahul/exception) 5 | [![codecov](https://codecov.io/gh/rbrahul/exception/branch/main/graph/badge.svg?token=CW54A6HWS6)](https://codecov.io/gh/rbrahul/exception) 6 | [![Go Reference](https://pkg.go.dev/badge/github.com/rbrahul/exception.svg)](https://pkg.go.dev/github.com/rbrahul/exception) 7 | 8 | ## Go Try Catch Exception Handler 9 | By design, Go doesn't offer any mechanism for Exception handling. But Programmers from different backgrounds like Java, C++, Php can be sceptical about the decision. Exception handling with *Try Catch Finally* is well adapted in all the modern languages. To ease the pain, this library offers utility functions for Exception Handling, which will help programmers to write Go code with *Try-Catch-Finally* approach. 10 | 11 | ### This is how you can throw Exception and handle within Catch: 12 | 13 | ```go 14 | import( 15 | e "github.com/rbrahul/exception" 16 | ) 17 | 18 | ... 19 | e.Try(func() { 20 | data := getValue() // get me the value from Allions 21 | if data != 100 { 22 | e.Throw(e.AssertionError("Expected value is not same as 100")) 23 | } 24 | }) 25 | .Catch(e.In(e.AssertionErrorType, e.ValueErrorType), func(excep *Exception) { 26 | fmt.Println("Message:",excep.Message) 27 | fmt.Println("Exception Type:",excep.Type) 28 | fmt.Println("Here is the Stack Trace:",excep.StackTrace) 29 | }) 30 | .Catch(nil, func(excep *Exception) { 31 | fmt.Println("I'll be executed as fallback:",excep.Message) 32 | }) 33 | .Finally(func() { 34 | fmt.Println("I have been executing always to clean the world!") 35 | }) 36 | .Run() 37 | ... 38 | ``` 39 | 40 | ### Throwing a custom exception 41 | 42 | You have to define a exception variable with ExceptionType. 43 | 44 | ```go 45 | const SomethingWentWrongError e.ExceptionType = "SomethingWentWrongError" 46 | ``` 47 | 48 | Now you have to initialize and throw your exception via e.New constructor. You can pass a proper error message as optional argument. 49 | 50 | ```go 51 | e.Try(func() { 52 | e.Throw(e.New(SomethingWentWrongError, "Something went worng!")) 53 | }) 54 | .Catch(e.In(SomethingWentWrongError), func(excep *Exception) { 55 | fmt.Println("Message:",excep.Message) 56 | fmt.Println("Exception Type:",excep.Type) 57 | }) 58 | .Finally(func() { 59 | fmt.Println("I'm Gonna fix it!") 60 | }) 61 | .Run() 62 | ``` 63 | 64 | ### You can wrap any panic with try-catch and recover it elegently 65 | 66 | ```go 67 | e.Try(func() { 68 | panic("I'm gonna panic but don't worry") 69 | }) 70 | .Catch(nil, func(excep *Exception) { 71 | fmt.Println("I knew you are gonna catch me :p", excep.Message) 72 | }) 73 | .Run() 74 | ``` 75 | -------------------------------------------------------------------------------- /exception.go: -------------------------------------------------------------------------------- 1 | package exception 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime/debug" 7 | "strings" 8 | ) 9 | 10 | type ExceptionType string 11 | 12 | const ( 13 | UnknownErrorType ExceptionType = "UnknownError" 14 | IndexErrorType ExceptionType = "IndexError" 15 | RuntimeErrorType ExceptionType = "RuntimeError" 16 | ValueErrorType ExceptionType = "ValueError" 17 | NetworkErrorType ExceptionType = "NetworkError" 18 | SyntaxErrorType ExceptionType = "SyntaxError" 19 | PermissionErrorType ExceptionType = "PermissionError" 20 | TimeoutErrorType ExceptionType = "TimeoutError" 21 | TypeErrorType ExceptionType = "TypeError" 22 | AssertionErrorType ExceptionType = "AssertionError" 23 | ConnectionErrorType ExceptionType = "ConnectionError" 24 | ReferenceErrorType ExceptionType = "ReferenceError" 25 | EOFErrorType ExceptionType = "EOFError" 26 | LookupErrorType ExceptionType = "LookupError" 27 | ) 28 | 29 | var exceptionErrorMap map[ExceptionType]string = map[ExceptionType]string{ 30 | UnknownErrorType: "Unknown Error", 31 | IndexErrorType: "Index Error", 32 | ValueErrorType: "Value Error", 33 | NetworkErrorType: "Network Error", 34 | SyntaxErrorType: "Syntax Error", 35 | PermissionErrorType: "Permission Error", 36 | TimeoutErrorType: "Timeout Error", 37 | TypeErrorType: "Type Error", 38 | AssertionErrorType: "Assertion Error", 39 | ConnectionErrorType: "Connection Error", 40 | ReferenceErrorType: "Reference Error", 41 | EOFErrorType: "EOF Error", 42 | LookupErrorType: "Lookup Error", 43 | } 44 | 45 | type Exception struct { 46 | Message string 47 | Type ExceptionType 48 | StackTrace string 49 | } 50 | 51 | //New is constructor which is used to create a new Exception 52 | func New(exceptionType ExceptionType, args ...interface{}) *Exception { 53 | message, ok := exceptionErrorMap[exceptionType] 54 | if !ok { 55 | message = string(exceptionType) 56 | } 57 | if len(args) > 0 { 58 | message = fmt.Sprintf("%v", args[0]) 59 | } 60 | return &Exception{ 61 | Message: message, 62 | Type: exceptionType, 63 | } 64 | } 65 | 66 | //AssertionError should be used to throw an exception indicating assertion failure. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 67 | func AssertionError(args ...interface{}) *Exception { 68 | return New(AssertionErrorType, args...) 69 | } 70 | 71 | //IndexError should be used to throw an exception indicating index is out of bound failure. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 72 | func IndexError(args ...interface{}) *Exception { 73 | return New(IndexErrorType, args...) 74 | } 75 | 76 | //ConnectionError should be used to throw an exception indicating connection related failure. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 77 | func ConnectionError(args ...interface{}) *Exception { 78 | return New(ConnectionErrorType, args...) 79 | } 80 | 81 | //EOFError should be used to throw an exception indicating end of file related issue. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 82 | func EOFError(args ...interface{}) *Exception { 83 | return New(EOFErrorType, args...) 84 | } 85 | 86 | //LookupError should be used to throw an exception indicating the key is unavailable in a map. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 87 | func LookupError(args ...interface{}) *Exception { 88 | return New(LookupErrorType, args...) 89 | } 90 | 91 | //NetworkError should be used to throw an exception indicating the network related issue. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 92 | func NetworkError(args ...interface{}) *Exception { 93 | return New(NetworkErrorType, args...) 94 | } 95 | 96 | //PermissionError should be used to throw an exception indicating the permission related issue. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 97 | func PermissionError(args ...interface{}) *Exception { 98 | return New(PermissionErrorType, args...) 99 | } 100 | 101 | //ReferenceError should be used to throw an exception indicating the reference related of issue. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 102 | func ReferenceError(args ...interface{}) *Exception { 103 | return New(ReferenceErrorType, args...) 104 | } 105 | 106 | //SyntaxError should be used to throw an exception indicating the reference related of issue. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 107 | func SyntaxError(args ...interface{}) *Exception { 108 | return New(SyntaxErrorType, args...) 109 | } 110 | 111 | //TypeError should be used to throw an exception indicating the type is not as expected. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 112 | func TypeError(args ...interface{}) *Exception { 113 | return New(TimeoutErrorType, args...) 114 | } 115 | 116 | //TimeoutError should be used to throw an exception indicating the Timeout related issue. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 117 | func TimeoutError(args ...interface{}) *Exception { 118 | return New(TimeoutErrorType, args...) 119 | } 120 | 121 | //ValueError should be used to throw an exception indicating the value is not in correct format. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 122 | func ValueError(args ...interface{}) *Exception { 123 | return New(ValueErrorType, args...) 124 | } 125 | 126 | //Throw is used to throw an exception. You can use some builtin Exceptions such as LookupError, PermissionError, NetworkError etc. which are offered by this library or you can also create your custom Exception and throw. It accepts error message as an optional argument. Otherwise the ExceptionType is used as default error message. 127 | func Throw(exp *Exception) { 128 | message := exp.Message 129 | errorMsg := fmt.Sprintf("Message::%s||Exception::%s", message, exp.Type) 130 | panic(errorMsg) 131 | } 132 | 133 | // In accepts a variable number of ExceptionType as arguments. It creates a list ExceptionType that will be used for the mathcing of exception. 134 | func In(exceptionTypes ...ExceptionType) []ExceptionType { 135 | return exceptionTypes 136 | } 137 | 138 | type catchblockEntry struct { 139 | Exceptions []ExceptionType 140 | Handler func(arg *Exception) 141 | } 142 | 143 | type exceptionHandler struct { 144 | exception *Exception 145 | tryHandler func() 146 | catchHandlers []catchblockEntry 147 | finallyHandler func() 148 | } 149 | 150 | //Try executes your code and finds if there is any panic or exception and passes the exception to catch block 151 | func Try(cb func()) *exceptionHandler { 152 | resp := &exceptionHandler{exception: &Exception{Message: ""}, catchHandlers: []catchblockEntry{}, finallyHandler: nil} 153 | resp.tryHandler = cb 154 | return resp 155 | } 156 | 157 | //Catch gets executed if any panic or exception occurred inside Try. You can control the execution of any Catch block by passing a e.In() matcher which listens for certain Exception to be thrown. If you pass nil as first argument then the Catch block will be executed as default if there is no matching Catch block found. 158 | func (c *exceptionHandler) Catch(exceptionTypes []ExceptionType, cb func(excep *Exception)) *exceptionHandler { 159 | c.catchHandlers = append(c.catchHandlers, catchblockEntry{Exceptions: exceptionTypes, Handler: cb}) 160 | return c 161 | } 162 | 163 | //Finally is executed always even if the Try block Succeeds or Fails. But it won't be executed if there is a uncaught or unhandled Exception 164 | func (c *exceptionHandler) Finally(cb func()) *exceptionHandler { 165 | c.finallyHandler = cb 166 | return c 167 | } 168 | 169 | //Run must be called to run the Try-Catch-Finally handlers. It should be always invoked at the end of the chain operations. It triggers the execution of Exception Handling flow. 170 | func (c *exceptionHandler) Run() { 171 | c.executeTry() 172 | c.executeCatchHanlder() 173 | c.executeFinally() 174 | } 175 | 176 | func (c *exceptionHandler) executeTry() { 177 | defer func() { 178 | err := recover() 179 | if err != nil { 180 | value := reflect.ValueOf(err) 181 | errorMessage := fmt.Sprintf("%v", value) 182 | if !strings.Contains(errorMessage, "||") { 183 | errorMessage = fmt.Sprintf("Message::%s||Exception::%s", errorMessage, "RuntimeError") 184 | 185 | } 186 | c.exception = &Exception{ 187 | Message: errorMessage, 188 | StackTrace: string(debug.Stack()), 189 | } 190 | } 191 | }() 192 | c.tryHandler() 193 | } 194 | 195 | func (c *exceptionHandler) getExceptionType() string { 196 | messageItems := strings.Split(c.exception.Message, "||") 197 | if len(messageItems) > 0 { 198 | exceptionPart := messageItems[1] 199 | parts := strings.Split(exceptionPart, "::") 200 | if len(parts) > 1 { 201 | return parts[1] 202 | } 203 | } 204 | return "" 205 | } 206 | 207 | func (c *exceptionHandler) getMessage() string { 208 | messageItems := strings.Split(c.exception.Message, "||") 209 | if len(messageItems) > 0 { 210 | messagePart := messageItems[0] 211 | splitedMessage := strings.Split(messagePart, "::") 212 | if len(splitedMessage) > 1 { 213 | return splitedMessage[1] 214 | } 215 | } 216 | return "" 217 | } 218 | 219 | func (c *exceptionHandler) executeCatchHanlder() { 220 | if len(c.catchHandlers) == 0 { 221 | return 222 | } 223 | if c.exception != nil && len(c.exception.Message) > 0 { 224 | var defaultHandler func(_ *Exception) 225 | for _, handler := range c.catchHandlers { 226 | if handler.Exceptions != nil && len(handler.Exceptions) > 0 { 227 | for _, exceptionType := range handler.Exceptions { 228 | exceptionTypePart := c.getExceptionType() 229 | if exceptionTypePart == string(exceptionType) { 230 | c.exception.Type = ExceptionType(exceptionTypePart) 231 | c.exception.Message = c.getMessage() 232 | handler.Handler(c.exception) 233 | return 234 | } 235 | } 236 | } else { 237 | defaultHandler = handler.Handler 238 | } 239 | } 240 | if defaultHandler != nil { 241 | c.exception.Type = ExceptionType(c.getExceptionType()) 242 | c.exception.Message = c.getMessage() 243 | defaultHandler(c.exception) 244 | } 245 | } 246 | } 247 | 248 | func (c *exceptionHandler) executeFinally() { 249 | if c.finallyHandler != nil { 250 | c.finallyHandler() 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /exception_test.go: -------------------------------------------------------------------------------- 1 | package exception 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestTryGetsExecuted(t *testing.T) { 9 | var hasTryExecuted bool 10 | Try(func() { 11 | hasTryExecuted = true 12 | }). 13 | Catch(nil, func(excep *Exception) {}). 14 | Run() 15 | 16 | if hasTryExecuted == false { 17 | t.Fatal("Try was not executed") 18 | } 19 | } 20 | 21 | func TestCatchGetsExecutedForAllErrorsIfExceptionThrownInTry(t *testing.T) { 22 | var exceptionType ExceptionType 23 | var hasCaughtException bool 24 | Try(func() { 25 | Throw(ReferenceError("Dummy error text")) 26 | }). 27 | Catch(nil, func(excep *Exception) { 28 | hasCaughtException = true 29 | exceptionType = excep.Type 30 | }).Run() 31 | 32 | if hasCaughtException == false { 33 | t.Fatal("Try was not executed") 34 | } 35 | if exceptionType != ReferenceErrorType { 36 | t.Fatalf("Expecting %s but found %s", ReferenceErrorType, exceptionType) 37 | } 38 | } 39 | 40 | func TestFinallyGetsExecutedAlways(t *testing.T) { 41 | var hasFinallyExecuted bool 42 | Try(func() { 43 | Throw(NetworkError()) 44 | }). 45 | Catch(nil, func(excep *Exception) {}). 46 | Finally(func() { 47 | hasFinallyExecuted = true 48 | }). 49 | Run() 50 | 51 | if hasFinallyExecuted == false { 52 | t.Fatal("Finally was not executed") 53 | } 54 | } 55 | 56 | func TestExpectedCatchBlockGetsExecutedForDefinedExceptionType(t *testing.T) { 57 | var thrownExceptionType ExceptionType 58 | var caughtFrom string 59 | Try(func() { 60 | user := map[string]string{ 61 | "name": "John Doe", 62 | } 63 | _, ok := user["email"] 64 | if !ok { 65 | Throw(LookupError("Email doesn't exist")) 66 | } 67 | }). 68 | Catch(In(LookupErrorType), func(excep *Exception) { 69 | thrownExceptionType = excep.Type 70 | caughtFrom = "LookupErrorHandler" 71 | }). 72 | Catch(In(ReferenceErrorType, IndexErrorType), func(excep *Exception) { 73 | thrownExceptionType = excep.Type 74 | caughtFrom = "ReferenceErrorHandler" 75 | }). 76 | Catch(nil, func(excep *Exception) { 77 | thrownExceptionType = excep.Type 78 | caughtFrom = "DefaultExceptionHandler" 79 | }).Run() 80 | 81 | if thrownExceptionType != LookupErrorType { 82 | t.Fatalf("Expecting Exception type to be %s but found %s", LookupErrorType, thrownExceptionType) 83 | } 84 | 85 | if caughtFrom != "LookupErrorHandler" { 86 | t.Fatalf("Expecting Catch block to be %s but found %s", "LookupErrorHandler", caughtFrom) 87 | } 88 | } 89 | 90 | func TestDefaultCatchBlockGetsExecutedForUnmatchedException(t *testing.T) { 91 | var thrownExceptionType ExceptionType 92 | var caughtFrom string 93 | Try(func() { 94 | Throw(New(UnknownErrorType, "Unknown Error")) 95 | }). 96 | Catch(In(LookupErrorType), func(excep *Exception) { 97 | thrownExceptionType = excep.Type 98 | caughtFrom = "LookupErrorHandler" 99 | }). 100 | Catch(In(ReferenceErrorType, IndexErrorType), func(excep *Exception) { 101 | thrownExceptionType = excep.Type 102 | caughtFrom = "ReferenceErrorHandler" 103 | }). 104 | Catch(nil, func(excep *Exception) { 105 | thrownExceptionType = excep.Type 106 | caughtFrom = "DefaultExceptionHandler" 107 | }).Run() 108 | 109 | if thrownExceptionType != UnknownErrorType { 110 | t.Fatalf("Expecting Exception type to be %s but found %s", UnknownErrorType, thrownExceptionType) 111 | } 112 | 113 | if caughtFrom != "DefaultExceptionHandler" { 114 | t.Fatalf("Expecting Catch block to be %s but found %s", "LookupErrorHandler", caughtFrom) 115 | } 116 | } 117 | 118 | func TestAllPanicGetsRecoveredWithinTryCatch(t *testing.T) { 119 | var panicAttack = "Something went very wrong!" 120 | var caughtMessage string 121 | Try(func() { 122 | panic(panicAttack) 123 | }). 124 | Catch(nil, func(excep *Exception) { 125 | caughtMessage = excep.Message 126 | }). 127 | Run() 128 | 129 | if caughtMessage != panicAttack { 130 | t.Fatal("Could not recover panic attack!") 131 | } 132 | } 133 | 134 | func TestNestedExceptionWasHandledAsExpected(t *testing.T) { 135 | var firstThrownExceptionType ExceptionType 136 | var secondThrownExceptionType ExceptionType 137 | var hasCaughtNestedException bool 138 | var CustomExceptionType ExceptionType = "CustomException" 139 | 140 | Try(func() { 141 | Throw(ReferenceError("Dummy error text")) 142 | }). 143 | Catch(nil, func(excep1 *Exception) { 144 | Try(func() { 145 | // trying to save the world 146 | Throw(New(CustomExceptionType, "Custome Error Message")) 147 | }).Catch(In(CustomExceptionType), func(excep2 *Exception) { 148 | firstThrownExceptionType = excep1.Type 149 | secondThrownExceptionType = excep2.Type 150 | hasCaughtNestedException = true 151 | }).Run() 152 | }).Run() 153 | 154 | if hasCaughtNestedException == false { 155 | t.Fatal("Nested exception was not handled") 156 | } 157 | 158 | if firstThrownExceptionType != ReferenceErrorType { 159 | t.Fatalf("Expecting first exception to be %s but found %s", ReferenceErrorType, firstThrownExceptionType) 160 | } 161 | 162 | if secondThrownExceptionType != CustomExceptionType { 163 | t.Fatalf("Expecting second exception to be %s but found %s", CustomExceptionType, secondThrownExceptionType) 164 | } 165 | } 166 | 167 | func TestPanicIsRecoveredInDefaultCatch(t *testing.T) { 168 | errorMsg := "I'm gonna panic but don't worry" 169 | var caughtErrorMsg string 170 | Try(func() { 171 | panic(errorMsg) 172 | }).Catch(nil, func(excep *Exception) { 173 | caughtErrorMsg = excep.Message 174 | fmt.Println("I knew you are gonna catch me :p", excep.Message) 175 | }).Run() 176 | 177 | if caughtErrorMsg != errorMsg { 178 | t.Fatalf("Expecting error message to be %s but found %s", errorMsg, caughtErrorMsg) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rbrahul/exception 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/exception/78764b6b70c698e80abfa770dd02f86ee71e944f/gopher.png --------------------------------------------------------------------------------