golem_schema/schema/render/error.rs
1// Copyright 2024-2026 Golem Cloud
2//
3// Licensed under the Golem Source License v1.1 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://license.golem.cloud/LICENSE
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Shared error type for the renderer family.
16
17use crate::schema::canonical::ParseError;
18use std::fmt::{Display, Formatter};
19
20/// Errors produced by the schema/value renderers.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub enum RenderError {
23 /// A [`crate::schema::SchemaValue`] did not match the shape required by
24 /// the accompanying [`crate::schema::SchemaType`].
25 ValueMismatch { path: String, reason: String },
26 /// A construct is recognised but not supported by the renderer (e.g.,
27 /// `Future` / `Stream` payloads).
28 Unsupported(&'static str),
29 /// A canonical scalar encoder or decoder rejected the input.
30 Canonical(ParseError),
31 /// A JSON conversion failed (e.g., a number out of range for the
32 /// target integer width).
33 Json(String),
34 /// A record decoder saw a JSON object with a field that is not declared
35 /// by the schema.
36 UnexpectedField { record: String, field: String },
37 /// A flags decoder saw the same flag name listed twice.
38 DuplicateFlag { flag: String },
39 /// A union encoder picked a branch by tag but the branch body did not
40 /// satisfy the branch's discriminator rule.
41 UnionTagMismatch { tag: String, reason: String },
42 /// A union decoder found multiple branches whose discriminator rules
43 /// match the same incoming value (a structural ambiguity that should
44 /// have been caught at validation; runtime is a safety net).
45 UnionAmbiguous { matched: Vec<String> },
46 /// A union decoder found no branch whose discriminator rule matches the
47 /// incoming value.
48 UnionNoMatch,
49}
50
51impl Display for RenderError {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 match self {
54 RenderError::ValueMismatch { path, reason } => {
55 write!(f, "value mismatch at {path}: {reason}")
56 }
57 RenderError::Unsupported(what) => write!(f, "unsupported: {what}"),
58 RenderError::Canonical(inner) => write!(f, "canonical: {inner}"),
59 RenderError::Json(msg) => write!(f, "json: {msg}"),
60 RenderError::UnexpectedField { record, field } => {
61 write!(f, "unexpected field `{field}` on record `{record}`")
62 }
63 RenderError::DuplicateFlag { flag } => write!(f, "duplicate flag `{flag}`"),
64 RenderError::UnionTagMismatch { tag, reason } => {
65 write!(
66 f,
67 "union branch `{tag}` rejected by discriminator: {reason}"
68 )
69 }
70 RenderError::UnionAmbiguous { matched } => write!(
71 f,
72 "value matches multiple union branches: {}",
73 matched.join(", ")
74 ),
75 RenderError::UnionNoMatch => write!(f, "no union branch matched"),
76 }
77 }
78}
79
80impl std::error::Error for RenderError {}
81
82impl From<ParseError> for RenderError {
83 fn from(value: ParseError) -> Self {
84 RenderError::Canonical(value)
85 }
86}